Merge "SmartMonkey log parser and unit tests"
diff --git a/src/com/android/loganalysis/item/BugreportItem.java b/src/com/android/loganalysis/item/BugreportItem.java
index 11a41fe..95b36a8 100644
--- a/src/com/android/loganalysis/item/BugreportItem.java
+++ b/src/com/android/loganalysis/item/BugreportItem.java
@@ -29,11 +29,13 @@
     private static final String TIME = "TIME";
     private static final String MEM_INFO = "MEM_INFO";
     private static final String PROCRANK = "PROCRANK";
+    private static final String TOP = "TOP";
     private static final String SYSTEM_LOG = "SYSTEM_LOG";
     private static final String SYSTEM_PROPS = "SYSTEM_PROPS";
+    private static final String DUMPSYS = "DUMPSYS";
 
     private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList(
-            TIME, MEM_INFO, PROCRANK, SYSTEM_LOG, SYSTEM_PROPS));
+            TIME, MEM_INFO, PROCRANK, TOP, SYSTEM_LOG, SYSTEM_PROPS, DUMPSYS));
 
     /**
      * The constructor for {@link BugreportItem}.
@@ -85,6 +87,20 @@
     }
 
     /**
+     * Get the {@link TopItem} of the bugreport.
+     */
+    public TopItem getTop() {
+        return (TopItem) getAttribute(TOP);
+    }
+
+    /**
+     * Set the {@link TopItem} of the bugreport.
+     */
+    public void setTop(TopItem top) {
+        setAttribute(TOP, top);
+    }
+
+    /**
      * Get the {@link LogcatItem} of the bugreport.
      */
     public LogcatItem getSystemLog() {
@@ -111,4 +127,18 @@
     public void setSystemProps(SystemPropsItem systemProps) {
         setAttribute(SYSTEM_PROPS, systemProps);
     }
+
+    /**
+     * Get the {@link DumpsysItem} of the bugreport.
+     */
+    public DumpsysItem getDumpsys() {
+        return (DumpsysItem) getAttribute(DUMPSYS);
+    }
+
+    /**
+     * Set the {@link DumpsysItem} of the bugreport.
+     */
+    public void setDumpsys(DumpsysItem dumpsys) {
+        setAttribute(DUMPSYS, dumpsys);
+    }
 }
