Update boot reason parsing for with 3.10 kernel

Also, bring kernel log parser in line with APR and only record first
kernel reset.

Bug: 14829888
Change-Id: Ice5db062b3fc8ece774afc8e284f1a276aa52d43
diff --git a/src/com/android/loganalysis/item/BugreportItem.java b/src/com/android/loganalysis/item/BugreportItem.java
index 1df7d30..4b35816 100644
--- a/src/com/android/loganalysis/item/BugreportItem.java
+++ b/src/com/android/loganalysis/item/BugreportItem.java
@@ -28,6 +28,8 @@
     /** Constant for JSON output */
     public static final String TIME = "TIME";
     /** Constant for JSON output */
+    public static final String COMMAND_LINE = "COMMAND_LINE";
+    /** Constant for JSON output */
     public static final String MEM_INFO = "MEM_INFO";
     /** Constant for JSON output */
     public static final String PROCRANK = "PROCRANK";
@@ -45,8 +47,10 @@
     public static final String DUMPSYS = "DUMPSYS";
 
     private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList(
-            TIME, MEM_INFO, PROCRANK, TOP, KERNEL_LOG, LAST_KMSG, SYSTEM_LOG, SYSTEM_PROPS,
-            DUMPSYS));
+            TIME, COMMAND_LINE, MEM_INFO, PROCRANK, TOP, KERNEL_LOG, LAST_KMSG, SYSTEM_LOG,
+            SYSTEM_PROPS, DUMPSYS));
+
+    public static class CommandLineItem extends GenericMapItem<String> {}
 
     /**
      * The constructor for {@link BugreportItem}.
@@ -70,6 +74,20 @@
     }
 
     /**
+     * Get the {@link CommandLineItem} of the bugreport.
+     */
+    public CommandLineItem getCommandLine() {
+        return (CommandLineItem) getAttribute(COMMAND_LINE);
+    }
+
+    /**
+     * Set the {@link CommandLineItem} of the bugreport.
+     */
+    public void setCommandLine(CommandLineItem commandLine) {
+        setAttribute(COMMAND_LINE, commandLine);
+    }
+
+    /**
      * Get the {@link MemInfoItem} of the bugreport.
      */
     public MemInfoItem getMemInfo() {
diff --git a/src/com/android/loganalysis/item/KernelLogItem.java b/src/com/android/loganalysis/item/KernelLogItem.java
index c9ebf16..60ed4de 100644
--- a/src/com/android/loganalysis/item/KernelLogItem.java
+++ b/src/com/android/loganalysis/item/KernelLogItem.java
@@ -15,6 +15,8 @@
  */
 package com.android.loganalysis.item;
 
+import com.android.loganalysis.parser.KernelLogParser;
+
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -91,6 +93,11 @@
      * Add an {@link MiscKernelLogItem} event to the end of the list of events.
      */
     public void addEvent(MiscKernelLogItem event) {
+        // Only take the first kernel reset
+        if (KernelLogParser.KERNEL_RESET.equals(event.getCategory()) &&
+                !getMiscEvents(KernelLogParser.KERNEL_RESET).isEmpty()) {
+            return;
+        }
         ((ItemList) getAttribute(EVENTS)).add(event);
     }
 
diff --git a/src/com/android/loganalysis/parser/BugreportParser.java b/src/com/android/loganalysis/parser/BugreportParser.java
index 3947a98..604ec1a 100644
--- a/src/com/android/loganalysis/parser/BugreportParser.java
+++ b/src/com/android/loganalysis/parser/BugreportParser.java
@@ -17,11 +17,13 @@
 
 import com.android.loganalysis.item.AnrItem;
 import com.android.loganalysis.item.BugreportItem;
+import com.android.loganalysis.item.BugreportItem.CommandLineItem;
 import com.android.loganalysis.item.DumpsysItem;
 import com.android.loganalysis.item.IItem;
 import com.android.loganalysis.item.KernelLogItem;
 import com.android.loganalysis.item.LogcatItem;
 import com.android.loganalysis.item.MemInfoItem;
+import com.android.loganalysis.item.MiscKernelLogItem;
 import com.android.loganalysis.item.MiscLogcatItem;
 import com.android.loganalysis.item.ProcrankItem;
 import com.android.loganalysis.item.SystemPropsItem;
@@ -55,12 +57,20 @@
     private static final String DUMPSYS_SECTION_REGEX = "------ DUMPSYS .*";
     private static final String NOOP_SECTION_REGEX = "------ .*";
 
+    private static final String BOOTREASON = "androidboot.bootreason";
+
     /**
      * Matches: == dumpstate: 2012-04-26 12:13:14
      */
     private static final Pattern DATE = Pattern.compile(
             "^== dumpstate: (\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})$");
 
+    /**
+     * Matches: Command line: key=value key=value
+     */
+    private static final Pattern COMMAND_LINE = Pattern.compile(
+            "Command line:((\\s+[^\\s=]+=[^\\s]*)*)\\s*");
+
     private IParser mBugreportParser = new IParser() {
         @Override
         public BugreportItem parse(List<String> lines) {
@@ -73,6 +83,18 @@
                 if (m.matches()) {
                     bugreport.setTime(parseTime(m.group(1)));
                 }
+                m = COMMAND_LINE.matcher(line);
+                if (m.matches()) {
+                    String argString = m.group(1).trim();
+                    if (!argString.isEmpty()) {
+                        String[] pairs = argString.split("\\s+");
+                        for (String pair : pairs) {
+                            System.out.println(pair);
+                            String[] keyValue = pair.split("=", 2);
+                            mCommandLine.put(keyValue[0], keyValue[1]);
+                        }
+                    }
+                }
             }
             return bugreport;
         }
@@ -88,6 +110,7 @@
     private DumpsysParser mDumpsysParser = new DumpsysParser();
 
     private BugreportItem mBugreport = null;
+    private CommandLineItem mCommandLine = new CommandLineItem();
 
     private boolean mParsedInput = false;
 
@@ -164,6 +187,7 @@
         }
 
         if (mBugreport != null) {
+            mBugreport.setCommandLine(mCommandLine);
             mBugreport.setMemInfo((MemInfoItem) getSection(mMemInfoParser));
             mBugreport.setProcrank((ProcrankItem) getSection(mProcrankParser));
             mBugreport.setTop((TopItem) getSection(mTopParser));
@@ -189,7 +213,20 @@
                     mBugreport.getSystemLog() != null) {
                 addAnrTrace(mBugreport.getSystemLog().getAnrs(), traces.getApp(),
                         traces.getStack());
+            }
 
