/* * 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.KernelLogItem; import com.android.loganalysis.item.LowMemoryKillerItem; import com.android.loganalysis.item.MiscKernelLogItem; import com.android.loganalysis.item.PageAllocationFailureItem; import com.android.loganalysis.item.SELinuxItem; import com.android.loganalysis.util.LogPatternUtil; import com.android.loganalysis.util.LogTailUtil; import java.io.BufferedReader; import java.io.IOException; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A {@link IParser} to parse {@code /proc/last_kmsg}, {@code /proc/kmsg}, and the output from * {@code dmesg}. */ public class KernelLogParser implements IParser { public static final String KERNEL_RESET = "KERNEL_RESET"; public static final String KERNEL_ERROR = "KERNEL_ERROR"; public static final String SELINUX_DENIAL = "SELINUX_DENIAL"; public static final String NORMAL_REBOOT = "NORMAL_REBOOT"; public static final String PAGE_ALLOC_FAILURE = "PAGE_ALLOC_FAILURE"; public static final String LOW_MEMORY_KILLER = "LOW_MEMORY_KILLER"; /** * Matches: [ 0.000000] Message
* Matches: <3>[ 0.000000] Message */ private static final Pattern LOG_LINE = Pattern.compile( "^(<\\d+>)?\\[\\s*(\\d+\\.\\d{6})\\] (.*)$"); private static final Pattern SELINUX_DENIAL_PATTERN = Pattern.compile( ".*avc:\\s.*scontext=\\w*:\\w*:([\\w\\s]*):\\w*\\s.*"); // Matches: page allocation failure: order:3, mode:0x10c0d0 private static final Pattern PAGE_ALLOC_FAILURE_PATTERN = Pattern.compile( ".*page\\s+allocation\\s+failure:\\s+order:(\\d+).*"); // Matches: Killing '.qcrilmsgtunnel' (3699), adj 100, private static final Pattern LOW_MEMORY_KILLER_PATTERN = Pattern.compile( ".*Killing\\s+'(.*)'\\s+\\((\\d+)\\),.*adj\\s+(\\d+).*"); /** * 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|" + "watchdog.*|Watchdog|Panic|srto:.*|oemerr.*)"); /** * Regular expression representing all known bootreasons which are good. */ public static final Pattern GOOD_BOOTREASONS = Pattern.compile( "(?:PowerKey|normal|recovery|reboot.*)"); private boolean mAddUnknownBootreason = true; private KernelLogItem mKernelLog = null; private Double mStartTime = null; private Double mStopTime = null; private LogPatternUtil mPatternUtil = new LogPatternUtil(); private LogTailUtil mPreambleUtil = new LogTailUtil(500, 50, 50); private boolean mBootreasonFound = false; public KernelLogParser() { initPatterns(); } public void setAddUnknownBootreason(boolean enable) { mAddUnknownBootreason = enable; } /** * Parse a kernel log from a {@link BufferedReader} into an {@link KernelLogItem} object. * * @return The {@link KernelLogItem}. * @see #parse(List) */ public KernelLogItem parse(BufferedReader input) throws IOException { String line; while ((line = input.readLine()) != null) { parseLine(line); } commit(); return mKernelLog; } /** * {@inheritDoc} * * @return The {@link KernelLogItem}. */ @Override public KernelLogItem parse(List lines) { mBootreasonFound = false; for (String line : lines) { parseLine(line); } commit(); return mKernelLog; } /** * Parse a line of input. * * @param line The line to parse */ private void parseLine(String line) { if ("".equals(line.trim())) { return; } if (mKernelLog == null) { mKernelLog = new KernelLogItem(); } Matcher m = LOG_LINE.matcher(line); if (m.matches()) { Double time = Double.parseDouble(m.group(2)); String msg = m.group(3); if (mStartTime == null) { mStartTime = time; } mStopTime = time; checkAndAddKernelEvent(msg); mPreambleUtil.addLine(null, line); } else { checkAndAddKernelEvent(line); } } /** * Checks if a kernel log message matches a pattern and add a kernel event if it does. */ private void checkAndAddKernelEvent(String message) { String category = mPatternUtil.checkMessage(message); if (category == null) { return; } if (category.equals(KERNEL_RESET) || category.equals(NORMAL_REBOOT)) { mBootreasonFound = true; } if (category.equals(NORMAL_REBOOT)) { return; } MiscKernelLogItem kernelLogItem; if (category.equals(SELINUX_DENIAL)) { SELinuxItem selinuxItem = new SELinuxItem(); Matcher m = SELINUX_DENIAL_PATTERN.matcher(message); if (m.matches()) { selinuxItem.setSContext(m.group(1)); } kernelLogItem = selinuxItem; } else if (category.equals(PAGE_ALLOC_FAILURE)) { PageAllocationFailureItem allocItem = new PageAllocationFailureItem(); Matcher m = PAGE_ALLOC_FAILURE_PATTERN.matcher(message); if (m.matches()) { allocItem.setOrder(Integer.parseInt(m.group(1))); } kernelLogItem = allocItem; } else if (category.equals(LOW_MEMORY_KILLER)) { LowMemoryKillerItem lmkItem = new LowMemoryKillerItem(); Matcher m = LOW_MEMORY_KILLER_PATTERN.matcher(message); if (m.matches()) { lmkItem.setProcessName(m.group(1)); lmkItem.setPid(Integer.parseInt(m.group(2))); lmkItem.setAdjustment(Integer.parseInt(m.group(3))); } kernelLogItem = lmkItem; } else { kernelLogItem = new MiscKernelLogItem(); } kernelLogItem.setEventTime(mStopTime); kernelLogItem.setPreamble(mPreambleUtil.getLastTail()); kernelLogItem.setStack(message); kernelLogItem.setCategory(category); mKernelLog.addEvent(kernelLogItem); } /** * Signal that the input has finished. */ private void commit() { if (mKernelLog == null) { return; } mKernelLog.setStartTime(mStartTime); mKernelLog.setStopTime(mStopTime); if (mAddUnknownBootreason && !mBootreasonFound) { MiscKernelLogItem unknownReset = new MiscKernelLogItem(); unknownReset.setEventTime(mStopTime); unknownReset.setPreamble(mPreambleUtil.getLastTail()); unknownReset.setCategory(KERNEL_RESET); unknownReset.setStack("Unknown reason"); mKernelLog.addEvent(unknownReset); } } private void initPatterns() { // Kernel resets // TODO: Separate out device specific patterns final String[] kernelResets = { "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: " + BAD_BOOTREASONS, "Last reset was system watchdog timer reset.*", }; final String[] goodSignatures = { "Restarting system.*", "Power down.*", "Last boot reason: " + GOOD_BOOTREASONS, }; for (String pattern : kernelResets) { mPatternUtil.addPattern(Pattern.compile(pattern), KERNEL_RESET); } for (String pattern : goodSignatures) { mPatternUtil.addPattern(Pattern.compile(pattern), NORMAL_REBOOT); } mPatternUtil.addPattern(Pattern.compile("Internal error:.*"), KERNEL_ERROR); // SELINUX denials mPatternUtil.addPattern(SELINUX_DENIAL_PATTERN, SELINUX_DENIAL); // Page allocation failures mPatternUtil.addPattern(PAGE_ALLOC_FAILURE_PATTERN, PAGE_ALLOC_FAILURE); // Lowmemorykiller kills mPatternUtil.addPattern(LOW_MEMORY_KILLER_PATTERN, LOW_MEMORY_KILLER); } /** * Get the internal {@link LogPatternUtil}. Exposed for unit testing. */ LogPatternUtil getLogPatternUtil() { return mPatternUtil; } }