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 
KernelLogParser()82     public KernelLogParser() {
83         initPatterns();
84     }
85 
setAddUnknownBootreason(boolean enable)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      */
parse(BufferedReader input)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
parse(List<String> lines)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      */
parseLine(String line)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      */
checkAndAddKernelEvent(String message)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      */
commit()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 
initPatterns()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      */
getLogPatternUtil()268     LogPatternUtil getLogPatternUtil() {
269         return mPatternUtil;
270     }
271 }
272