+            if (mCommandLine.containsKey(BOOTREASON)) {
+                String bootreason = mCommandLine.get(BOOTREASON);
+                Matcher m = KernelLogParser.BAD_BOOTREASONS.matcher(bootreason);
+                if (m.matches()) {
+                    if (mBugreport.getLastKmsg() == null) {
+                        mBugreport.setLastKmsg(new KernelLogItem());
+                    }
+                    MiscKernelLogItem item = new MiscKernelLogItem();
+                    item.setStack("Last boot reason: " + bootreason.trim());
+                    item.setCategory(KernelLogParser.KERNEL_RESET);
+                    mBugreport.getLastKmsg().addEvent(item);
+                }
             }
         }
     }
diff --git a/src/com/android/loganalysis/parser/KernelLogParser.java b/src/com/android/loganalysis/parser/KernelLogParser.java
index e240432..741c261 100644
--- a/src/com/android/loganalysis/parser/KernelLogParser.java
+++ b/src/com/android/loganalysis/parser/KernelLogParser.java
@@ -44,6 +44,13 @@
     private static final Pattern SELINUX_DENIAL_PATTERN = Pattern.compile(
             ".*avc:\\s.*scontext=\\w*:\\w*:([\\w\\s]*):\\w*\\s.*");
 
+    /**
+     * Regular expression representing all known bootreasons which are bad.
+     */
+    public static final Pattern BAD_BOOTREASONS = Pattern.compile(
+            "(?:kernel_panic|rpm_err|hw_reset(?:$|\\n)|wdog_.*|tz_err|adsp_err|modem_err|mba_err|"
+            + "watchdogr?|Watchdog|Panic|srto:.*)");
+
     private KernelLogItem mKernelLog = null;
     private Double mStartTime = null;
     private Double mStopTime = null;
