Home | History | Annotate | Download | only in parser
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.loganalysis.parser;
     17 
     18 import com.android.loganalysis.item.KernelLogItem;
     19 import com.android.loganalysis.item.LowMemoryKillerItem;
     20 import com.android.loganalysis.item.MiscKernelLogItem;
     21 import com.android.loganalysis.item.PageAllocationFailureItem;
     22 import com.android.loganalysis.item.SELinuxItem;
     23 import com.android.loganalysis.util.LogPatternUtil;
     24 import com.android.loganalysis.util.LogTailUtil;
     25 
     26 import java.io.BufferedReader;
     27 import java.io.IOException;
     28 import java.util.List;
     29 import java.util.regex.Matcher;
     30 import java.util.regex.Pattern;
     31 
     32 /**
     33 * A {@link IParser} to parse {@code /proc/last_kmsg}, {@code /proc/kmsg}, and the output from
     34 * {@code dmesg}.
     35 */
     36 public class KernelLogParser implements IParser {
     37     public static final String KERNEL_RESET = "KERNEL_RESET";
     38     public static final String KERNEL_ERROR = "KERNEL_ERROR";
     39     public static final String SELINUX_DENIAL = "SELINUX_DENIAL";
     40     public static final String NORMAL_REBOOT = "NORMAL_REBOOT";
     41     public static final String PAGE_ALLOC_FAILURE = "PAGE_ALLOC_FAILURE";
     42     public static final String LOW_MEMORY_KILLER = "LOW_MEMORY_KILLER";
     43 
     44     /**
     45      * Matches: [     0.000000] Message<br />
     46      * Matches: &lt;3&gt;[     0.000000] Message
     47      */
     48     private static final Pattern LOG_LINE = Pattern.compile(
     49             "^(<\\d+>)?\\[\\s*(\\d+\\.\\d{6})\\] (.*)$");
     50     private static final Pattern SELINUX_DENIAL_PATTERN = Pattern.compile(
     51             ".*avc:\\s.*scontext=\\w*:\\w*:([\\w\\s]*):\\w*\\s.*");
     52     // Matches: page allocation failure: order:3, mode:0x10c0d0
     53     private static final Pattern PAGE_ALLOC_FAILURE_PATTERN = Pattern.compile(
     54             ".*page\\s+allocation\\s+failure:\\s+order:(\\d+).*");
     55     // Matches: Killing '.qcrilmsgtunnel' (3699), adj 100,
     56     private static final Pattern LOW_MEMORY_KILLER_PATTERN = Pattern.compile(
     57             ".*Killing\\s+'(.*)'\\s+\\((\\d+)\\),.*adj\\s+(\\d+).*");
     58 
     59     /**
     60      * Regular expression representing all known bootreasons which are bad.
     61      */
     62     public static final Pattern BAD_BOOTREASONS = Pattern.compile(
     63             "(?:kernel_panic.*|rpm_err|hw_reset(?:$|\\n)|wdog_.*|tz_err|adsp_err|modem_err|mba_err|"
     64             + "watchdog.*|Watchdog|Panic|srto:.*|oemerr.*)");
     65 
     66     /**
     67      * Regular expression representing all known bootreasons which are good.
     68      */
     69     public static final Pattern GOOD_BOOTREASONS = Pattern.compile(
     70             "(?:PowerKey|normal|recovery|reboot.*)");
     71 
     72     private boolean mAddUnknownBootreason = true;
     73 
     74     private KernelLogItem mKernelLog = null;
     75     private Double mStartTime = null;
     76     private Double mStopTime = null;
     77 
     78     private LogPatternUtil mPatternUtil = new LogPatternUtil();
     79     private LogTailUtil mPreambleUtil = new LogTailUtil(500, 50, 50);
     80     private boolean mBootreasonFound = false;
     81 
     82     public KernelLogParser() {
     83         initPatterns();
     84     }
     85 
     86     public void setAddUnknownBootreason(boolean enable) {
     87         mAddUnknownBootreason = enable;
     88     }
     89 
     90     /**
     91      * Parse a kernel log from a {@link BufferedReader} into an {@link KernelLogItem} object.
     92      *
     93      * @return The {@link KernelLogItem}.
     94      * @see #parse(List)
     95      */
     96     public KernelLogItem parse(BufferedReader input) throws IOException {
     97         String line;
     98         while ((line = input.readLine()) != null) {
     99             parseLine(line);
    100         }
    101         commit();
    102 
    103         return mKernelLog;
    104     }
    105 
    106     /**
    107      * {@inheritDoc}
    108      *
    109      * @return The {@link KernelLogItem}.
    110      */
    111     @Override
    112     public KernelLogItem parse(List<String> lines) {
    113         mBootreasonFound = false;
    114         for (String line : lines) {
    115             parseLine(line);
    116         }
    117         commit();
    118 
    119         return mKernelLog;
    120     }
    121 
    122     /**
    123      * Parse a line of input.
    124      *
    125      * @param line The line to parse
    126      */
    127     private void parseLine(String line) {
    128         if ("".equals(line.trim())) {
    129             return;
    130         }
    131         if (mKernelLog == null) {
    132             mKernelLog = new KernelLogItem();
    133         }
    134         Matcher m = LOG_LINE.matcher(line);
    135         if (m.matches()) {
    136             Double time = Double.parseDouble(m.group(2));
    137             String msg = m.group(3);
    138 
    139             if (mStartTime == null) {
    140                 mStartTime = time;
    141             }
    142             mStopTime = time;
    143 
    144             checkAndAddKernelEvent(msg);
    145 
    146             mPreambleUtil.addLine(null, line);
    147         } else {
    148             checkAndAddKernelEvent(line);
    149         }
    150     }
    151 
    152     /**
    153      * Checks if a kernel log message matches a pattern and add a kernel event if it does.
    154      */
    155     private void checkAndAddKernelEvent(String message) {
    156         String category = mPatternUtil.checkMessage(message);
    157         if (category == null) {
    158             return;
    159         }
    160 
    161         if (category.equals(KERNEL_RESET) || category.equals(NORMAL_REBOOT)) {
    162             mBootreasonFound = true;
    163         }
    164 
    165         if (category.equals(NORMAL_REBOOT)) {
    166             return;
    167         }
    168 
    169         MiscKernelLogItem kernelLogItem;
    170         if (category.equals(SELINUX_DENIAL)) {
    171             SELinuxItem selinuxItem = new SELinuxItem();
    172             Matcher m = SELINUX_DENIAL_PATTERN.matcher(message);
    173             if (m.matches()) {
    174                 selinuxItem.setSContext(m.group(1));
    175             }
    176             kernelLogItem = selinuxItem;
    177         } else if (category.equals(PAGE_ALLOC_FAILURE)) {
    178             PageAllocationFailureItem allocItem = new PageAllocationFailureItem();
    179             Matcher m = PAGE_ALLOC_FAILURE_PATTERN.matcher(message);
    180             if (m.matches()) {
    181                 allocItem.setOrder(Integer.parseInt(m.group(1)));
    182             }
    183             kernelLogItem = allocItem;
    184         } else if (category.equals(LOW_MEMORY_KILLER)) {
    185             LowMemoryKillerItem lmkItem = new LowMemoryKillerItem();
    186             Matcher m = LOW_MEMORY_KILLER_PATTERN.matcher(message);
    187             if (m.matches()) {
    188                 lmkItem.setProcessName(m.group(1));
    189                 lmkItem.setPid(Integer.parseInt(m.group(2)));
    190                 lmkItem.setAdjustment(Integer.parseInt(m.group(3)));
    191             }
    192             kernelLogItem = lmkItem;
    193         } else {
    194             kernelLogItem = new MiscKernelLogItem();
    195         }
    196         kernelLogItem.setEventTime(mStopTime);
    197         kernelLogItem.setPreamble(mPreambleUtil.getLastTail());
    198         kernelLogItem.setStack(message);
    199         kernelLogItem.setCategory(category);
    200         mKernelLog.addEvent(kernelLogItem);
    201     }
    202 
    203     /**
    204      * Signal that the input has finished.
    205      */
    206     private void commit() {
    207         if (mKernelLog == null) {
    208             return;
    209         }
    210         mKernelLog.setStartTime(mStartTime);
    211         mKernelLog.setStopTime(mStopTime);
    212 
    213         if (mAddUnknownBootreason && !mBootreasonFound) {
    214             MiscKernelLogItem unknownReset = new MiscKernelLogItem();
    215             unknownReset.setEventTime(mStopTime);
    216             unknownReset.setPreamble(mPreambleUtil.getLastTail());
    217             unknownReset.setCategory(KERNEL_RESET);
    218             unknownReset.setStack("Unknown reason");
    219             mKernelLog.addEvent(unknownReset);
    220         }
    221     }
    222 
    223     private void initPatterns() {
    224         // Kernel resets
    225         // TODO: Separate out device specific patterns
    226         final String[] kernelResets = {
    227             "smem: DIAG.*",
    228             "smsm: AMSS FATAL ERROR.*",
    229             "kernel BUG at .*",
    230             "BUG: failure at .*",
    231             "PVR_K:\\(Fatal\\): Debug assertion failed! \\[.*\\]",
    232             "Kernel panic.*",
    233             "Unable to handle kernel paging request.*",
    234             "BP panicked",
    235             "WROTE DSP RAMDUMP",
    236             "tegra_wdt: last reset due to watchdog timeout.*",
    237             "tegra_wdt tegra_wdt.0: last reset is due to watchdog timeout.*",
    238             "Last reset was MPU Watchdog Timer reset.*",
    239             "\\[MODEM_IF\\].*CRASH.*",
    240             "Last boot reason: " + BAD_BOOTREASONS,
    241             "Last reset was system watchdog timer reset.*",
    242         };
    243         final String[] goodSignatures = {
    244                 "Restarting system.*",
    245                 "Power down.*",
    246                 "Last boot reason: " + GOOD_BOOTREASONS,
    247         };
    248         for (String pattern : kernelResets) {
    249             mPatternUtil.addPattern(Pattern.compile(pattern), KERNEL_RESET);
    250         }
    251         for (String pattern : goodSignatures) {
    252             mPatternUtil.addPattern(Pattern.compile(pattern), NORMAL_REBOOT);
    253         }
    254 
    255         mPatternUtil.addPattern(Pattern.compile("Internal error:.*"), KERNEL_ERROR);
    256 
    257         // SELINUX denials
    258         mPatternUtil.addPattern(SELINUX_DENIAL_PATTERN, SELINUX_DENIAL);
    259         // Page allocation failures
    260         mPatternUtil.addPattern(PAGE_ALLOC_FAILURE_PATTERN, PAGE_ALLOC_FAILURE);
    261         // Lowmemorykiller kills
    262         mPatternUtil.addPattern(LOW_MEMORY_KILLER_PATTERN, LOW_MEMORY_KILLER);
    263     }
    264 
    265     /**
    266      * Get the internal {@link LogPatternUtil}. Exposed for unit testing.
    267      */
    268     LogPatternUtil getLogPatternUtil() {
    269         return mPatternUtil;
    270     }
    271 }
    272