/* * Copyright (C) 2012 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.AnrItem; import com.android.loganalysis.item.MiscLogcatItem; import com.android.loganalysis.item.MonkeyLogItem; import com.android.loganalysis.item.MonkeyLogItem.DroppedCategory; import com.android.loganalysis.item.NativeCrashItem; import com.android.loganalysis.item.TracesItem; import java.io.BufferedReader; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A {@link IParser} to parse monkey logs. */ public class MonkeyLogParser implements IParser { private static final Pattern THROTTLE = Pattern.compile( "adb shell monkey.* --throttle (\\d+).*"); private static final Pattern SEED_AND_TARGET_COUNT = Pattern.compile( ":Monkey: seed=(\\d+) count=(\\d+)"); private static final Pattern SECURITY_EXCEPTIONS = Pattern.compile( "adb shell monkey.* --ignore-security-exceptions.*"); private static final Pattern PACKAGES = Pattern.compile(":AllowPackage: (\\S+)"); private static final Pattern CATEGORIES = Pattern.compile(":IncludeCategory: (\\S+)"); private static final Pattern START_UPTIME = Pattern.compile( "# (.*) - device uptime = (\\d+\\.\\d+): Monkey command used for this test:"); private static final Pattern STOP_UPTIME = Pattern.compile( "# (.*) - device uptime = (\\d+\\.\\d+): Monkey command ran for: " + "(\\d+):(\\d+) \\(mm:ss\\)"); private static final Pattern INTERMEDIATE_COUNT = Pattern.compile( "\\s+// Sending event #(\\d+)"); private static final Pattern FINISHED = Pattern.compile("// Monkey finished"); private static final Pattern FINAL_COUNT = Pattern.compile("Events injected: (\\d+)"); private static final Pattern NO_ACTIVITIES = Pattern.compile( "\\*\\* No activities found to run, monkey aborted."); private static final Pattern DROPPED_KEYS = Pattern.compile(":Dropped: .*keys=(\\d+).*"); private static final Pattern DROPPED_POINTERS = Pattern.compile( ":Dropped: .*pointers=(\\d+).*"); private static final Pattern DROPPED_TRACKBALLS = Pattern.compile( ":Dropped: .*trackballs=(\\d+).*"); private static final Pattern DROPPED_FLIPS = Pattern.compile(":Dropped: .*flips=(\\d+).*"); private static final Pattern DROPPED_ROTATIONS = Pattern.compile( ":Dropped: .*rotations=(\\d+).*"); // Log messages can get intermixed in crash message, ignore those in the crash context. private static final Pattern MONKEY_LOG_MESSAGE = Pattern.compile("$(:|Sleeping| //)"); private static final Pattern ANR = Pattern.compile( "// NOT RESPONDING: (\\S+) \\(pid (\\d+)\\)"); private static final Pattern CRASH = Pattern.compile( "// CRASH: (\\S+) \\(pid (\\d+)\\)"); private static final Pattern EMPTY_NATIVE_CRASH = Pattern.compile("" + "\\*\\* New native crash detected."); private static final Pattern ABORTED = Pattern.compile("\\*\\* Monkey aborted due to error."); private static final Pattern TRACES_START = Pattern.compile("anr traces:"); private static final Pattern TRACES_STOP = Pattern.compile("// anr traces status was \\d+"); private boolean mMatchingAnr = false; private boolean mMatchingCrash = false; private boolean mMatchingJavaCrash = false; private boolean mMatchingNativeCrash = false; private boolean mMatchingTraces = false; private boolean mMatchedTrace = false; private List mBlock = null; private String mApp = null; private Integer mPid = null; private MonkeyLogItem mMonkeyLog = new MonkeyLogItem(); /** * Parse a monkey log from a {@link BufferedReader} into an {@link MonkeyLogItem} object. * * @param input a {@link BufferedReader}. * @return The {@link MonkeyLogItem}. * @see #parse(List) */ public MonkeyLogItem parse(BufferedReader input) throws IOException { String line; while ((line = input.readLine()) != null) { parseLine(line); } return mMonkeyLog; } /** * {@inheritDoc} * * @return The {@link MonkeyLogItem}. */ @Override public MonkeyLogItem parse(List lines) { for (String line : lines) { parseLine(line); } return mMonkeyLog; } /** * Parse a line of input. */ private void parseLine(String line) { Matcher m; if (mMatchingAnr) { if ("".equals(line)) { AnrItem crash = new AnrParser().parse(mBlock); addCrashAndReset(crash); } else { m = MONKEY_LOG_MESSAGE.matcher(line); if (!m.matches()) { mBlock.add(line); } return; } } if (mMatchingCrash) { if (!mMatchingJavaCrash && !mMatchingNativeCrash && line.startsWith("// Short Msg: ")) { if (line.contains("Native crash")) { mMatchingNativeCrash = true; } else { mMatchingJavaCrash = true; } } m = ABORTED.matcher(line); if (m.matches()) { MiscLogcatItem crash = null; if (mMatchingJavaCrash) { crash = new JavaCrashParser().parse(mBlock); } else if (mMatchingNativeCrash) { crash = new NativeCrashParser().parse(mBlock); } addCrashAndReset(crash); } else { m = MONKEY_LOG_MESSAGE.matcher(line); if (!m.matches() && line.startsWith("// ") && !line.startsWith("// ** ")) { line = line.replace("// ", ""); mBlock.add(line); } return; } } if (mMatchingTraces) { m = TRACES_STOP.matcher(line); if (m.matches()) { TracesItem traces = new TracesParser().parse(mBlock); // Set the trace if the crash is an ANR and if the app for the crash and trace match if (traces != null && traces.getApp() != null && traces.getStack() != null && mMonkeyLog.getCrash() instanceof AnrItem && traces.getApp().equals(mMonkeyLog.getCrash().getApp())) { ((AnrItem) mMonkeyLog.getCrash()).setTrace(traces.getStack()); } reset(); mMatchedTrace = true; } else { m = MONKEY_LOG_MESSAGE.matcher(line); if (!m.matches()) { mBlock.add(line); } return; } } m = THROTTLE.matcher(line); if (m.matches()) { mMonkeyLog.setThrottle(Integer.parseInt(m.group(1))); } m = SEED_AND_TARGET_COUNT.matcher(line); if (m.matches()) { mMonkeyLog.setSeed(Long.parseLong(m.group(1))); mMonkeyLog.setTargetCount(Integer.parseInt(m.group(2))); } m = SECURITY_EXCEPTIONS.matcher(line); if (m.matches()) { mMonkeyLog.setIgnoreSecurityExceptions(true); } m = PACKAGES.matcher(line); if (m.matches()) { mMonkeyLog.addPackage(m.group(1)); } m = CATEGORIES.matcher(line); if (m.matches()) { mMonkeyLog.addCategory(m.group(1)); } m = START_UPTIME.matcher(line); if (m.matches()) { mMonkeyLog.setStartTime(parseTime(m.group(1))); mMonkeyLog.setStartUptimeDuration((long) (Double.parseDouble(m.group(2)) * 1000)); } m = STOP_UPTIME.matcher(line); if (m.matches()) { mMonkeyLog.setStopTime(parseTime(m.group(1))); mMonkeyLog.setStopUptimeDuration((long) (Double.parseDouble(m.group(2)) * 1000)); mMonkeyLog.setTotalDuration(60 * 1000 * Integer.parseInt(m.group(3)) + 1000 *Integer.parseInt(m.group(4))); } m = INTERMEDIATE_COUNT.matcher(line); if (m.matches()) { mMonkeyLog.setIntermediateCount(Integer.parseInt(m.group(1))); } m = FINAL_COUNT.matcher(line); if (m.matches()) { mMonkeyLog.setFinalCount(Integer.parseInt(m.group(1))); } m = FINISHED.matcher(line); if (m.matches()) { mMonkeyLog.setIsFinished(true); } m = NO_ACTIVITIES.matcher(line); if (m.matches()) { mMonkeyLog.setNoActivities(true); } m = DROPPED_KEYS.matcher(line); if (m.matches()) { mMonkeyLog.setDroppedCount(DroppedCategory.KEYS, Integer.parseInt(m.group(1))); } m = DROPPED_POINTERS.matcher(line); if (m.matches()) { mMonkeyLog.setDroppedCount(DroppedCategory.POINTERS, Integer.parseInt(m.group(1))); } m = DROPPED_TRACKBALLS.matcher(line); if (m.matches()) { mMonkeyLog.setDroppedCount(DroppedCategory.TRACKBALLS, Integer.parseInt(m.group(1))); } m = DROPPED_FLIPS.matcher(line); if (m.matches()) { mMonkeyLog.setDroppedCount(DroppedCategory.FLIPS, Integer.parseInt(m.group(1))); } m = DROPPED_ROTATIONS.matcher(line); if (m.matches()) { mMonkeyLog.setDroppedCount(DroppedCategory.ROTATIONS, Integer.parseInt(m.group(1))); } m = ANR.matcher(line); if (mMonkeyLog.getCrash() == null && m.matches()) { mApp = m.group(1); mPid = Integer.parseInt(m.group(2)); mBlock = new LinkedList(); mMatchingAnr = true; } m = CRASH.matcher(line); if (mMonkeyLog.getCrash() == null && m.matches()) { mApp = m.group(1); mPid = Integer.parseInt(m.group(2)); mBlock = new LinkedList(); mMatchingCrash = true; } m = EMPTY_NATIVE_CRASH.matcher(line); if (mMonkeyLog.getCrash() == null && m.matches()) { MiscLogcatItem crash = new NativeCrashItem(); crash.setStack(""); addCrashAndReset(crash); } m = TRACES_START.matcher(line); if (!mMatchedTrace && m.matches()) { mBlock = new LinkedList(); mMatchingTraces = true; } } /** * Add a crash to the monkey log item and reset the parser state for crashes. */ private void addCrashAndReset(MiscLogcatItem crash) { if (crash != null) { if (crash.getPid() == null) { crash.setPid(mPid); } if (crash.getApp() == null) { crash.setApp(mApp); } mMonkeyLog.setCrash(crash); } reset(); } /** * Reset the parser state for crashes. */ private void reset() { mApp = null; mPid = null; mMatchingAnr = false; mMatchingCrash = false; mMatchingJavaCrash = false; mMatchingNativeCrash = false; mMatchingTraces = false; mBlock = null; } /** * Parse the timestamp and return a date. * * @param timeStr The timestamp in the format {@code E, MM/dd/yyyy hh:mm:ss a} or * {@code EEE MMM dd HH:mm:ss zzz yyyy}. * @return The {@link Date}. */ private Date parseTime(String timeStr) { try { return new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy").parse(timeStr); } catch (ParseException e) { // CLog.v("Could not parse date %s with format EEE MMM dd HH:mm:ss zzz yyyy", timeStr); } try { return new SimpleDateFormat("E, MM/dd/yyyy hh:mm:ss a").parse(timeStr); } catch (ParseException e) { // CLog.v("Could not parse date %s with format E, MM/dd/yyyy hh:mm:ss a", timeStr); } // CLog.e("Could not parse date %s", timeStr); return null; } }