@@ -161,16 +168,17 @@
             "smem: DIAG.*",
             "smsm: AMSS FATAL ERROR.*",
             "kernel BUG at .*",
+            "BUG: failure at .*",
             "PVR_K:\\(Fatal\\): Debug assertion failed! \\[.*\\]",
             "Kernel panic.*",
+            "Unable to handle kernel paging request.*",
             "BP panicked",
             "WROTE DSP RAMDUMP",
             "tegra_wdt: last reset due to watchdog timeout.*",
             "tegra_wdt tegra_wdt.0: last reset is due to watchdog timeout.*",
             "Last reset was MPU Watchdog Timer reset.*",
             "\\[MODEM_IF\\].*CRASH.*",
-            "Last boot reason: (?:kernel_panic|rpm_err|hw_reset(?:$|\n)|wdog_.*|" +
-            "tz_err|adsp_err|modem_err|mba_err|watchdogr?|Watchdog|Panic)",
+            "Last boot reason: " + BAD_BOOTREASONS,
             "Last reset was system watchdog timer reset.*",
         };
         for (String pattern : kernelResets) {
diff --git a/tests/src/com/android/loganalysis/parser/BugreportParserTest.java b/tests/src/com/android/loganalysis/parser/BugreportParserTest.java
index b080e0d..1eedf46 100644
--- a/tests/src/com/android/loganalysis/parser/BugreportParserTest.java
+++ b/tests/src/com/android/loganalysis/parser/BugreportParserTest.java
@@ -92,7 +92,7 @@
                 "",
                 "------ LAST KMSG (/proc/last_kmsg) ------",
                 "[    0.000000] Initializing cgroup subsys cpu",
-                "[   16.203491] benight message",
+                "[   16.203491] benign message",
                 "",
                 "------ SECTION ------",
                 "",
@@ -183,6 +183,83 @@
     }
 
     /**
+     * Test that the command line is parsed
+     */
+    public void testParse_command_line() {
+        List<String> lines = Arrays.asList("Command line:");
+        BugreportItem bugreport = new BugreportParser().parse(lines);
+        assertTrue(bugreport.getCommandLine().isEmpty());
+
+        lines = Arrays.asList("Command line: key=value");
+        bugreport = new BugreportParser().parse(lines);
+        assertEquals(1, bugreport.getCommandLine().size());
+        assertEquals("value", bugreport.getCommandLine().get("key"));
+
+        lines = Arrays.asList("Command line: key1=value1 key2=value2");
+        bugreport = new BugreportParser().parse(lines);
+        assertEquals(2, bugreport.getCommandLine().size());
+        assertEquals("value1", bugreport.getCommandLine().get("key1"));
+        assertEquals("value2", bugreport.getCommandLine().get("key2"));
+
+        // Test a bad strings
+        lines = Arrays.asList("Command line:   key1=value=withequals  key2=  ");
+        bugreport = new BugreportParser().parse(lines);
+        assertEquals(2, bugreport.getCommandLine().size());
+        assertEquals("value=withequals", bugreport.getCommandLine().get("key1"));
+        assertEquals("", bugreport.getCommandLine().get("key2"));
+
+        lines = Arrays.asList("Command line:   key1=value=withequals  key2=  key3");
+        bugreport = new BugreportParser().parse(lines);
+        assertTrue(bugreport.getCommandLine().isEmpty());
+    }
+
+    /**
+     * Test
+     */
+    public void testParse_bootreason_good() {
+        List<String> lines = Arrays.asList(
+                "========================================================",
+                "== dumpstate: 1999-01-01 02:03:04",
+                "========================================================",
+                "Command line: androidboot.bootreason=reboot",
+                "");
+        BugreportItem bugreport = new BugreportParser().parse(lines);
+        assertNull(bugreport.getLastKmsg());
+    }
+
+    public void testParse_bootreason_bad() {
+        List<String> lines = Arrays.asList(
+                "========================================================",
+                "== dumpstate: 1999-01-01 02:03:04",
+                "========================================================",
+                "Command line: androidboot.bootreason=hw_reset",
+                "");
+        BugreportItem bugreport = new BugreportParser().parse(lines);
+        assertNotNull(bugreport.getLastKmsg());
+        assertEquals(1, bugreport.getLastKmsg().getEvents().size());
+        assertEquals("Last boot reason: hw_reset",
+                bugreport.getLastKmsg().getEvents().get(0).getStack());
+        assertEquals("KERNEL_RESET", bugreport.getLastKmsg().getEvents().get(0).getCategory());
+    }
+
+    public void testParse_bootreason_duplicate() {
+        List<String> lines = Arrays.asList(
+                "========================================================",
+                "== dumpstate: 1999-01-01 02:03:04",
+                "========================================================",
+                "Command line: androidboot.bootreason=hw_reset",
+                "------ LAST KMSG (/proc/last_kmsg) ------",
+                "[    0.000000] Initializing cgroup subsys cpu",
+                "[   16.203491] Kernel panic",
+                "");
+        BugreportItem bugreport = new BugreportParser().parse(lines);
+        assertNotNull(bugreport.getLastKmsg());
+        assertEquals(1, bugreport.getLastKmsg().getEvents().size());
+        assertEquals("Kernel panic", bugreport.getLastKmsg().getEvents().get(0).getStack());
+        assertEquals("KERNEL_RESET", bugreport.getLastKmsg().getEvents().get(0).getCategory());
+    }
+
+    /**
      * Test that the trace is set correctly if there is only one ANR.
      */
     public void testSetAnrTrace_single() {
diff --git a/tests/src/com/android/loganalysis/parser/KernelLogParserTest.java b/tests/src/com/android/loganalysis/parser/KernelLogParserTest.java
index 118f3ff..890a196 100644
--- a/tests/src/com/android/loganalysis/parser/KernelLogParserTest.java
+++ b/tests/src/com/android/loganalysis/parser/KernelLogParserTest.java
@@ -212,4 +212,30 @@
         assertEquals(1, kernelLog.getEvents().size());
         assertEquals(1, kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).size());
     }