diff --git a/src/com/android/loganalysis/item/DumpsysBatteryInfoItem.java b/src/com/android/loganalysis/item/DumpsysBatteryInfoItem.java
new file mode 100644
index 0000000..196932d
--- /dev/null
+++ b/src/com/android/loganalysis/item/DumpsysBatteryInfoItem.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.loganalysis.item;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * An {@link IItem} used to store the battery info part of the dumpsys output.
+ */
+public class DumpsysBatteryInfoItem implements IItem {
+    public static final String TYPE = "DUMPSYS_BATTERY_INFO";
+
+    /**
+     * A class designed to store information related to wake locks and kernel wake locks.
+     */
+    public class WakeLock {
+        private String mName;
+        private Integer mNumber;
+        private long mHeldTime;
+        private int mLockedCount;
+
+        /**
+         * The constructor for {@link WakeLock}
+         *
+         * @param name The name of the wake lock
+         * @param heldTime The amount of time held in milliseconds
+         * @param lockedCount The number of times the wake lock was locked
+         */
+        public WakeLock(String name, long heldTime, int lockedCount) {
+            this(name, null, heldTime, lockedCount);
+        }
+
+        /**
+         * The constructor for {@link WakeLock}
+         *
+         * @param name The name of the wake lock
+         * @param number The number of the wake lock
+         * @param heldTime The amount of time held in milliseconds
+         * @param lockedCount The number of times the wake lock was locked
+         */
+        public WakeLock(String name, Integer number, long heldTime, int lockedCount) {
+            mName = name;
+            mNumber = number;
+            mHeldTime = heldTime;
+            mLockedCount = lockedCount;
+        }
+
+        /**
+         * Get the name of the wake lock.
+         */
+        public String getName() {
+            return mName;
+        }
+
+        /**
+         * Get the number of the wake lock.
+         */
+        public Integer getNumber() {
+            return mNumber;
+        }
+
+        /**
+         * Get the time the wake lock was held in milliseconds.
+         */
+        public long getHeldTime() {
+            return mHeldTime;
+        }
+
+        /**
+         * Get the number of times the wake lock was locked.
+         */
+        public int getLockedCount() {
+            return mLockedCount;
+        }
+    }
+
+    private List<WakeLock> mLastUnpluggedKernelWakeLocks = new LinkedList<WakeLock>();
+    private List<WakeLock> mLastUnpluggedWakeLocks = new LinkedList<WakeLock>();
+
+    /**
+     * Add a kernel wake lock from the last unplugged section of the battery info.
+     */
+    public void addLastUnpluggedKernelWakeLock(String name, long heldTime, int timesCalled) {
+        mLastUnpluggedKernelWakeLocks.add(new WakeLock(name, heldTime, timesCalled));
+    }
+
+    /**
+     * Add a wake lock from the last unplugged section of the battery info.
+     */
+    public void addLastUnpluggedWakeLock(String name, int number, long heldTime, int timesCalled) {
+        mLastUnpluggedWakeLocks.add(new WakeLock(name, number, heldTime, timesCalled));
+    }
+
+    /**
+     * Get the list of kernel wake locks from the last unplugged section of the battery info.
+     */
+    public List<WakeLock> getLastUnpluggedKernelWakeLock() {
+        return mLastUnpluggedKernelWakeLocks;
+    }
+
+    /**
+     * Get the list of wake locks from the last unplugged section of the battery info.
+     */
+    public List<WakeLock> getLastUnpluggedWakeLock() {
+        return mLastUnpluggedWakeLocks;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getType() {
+        return TYPE;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IItem merge(IItem other) throws ConflictingItemException {
+        throw new ConflictingItemException("Dumpsys battery info items cannot be merged");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isConsistent(IItem other) {
+        return false;
+    }
+
+}
diff --git a/src/com/android/loganalysis/item/DumpsysItem.java b/src/com/android/loganalysis/item/DumpsysItem.java
new file mode 100644
index 0000000..ed13b9c
--- /dev/null
+++ b/src/com/android/loganalysis/item/DumpsysItem.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.loganalysis.item;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * An {@link IItem} used to store the output of the dumpsys section of the bugreport.
+ */
+public class DumpsysItem extends GenericItem {
+    public static final String TYPE = "DUMPSYS";
+
+    private static final String BATTERY_INFO = "BATTERY_INFO";
+
+    private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList(BATTERY_INFO));
+
+    /**
+     * The constructor for {@link BugreportItem}.
+     */
+    public DumpsysItem() {
+        super(TYPE, ATTRIBUTES);
+    }
+
+    /**
+     * Get the battery info section of the dumpsys.
+     */
+    public DumpsysBatteryInfoItem getBatteryInfo() {
+        return (DumpsysBatteryInfoItem) getAttribute(BATTERY_INFO);
+    }
+
+    /**
+     * Set the battery info section of the dumpsys.
+     */
+    public void setBatteryInfo(DumpsysBatteryInfoItem batteryInfo) {
+        setAttribute(BATTERY_INFO, batteryInfo);
+    }
+}
diff --git a/src/com/android/loganalysis/item/ProcrankItem.java b/src/com/android/loganalysis/item/ProcrankItem.java
index a2d7e74..32d4ad1 100644
--- a/src/com/android/loganalysis/item/ProcrankItem.java
+++ b/src/com/android/loganalysis/item/ProcrankItem.java
@@ -27,7 +27,7 @@
     public static final String TYPE = "PROCRANK";
 
     private class ProcrankValue {
-        public String mProcessName = null;
+        public String mProcessName;
         public int mVss;
         public int mRss;
         public int mPss;
diff --git a/src/com/android/loganalysis/item/TopItem.java b/src/com/android/loganalysis/item/TopItem.java
new file mode 100644
index 0000000..bf15f7e
--- /dev/null
+++ b/src/com/android/loganalysis/item/TopItem.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.loganalysis.item;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * An {@link IItem} used to store the output of the top command.
+ */
+public class TopItem extends GenericItem {
+    public static final String TYPE = "TOP";
+
+    private static final String USER = "USER";
+    private static final String NICE = "NICE";
+    private static final String SYSTEM = "SYSTEM";
+    private static final String IDLE = "IDLE";
+    private static final String IOW = "IOW";
+    private static final String IRQ = "IRQ";
+    private static final String SIRQ = "SIRQ";
+    private static final String TOTAL = "TOTAL";
+
+    private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList(
+            USER, NICE, SYSTEM, IDLE, IOW, IRQ, SIRQ, TOTAL));
+
+    /**
+     * The constructor for {@link TopItem}.
+     */
+    public TopItem() {
+        super(TYPE, ATTRIBUTES);
+
+        for (String attribute : ATTRIBUTES) {
+            setAttribute(attribute, 0);
+        }
+    }
+
+    /**
+     * Get the number of user ticks.
+     */
+    public int getUser() {
+        return (Integer) getAttribute(USER);
+    }
+
+    /**
+     * Set the number of user ticks.
+     */
+    public void setUser(int user) {
+        setAttribute(USER, user);
+    }
+
+    /**
+     * Get the number of nice ticks.
+     */
+    public int getNice() {
+        return (Integer) getAttribute(NICE);
+    }
+
+    /**
+     * Set the number of nice ticks.
+     */
+    public void setNice(int nice) {
+        setAttribute(NICE, nice);
+    }
+
+    /**
+     * Get the number of system ticks.
+     */
+    public int getSystem() {
+        return (Integer) getAttribute(SYSTEM);
+    }
+
+    /**
+     * Set the number of system ticks.
+     */
+    public void setSystem(int system) {
+        setAttribute(SYSTEM, system);
+    }
+
+    /**
+     * Get the number of idle ticks.
+     */
+    public int getIdle() {
+        return (Integer) getAttribute(IDLE);
+    }
+
+    /**
+     * Set the number of idle ticks.
+     */
+    public void setIdle(int idle) {
+        setAttribute(IDLE, idle);
+    }
+
+    /**
+     * Get the number of IOW ticks.
+     */
+    public int getIow() {
+        return (Integer) getAttribute(IOW);
+    }
+
+    /**
+     * Set the number of IOW ticks.
+     */
+    public void setIow(int iow) {
+        setAttribute(IOW, iow);
+    }
+
+    /**
+     * Get the number of IRQ ticks.
+     */
+    public int getIrq() {
+        return (Integer) getAttribute(IRQ);
+    }
+
+    /**
+     * Set the number of IRQ ticks.
+     */
+    public void setIrq(int irq) {
+        setAttribute(IRQ, irq);
+    }
+
+    /**
+     * Get the number of SIRQ ticks.
+     */
+    public int getSirq() {
+        return (Integer) getAttribute(SIRQ);
+    }
+
+    /**
+     * Set the number of SIRQ ticks.
+     */
+    public void setSirq(int sirq) {
+        setAttribute(SIRQ, sirq);
+    }
+
+    /**
+     * Get the number of total ticks.
+     */
+    public int getTotal() {
+        return (Integer) getAttribute(TOTAL);
+    }
+
+    /**
+     * Set the number of total ticks.
+     */
+    public void setTotal(int total) {
+        setAttribute(TOTAL, total);
+    }
+}
diff --git a/src/com/android/loganalysis/parser/BugreportParser.java b/src/com/android/loganalysis/parser/BugreportParser.java
index 748f751..aed156c 100644
--- a/src/com/android/loganalysis/parser/BugreportParser.java
+++ b/src/com/android/loganalysis/parser/BugreportParser.java
@@ -17,12 +17,14 @@
 
 import com.android.loganalysis.item.AnrItem;
 import com.android.loganalysis.item.BugreportItem;
+import com.android.loganalysis.item.DumpsysItem;
 import com.android.loganalysis.item.GenericLogcatItem;
 import com.android.loganalysis.item.IItem;
 import com.android.loganalysis.item.LogcatItem;
 import com.android.loganalysis.item.MemInfoItem;
 import com.android.loganalysis.item.ProcrankItem;
 import com.android.loganalysis.item.SystemPropsItem;
+import com.android.loganalysis.item.TopItem;
 import com.android.loganalysis.item.TracesItem;
 
 import java.io.BufferedReader;
@@ -42,10 +44,12 @@
 public class BugreportParser extends AbstractSectionParser {
     private static final String MEM_INFO_SECTION_REGEX = "------ MEMORY INFO .*";
     private static final String PROCRANK_SECTION_REGEX = "------ PROCRANK .*";
+    private static final String TOP_SECTION_REGEX = "------ CPU INFO .*";
     private static final String SYSTEM_PROP_SECTION_REGEX = "------ SYSTEM PROPERTIES .*";
     private static final String SYSTEM_LOG_SECTION_REGEX =
             "------ (SYSTEM|MAIN|MAIN AND SYSTEM) LOG .*";
     private static final String ANR_TRACES_SECTION_REGEX = "------ VM TRACES AT LAST ANR .*";
+    private static final String DUMPSYS_SECTION_REGEX = "------ DUMPSYS .*";
     private static final String NOOP_SECTION_REGEX = "------ .*";
 
     /**
@@ -113,9 +117,11 @@
         });
         addSectionParser(new MemInfoParser(), MEM_INFO_SECTION_REGEX);
         addSectionParser(new ProcrankParser(), PROCRANK_SECTION_REGEX);
+        addSectionParser(new TopParser(), TOP_SECTION_REGEX);
         addSectionParser(new SystemPropsParser(), SYSTEM_PROP_SECTION_REGEX);
         addSectionParser(new TracesParser(), ANR_TRACES_SECTION_REGEX);
         addSectionParser(mLogcatParser, SYSTEM_LOG_SECTION_REGEX);
+        addSectionParser(new DumpsysParser(), DUMPSYS_SECTION_REGEX);
         addSectionParser(new NoopParser(), NOOP_SECTION_REGEX);
     }
 
@@ -130,8 +136,10 @@
         if (mBugreport != null) {
             mBugreport.setMemInfo((MemInfoItem) getSection(MemInfoItem.TYPE));
             mBugreport.setProcrank((ProcrankItem) getSection(ProcrankItem.TYPE));
+            mBugreport.setTop((TopItem) getSection(TopItem.TYPE));
             mBugreport.setSystemLog((LogcatItem) getSection(LogcatItem.TYPE));
             mBugreport.setSystemProps((SystemPropsItem) getSection(SystemPropsItem.TYPE));
+            mBugreport.setDumpsys((DumpsysItem) getSection(DumpsysItem.TYPE));
 
             if (mBugreport.getSystemLog() != null && mBugreport.getProcrank() != null) {
                 for (IItem item : mBugreport.getSystemLog().getEvents()) {
diff --git a/src/com/android/loganalysis/parser/DumpsysBatteryInfoParser.java b/src/com/android/loganalysis/parser/DumpsysBatteryInfoParser.java
new file mode 100644
index 0000000..5109966
--- /dev/null
+++ b/src/com/android/loganalysis/parser/DumpsysBatteryInfoParser.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.loganalysis.parser;
+
+import com.android.loganalysis.item.DumpsysBatteryInfoItem;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A {@link IParser} to handle the "dumpsys batteryinfo" command output.
+ */
+public class DumpsysBatteryInfoParser implements IParser {
+    private static final Pattern LAST_UNPLUGGED_START_PAT = Pattern.compile(
+            "^Statistics since last unplugged:$");
+    private static final Pattern WAKE_LOCK_START_PAT = Pattern.compile(
+            "^  All partial wake locks:$");
+
+    private static final String WAKE_LOCK_PAT_SUFFIX =
+            "((\\d+)d )?((\\d+)h )?((\\d+)m )?((\\d+)s )?((\\d+)ms )?\\((\\d+) times\\) realtime";
+
+    /**
+     * Match a valid line such as:
+     * "  Kernel Wake lock \"Process\": 1d 2h 3m 4s 5ms (6 times) realtime";
+     */
+    private static final Pattern KERNEL_WAKE_LOCK_PAT = Pattern.compile(
+            "^  Kernel Wake lock \"([^\"]+)\": " + WAKE_LOCK_PAT_SUFFIX);
+    /**
+     * Match a valid line such as:
+     * "  Wake lock #1234 Process: 1d 2h 3m 4s 5ms (6 times) realtime";
+     */
+    private static final Pattern WAKE_LOCK_PAT = Pattern.compile(
+            "^  Wake lock #(\\d+) (.+): " + WAKE_LOCK_PAT_SUFFIX);
+
+    private DumpsysBatteryInfoItem mItem = new DumpsysBatteryInfoItem();
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public DumpsysBatteryInfoItem parse(List<String> lines) {
+        boolean inLastUnplugged = false;
+        boolean inKernelWakeLock = false;
+        boolean inWakeLock = false;
+
+        // Look for the section for last unplugged statistics.  Kernel wakelocks are in the lines
+        // immediately following, until a blank line. Partial wake locks are in their own block,
+        // until a blank line. Return immediately after since there is nothing left to parse.
+        for (String line : lines) {
+            if (!inLastUnplugged) {
+                Matcher m = LAST_UNPLUGGED_START_PAT.matcher(line);
+                if (m.matches()) {
+                    inLastUnplugged = true;
+                    inKernelWakeLock = true;
+                }
+            } else {
+                if (inKernelWakeLock) {
+                    if ("".equals(line.trim())) {
+                        inKernelWakeLock = false;
+                    } else {
+                        parseLastUnpluggedKernelWakeLock(line);
+                    }
+                } else if (inWakeLock) {
+                    if ("".equals(line.trim())) {
+                        inWakeLock = false;
+                        return mItem;
+                    } else {
+                        parseLastUnpluggedWakeLock(line);
+                    }
+                } else {
+                    Matcher m = WAKE_LOCK_START_PAT.matcher(line);
+                    if (m.matches()) {
+                        inWakeLock = true;
+                    }
+                }
+            }
+        }
+        return mItem;
+    }
+
+    /**
+     * Parse a line of output and add it to the last unplugged kernel wake lock section.
+     * <p>
+     * Exposed for unit testing.
+     * </p>
+     */
+    void parseLastUnpluggedKernelWakeLock(String line) {
+        Matcher m = KERNEL_WAKE_LOCK_PAT.matcher(line);
+        if (!m.matches()) {
+            return;
+        }
+
+        final String name = m.group(1);
+        final long days = parseLongOrZero(m.group(3));
+        final long hours = parseLongOrZero(m.group(5));
+        final long mins = parseLongOrZero(m.group(7));
+        final long secs = parseLongOrZero(m.group(9));
+        final long msecs = parseLongOrZero(m.group(11));
+        final int timesCalled = Integer.parseInt(m.group(12));
+
+        mItem.addLastUnpluggedKernelWakeLock(name, getMs(days, hours, mins, secs, msecs),
+                timesCalled);
+    }
+
+    /**
+     * Parse a line of output and add it to the last unplugged wake lock section.
+     * <p>
+     * Exposed for unit testing.
+     * </p>
+     */
+    void parseLastUnpluggedWakeLock(String line) {
+        Matcher m = WAKE_LOCK_PAT.matcher(line);
+        if (!m.matches()) {
+            return;
+        }
+
+        final int number = Integer.parseInt(m.group(1));
+        final String name = m.group(2);
+        final long days = parseLongOrZero(m.group(4));
+        final long hours = parseLongOrZero(m.group(6));
+        final long mins = parseLongOrZero(m.group(8));
+        final long secs = parseLongOrZero(m.group(10));
+        final long msecs = parseLongOrZero(m.group(12));
+        final int timesCalled = Integer.parseInt(m.group(13));
+
+        mItem.addLastUnpluggedWakeLock(name, number, getMs(days, hours, mins, secs, msecs),
+                timesCalled);
+    }
+
+    /**
+     * Get the {@link DumpsysBatteryInfoItem}.
+     * <p>
+     * Exposed for unit testing.
+     * </p>
+     */
+    DumpsysBatteryInfoItem getItem() {
+        return mItem;
+    }
+
+    /**
+     * Convert days/hours/mins/secs/msecs into milliseconds.
+     * <p>
+     * Exposed for unit testing.
+     * </p>
+     */
+    static long getMs(long days, long hours, long mins, long secs, long msecs) {
+        return (((24 * days + hours) * 60 + mins) * 60 + secs) * 1000 + msecs;
+    }
+
+    /**
+     * Parses a string into a long, or returns 0 if the string is null.
+     *
+     * @param s a {@link String} containing the long representation to be parsed
+     * @return the long represented by the argument in decimal, or 0 if the string is {@link null}.
+     * @throws NumberFormatException if the string is not {@link null} or does not contain a
+     * parsable long.
+     */
+    private long parseLongOrZero(String s) {
+        if (s == null) {
+            return 0;
+        }
+        return Long.parseLong(s);
+    }
+}
diff --git a/src/com/android/loganalysis/parser/DumpsysParser.java b/src/com/android/loganalysis/parser/DumpsysParser.java
new file mode 100644
index 0000000..151db33
--- /dev/null
+++ b/src/com/android/loganalysis/parser/DumpsysParser.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.loganalysis.parser;
+
+import com.android.loganalysis.item.DumpsysBatteryInfoItem;
+import com.android.loganalysis.item.DumpsysItem;
+
+import java.util.List;
+
+/**
+ * A {@link IParser} to handle the output of the dumpsys section of the bugreport.
+ */
+public class DumpsysParser extends AbstractSectionParser {
+    private static final String BATTERY_INFO_SECTION_REGEX = "DUMP OF SERVICE batteryinfo:";
+    private static final String NOOP_SECTION_REGEX = "DUMP OF SERVICE .*";
+
+    private DumpsysItem mDumpsys = new DumpsysItem();
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return The {@link DumpsysItem}
+     */
+    public DumpsysItem parse(List<String> lines) {
+        setup();
+        for (String line : lines) {
+            parseLine(line);
+        }
+        commit();
+
+        return mDumpsys;
+    }
+
+    /**
+     * Sets up the parser by adding the section parsers.
+     */
+    protected void setup() {
+        addSectionParser(new DumpsysBatteryInfoParser(), BATTERY_INFO_SECTION_REGEX);
+        addSectionParser(new NoopParser(), NOOP_SECTION_REGEX);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void commit() {
+        // signal EOF
+        super.commit();
+
+        if (mDumpsys != null) {
+            mDumpsys.setBatteryInfo(
+                    (DumpsysBatteryInfoItem) getSection(DumpsysBatteryInfoItem.TYPE));
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/loganalysis/parser/TopParser.java b/src/com/android/loganalysis/parser/TopParser.java
new file mode 100644
index 0000000..e5022c3
--- /dev/null
+++ b/src/com/android/loganalysis/parser/TopParser.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.loganalysis.parser;
+
+import com.android.loganalysis.item.TopItem;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A {@link IParser} to handle the output of the top command output.
+ * <p>
+ * The parser only record the last top entry if multiple entries are printed by the top command. It
+ * only parses the total cpu usage.
+ * </p>
+ */
+public class TopParser implements IParser {
+
+    /**
+     * Match a valid cpu ticks line, such as:
+     * "User 5 + Nice 0 + Sys 14 + Idle 207 + IOW 0 + IRQ 0 + SIRQ 0 = 226"
+     */
+    private static final Pattern TICKS_PAT = Pattern.compile(
+            "User (\\d+) \\+ Nice (\\d+) \\+ Sys (\\d+) \\+ Idle (\\d+) \\+ IOW (\\d+) \\+ " +
+            "IRQ (\\d+) \\+ SIRQ (\\d+) = (\\d+)");
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public TopItem parse(List<String> lines) {
+        TopItem item = new TopItem();
+
+        for (String line : lines) {
+            Matcher m = TICKS_PAT.matcher(line);
+            if (m.matches()) {
+                item.setUser(Integer.parseInt(m.group(1)));
+                item.setNice(Integer.parseInt(m.group(2)));
+                item.setSystem(Integer.parseInt(m.group(3)));
+                item.setIdle(Integer.parseInt(m.group(4)));
+                item.setIow(Integer.parseInt(m.group(5)));
+                item.setIrq(Integer.parseInt(m.group(6)));
+                item.setSirq(Integer.parseInt(m.group(7)));
+                item.setTotal(Integer.parseInt(m.group(8)));
+            }
+        }
+        return item;
+    }
+}
diff --git a/tests/src/com/android/loganalysis/UnitTests.java b/tests/src/com/android/loganalysis/UnitTests.java
index 083d571..09a42d9 100644
--- a/tests/src/com/android/loganalysis/UnitTests.java
+++ b/tests/src/com/android/loganalysis/UnitTests.java
@@ -20,6 +20,8 @@
 import com.android.loganalysis.parser.AbstractSectionParserTest;
 import com.android.loganalysis.parser.AnrParserTest;
 import com.android.loganalysis.parser.BugreportParserTest;
+import com.android.loganalysis.parser.DumpsysBatteryInfoParserTest;
+import com.android.loganalysis.parser.DumpsysParserTest;
 import com.android.loganalysis.parser.JavaCrashParserTest;
 import com.android.loganalysis.parser.LogcatParserTest;
 import com.android.loganalysis.parser.MemInfoParserTest;
@@ -27,6 +29,7 @@
 import com.android.loganalysis.parser.NativeCrashParserTest;
 import com.android.loganalysis.parser.ProcrankParserTest;
 import com.android.loganalysis.parser.SystemPropsParserTest;
+import com.android.loganalysis.parser.TopParserTest;
 import com.android.loganalysis.parser.TracesParserTest;
 import com.android.loganalysis.util.ArrayUtilTest;
 import com.android.loganalysis.util.RegexTrieTest;
@@ -51,6 +54,8 @@
         addTestSuite(AbstractSectionParserTest.class);
         addTestSuite(AnrParserTest.class);
         addTestSuite(BugreportParserTest.class);
+        addTestSuite(DumpsysParserTest.class);
+        addTestSuite(DumpsysBatteryInfoParserTest.class);
         addTestSuite(JavaCrashParserTest.class);
         addTestSuite(LogcatParserTest.class);
         addTestSuite(MemInfoParserTest.class);
@@ -58,6 +63,7 @@
         addTestSuite(NativeCrashParserTest.class);
         addTestSuite(ProcrankParserTest.class);
         addTestSuite(SystemPropsParserTest.class);
+        addTestSuite(TopParserTest.class);
         addTestSuite(TracesParserTest.class);
 
         // util
diff --git a/tests/src/com/android/loganalysis/parser/BugreportParserTest.java b/tests/src/com/android/loganalysis/parser/BugreportParserTest.java
index b5f973e..5edf61a 100644
--- a/tests/src/com/android/loganalysis/parser/BugreportParserTest.java
+++ b/tests/src/com/android/loganalysis/parser/BugreportParserTest.java
@@ -101,6 +101,16 @@
                 "----- end 2887 -----",
                 "",
                 "------ SECTION ------",
+                "",
+                "------ DUMPSYS (dumpsys) ------",
+                "DUMP OF SERVICE batteryinfo:",
+                "Statistics since last unplugged:",
+                "  Kernel Wake lock \"PowerManagerService.WakeLocks\": 5m 10s 61ms (2 times) realtime",
+                "  Kernel Wake lock \"pm8921_eoc\": 9s 660ms (0 times) realtime",
+                "",
+                "  All partial wake locks:",
+                "  Wake lock #0 partialWakelock: 5m 9s 260ms (1 times) realtime",
+                "  Wake lock #1000 AlarmManager: 422ms (7 times) realtime",
                 "");
 
         BugreportItem bugreport = new BugreportParser().parse(lines);
@@ -122,6 +132,9 @@
 
         assertNotNull(bugreport.getSystemProps());
         assertEquals(4, bugreport.getSystemProps().size());
+
+        assertNotNull(bugreport.getDumpsys());
+        assertNotNull(bugreport.getDumpsys().getBatteryInfo());
     }
 
     /**
diff --git a/tests/src/com/android/loganalysis/parser/DumpsysBatteryInfoParserTest.java b/tests/src/com/android/loganalysis/parser/DumpsysBatteryInfoParserTest.java
new file mode 100644
index 0000000..dec82fa
--- /dev/null
+++ b/tests/src/com/android/loganalysis/parser/DumpsysBatteryInfoParserTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.loganalysis.parser;
+
+import com.android.loganalysis.item.DumpsysBatteryInfoItem;
+import com.android.loganalysis.item.DumpsysBatteryInfoItem.WakeLock;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link DumpsysBatteryInfoParser}
+ */
+public class DumpsysBatteryInfoParserTest extends TestCase {
+
+    /**
+     * Test that complete battery info dumpsys is parsed.
+     */
+    public void testParse() {
+        List<String> inputBlock = Arrays.asList(
+                "Battery History:",
+                "         -15m07s754ms  START",
+                "         -15m05s119ms 088 20080000 status=charging health=good plug=usb temp=269 volt=4358 +plugged +sensor",
+                "",
+                "Per-PID Stats:",
+                "  PID 4242 wake time: +5m10s24ms",
+                "  PID 543 wake time: +3s585ms",
+                "",
+                "Statistics since last charge:",
+                "  System starts: 2, currently on battery: false",
+                "  Time on battery: 8m 20s 142ms (55.1%) realtime, 5m 17s 5ms (34.9%) uptime",
+                "  Kernel Wake lock \"PowerManagerService.WakeLocks\": 5m 10s 61ms (2 times) realtime",
+                "  Kernel Wake lock \"pm8921_eoc\": 9s 660ms (0 times) realtime",
+                "  ",
+                "  All partial wake locks:",
+                "  Wake lock #0 partialWakelock: 5m 9s 260ms (1 times) realtime",
+                "  Wake lock #1000 AlarmManager: 422ms (7 times) realtime",
+                "",
+                "Statistics since last unplugged:",
+                "  Time on battery: 8m 20s 142ms (92.6%) realtime, 5m 17s 5ms (58.7%) uptime",
+                "  Total run time: 8m 59s 968ms realtime, 5m 56s 831ms uptime, ",
+                "  Screen on: 0ms (0.0%), Input events: 0, Active phone call: 0ms (0.0%)",
+                "  Screen brightnesses: No activity",
+                "  Kernel Wake lock \"PowerManagerService.WakeLocks\": 5m 10s 61ms (2 times) realtime",
+                "  Kernel Wake lock \"pm8921_eoc\": 9s 660ms (0 times) realtime",
+                "  Kernel Wake lock \"main\": 7s 323ms (0 times) realtime",
+                "  Total received: 0B, Total sent: 0B",
+                "  Total full wakelock time: 0ms , Total partial wakelock time: 5m 10s 60ms ",
+                "  Signal levels: No activity",
+                "  Signal scanning time: 0ms ",
+                "  Radio types: none 8m 20s 142ms (100.0%) 0x",
+                "  Radio data uptime when unplugged: 0 ms",
+                "  Wifi on: 0ms (0.0%), Wifi running: 0ms (0.0%), Bluetooth on: 0ms (0.0%)",
+                " ",
+                "  Device is currently plugged into power",
+                "    Last discharge cycle start level: 87",
+                "    Last discharge cycle end level: 87",
+                "    Amount discharged while screen on: 0",
+                "    Amount discharged while screen off: 0",
+                " ",
+                "  All partial wake locks:",
+                "  Wake lock #0 partialWakelock: 5m 9s 260ms (1 times) realtime",
+                "  Wake lock #1000 AlarmManager: 422ms (7 times) realtime",
+                "  Wake lock #1000 show keyguard: 277ms (1 times) realtime",
+                "  Wake lock #1000 ActivityManager-Sleep: 72ms (1 times) realtime",
+                "  Wake lock #10015 AlarmManager: 16ms (1 times) realtime",
+                "",
+                "  #0:",
+                "    Wake lock partialWakelock: 5m 9s 260ms partial (1 times) realtime",
+                "    Proc /init:",
+                "      CPU: 10ms usr + 0ms krn",
+                "    Proc flush-179:0:",
+                "      CPU: 0ms usr + 10ms krn",
+                "    Proc vold:",
+                "      CPU: 20ms usr + 10ms krn",
+                "  #1000:",
+                "    User activity: 3 other, 1 button",
+                "    Wake lock show keyguard: 277ms partial (1 times) realtime",
+                "    Wake lock AlarmManager: 422ms partial (7 times) realtime");
+
+        DumpsysBatteryInfoParser parser = new DumpsysBatteryInfoParser();
+        DumpsysBatteryInfoItem item = parser.parse(inputBlock);
+
+        assertEquals(5, item.getLastUnpluggedWakeLock().size());
+        assertEquals(3, item.getLastUnpluggedKernelWakeLock().size());
+
+        assertEquals("partialWakelock",
+                item.getLastUnpluggedWakeLock().get(0).getName());
+        assertEquals("PowerManagerService.WakeLocks",
+                item.getLastUnpluggedKernelWakeLock().get(0).getName());
+    }
+
+    /**
+     * Test that kernel wakelocks are parsed.
+     */
+    public void testParseKernelWakeLock() {
+        String inputLine = "  Kernel Wake lock \"Process\": 1d 2h 3m 4s 5ms (6 times) realtime";
+
+        DumpsysBatteryInfoParser parser = new DumpsysBatteryInfoParser();
+        parser.parseLastUnpluggedKernelWakeLock(inputLine);
+        DumpsysBatteryInfoItem item = parser.getItem();
+
+        assertEquals(1, item.getLastUnpluggedKernelWakeLock().size());
+        WakeLock wakeLock = item.getLastUnpluggedKernelWakeLock().get(0);
+        assertEquals("Process", wakeLock.getName());
+        assertNull(wakeLock.getNumber());
+        assertEquals(DumpsysBatteryInfoParser.getMs(1, 2, 3, 4, 5), wakeLock.getHeldTime());
+        assertEquals(6, wakeLock.getLockedCount());
+
+        inputLine = "  Kernel Wake lock \"Process\": 5m 7ms (2 times) realtime";
+
+        parser = new DumpsysBatteryInfoParser();
+        parser.parseLastUnpluggedKernelWakeLock(inputLine);
+        item = parser.getItem();
+
+        assertEquals(1, item.getLastUnpluggedKernelWakeLock().size());
+        wakeLock = item.getLastUnpluggedKernelWakeLock().get(0);
+        assertEquals("Process", wakeLock.getName());
+        assertNull(wakeLock.getNumber());
+        assertEquals(5 * 60 * 1000 + 7, wakeLock.getHeldTime());
+        assertEquals(2, wakeLock.getLockedCount());
+    }
+
+    /**
+     * Test that wake locks are parsed.
+     */
+    public void testParseWakeLock() {
+        String inputLine = "  Wake lock #1234 Process: 1d 2h 3m 4s 5ms (6 times) realtime";
+
+        DumpsysBatteryInfoParser parser = new DumpsysBatteryInfoParser();
+        parser.parseLastUnpluggedWakeLock(inputLine);
+        DumpsysBatteryInfoItem item = parser.getItem();
+
+        assertEquals(1, item.getLastUnpluggedWakeLock().size());
+        WakeLock wakeLock = item.getLastUnpluggedWakeLock().get(0);
+        assertEquals("Process", wakeLock.getName());
+        assertEquals((Integer) 1234, wakeLock.getNumber());
+        assertEquals(DumpsysBatteryInfoParser.getMs(1, 2, 3, 4, 5), wakeLock.getHeldTime());
+        assertEquals(6, wakeLock.getLockedCount());
+
+        inputLine = "  Wake lock #1234 Process:with:colons: 5m 7ms (2 times) realtime";
+
+        parser = new DumpsysBatteryInfoParser();
+        parser.parseLastUnpluggedWakeLock(inputLine);
+        item = parser.getItem();
+
+        assertEquals(1, item.getLastUnpluggedWakeLock().size());
+        wakeLock = item.getLastUnpluggedWakeLock().get(0);
+        assertEquals("Process:with:colons", wakeLock.getName());
+        assertEquals((Integer) 1234, wakeLock.getNumber());
+        assertEquals(5 * 60 * 1000 + 7, wakeLock.getHeldTime());
+        assertEquals(2, wakeLock.getLockedCount());
+    }
+
+    /**
+     * Test the helper function to covert time to ms.
+     */
+    public void testGetMs() {
+        assertEquals(1, DumpsysBatteryInfoParser.getMs(0, 0, 0, 0, 1));
+        assertEquals(1000, DumpsysBatteryInfoParser.getMs(0, 0, 0, 1, 0));
+        assertEquals(60 * 1000, DumpsysBatteryInfoParser.getMs(0, 0, 1, 0, 0));
+        assertEquals(60 * 60 * 1000, DumpsysBatteryInfoParser.getMs(0, 1, 0, 0, 0));
+        assertEquals(24 * 60 * 60 * 1000, DumpsysBatteryInfoParser.getMs(1, 0, 0, 0, 0));
+    }
+}
+
diff --git a/tests/src/com/android/loganalysis/parser/DumpsysParserTest.java b/tests/src/com/android/loganalysis/parser/DumpsysParserTest.java
new file mode 100644
index 0000000..9561b7b
--- /dev/null
+++ b/tests/src/com/android/loganalysis/parser/DumpsysParserTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.loganalysis.parser;
+
+import com.android.loganalysis.item.DumpsysItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link DumpsysParser}
+ */
+public class DumpsysParserTest extends TestCase {
+
+    /**
+     * Test that the dumpsys section of the bugreport is parsed.
+     */
+    public void testParse() {
+        List<String> inputBlock = Arrays.asList(
+                "-------------------------------------------------------------------------------",
+                "DUMP OF SERVICE process1:",
+                "-------------------------------------------------------------------------------",
+                "DUMP OF SERVICE batteryinfo:",
+                "Statistics since last unplugged:",
+                "  Kernel Wake lock \"PowerManagerService.WakeLocks\": 5m 10s 61ms (2 times) realtime",
+                "  Kernel Wake lock \"pm8921_eoc\": 9s 660ms (0 times) realtime",
+                "",
+                "  All partial wake locks:",
+                "  Wake lock #0 partialWakelock: 5m 9s 260ms (1 times) realtime",
+                "  Wake lock #1000 AlarmManager: 422ms (7 times) realtime",
+                "",
+                "-------------------------------------------------------------------------------",
+                "DUMP OF SERVICE process2:",
+                "-------------------------------------------------------------------------------");
+
+        DumpsysParser parser = new DumpsysParser();
+        DumpsysItem item = parser.parse(inputBlock);
+
+        assertNotNull(item.getBatteryInfo());
+        assertEquals(2, item.getBatteryInfo().getLastUnpluggedWakeLock().size());
+        assertEquals(2, item.getBatteryInfo().getLastUnpluggedKernelWakeLock().size());
+    }
+}
diff --git a/tests/src/com/android/loganalysis/parser/TopParserTest.java b/tests/src/com/android/loganalysis/parser/TopParserTest.java
new file mode 100644
index 0000000..0f67849
--- /dev/null
+++ b/tests/src/com/android/loganalysis/parser/TopParserTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.loganalysis.parser;
+
+import com.android.loganalysis.item.TopItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link ProcrankParser}
+ */
+public class TopParserTest extends TestCase {
+
+    /**
+     * Test that the output of the top command is parsed.
+     */
+    public void testTopParser() {
+        List<String> inputBlock = Arrays.asList(
+                "User 20%, System 20%, IOW 5%, IRQ 3%",
+                "User 150 + Nice 50 + Sys 200 + Idle 510 + IOW 60 + IRQ 5 + SIRQ 25 = 1000",
+                "",
+                "  PID   TID PR CPU% S     VSS     RSS PCY UID      Thread          Proc",
+                " 4474  4474  0   2% R   1420K    768K     shell    top             top");
+        TopParser parser = new TopParser();
+        TopItem item = parser.parse(inputBlock);
+
+        assertEquals(150, item.getUser());
+        assertEquals(50, item.getNice());
+        assertEquals(200, item.getSystem());
+        assertEquals(510, item.getIdle());
+        assertEquals(60, item.getIow());
+        assertEquals(5, item.getIrq());
+        assertEquals(25, item.getSirq());
+        assertEquals(1000, item.getTotal());
+    }
+
+    /**
+     * Test that the last output is stored.
+     */
+    public void testLastTop() {
+        List<String> inputBlock = Arrays.asList(
+                "User 0 + Nice 0 + Sys 0 + Idle 1000 + IOW 0 + IRQ 0 + SIRQ 0 = 1000",
+                "User 0 + Nice 0 + Sys 0 + Idle 1000 + IOW 0 + IRQ 0 + SIRQ 0 = 1000",
+                "User 150 + Nice 50 + Sys 200 + Idle 510 + IOW 60 + IRQ 5 + SIRQ 25 = 1000");
+
+        TopParser parser = new TopParser();
+        TopItem item = parser.parse(inputBlock);
+
+        assertEquals(150, item.getUser());
+        assertEquals(50, item.getNice());
+        assertEquals(200, item.getSystem());
+        assertEquals(510, item.getIdle());
+        assertEquals(60, item.getIow());
+        assertEquals(5, item.getIrq());
+        assertEquals(25, item.getSirq());
+        assertEquals(1000, item.getTotal());
+    }
+}