+
+    /**
+     * Test that only the first kernel reset is taken but other signatures can have multiple
+     */
+    public void testMultipleKernelResets() {
+        final String SELINUX_DENIAL_STACK = "type=1400 audit(1384544483.730:10): avc:  denied  " +
+                "{ getattr } for  pid=797 comm=\"Binder_5\" path=\"/dev/pts/1\" + " +
+                "dev=devpts ino=4 scontext=u:r:system_server:s0 " +
+                "tcontext=u:object_r:devpts:s0 tclass=chr_file";
+        final List<String> lines = Arrays.asList(
+                "[ 0.000000] Kernel panic",
+                "[ 0.000000] Internal error:",
+                "[ 0.000000] " + SELINUX_DENIAL_STACK,
+                "[ 1.000000] Kernel panic",
+                "[ 1.000000] Internal error:",
+                "[ 1.000000] " + SELINUX_DENIAL_STACK,
+                "[ 2.000000] Kernel panic",
+                "[ 2.000000] Internal error:",
+                "[ 2.000000] " + SELINUX_DENIAL_STACK);
+
+        KernelLogItem kernelLog = new KernelLogParser().parse(lines);
+        assertEquals(7, kernelLog.getEvents().size());
+        assertEquals(1, kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).size());
+        assertEquals(0.0,
+                kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).get(0).getEventTime());
+    }
 }