blob: 0fbb3f8b813a104736848cad14cd854dcd4f464f [file] [log] [blame]
/*
* 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<String> 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<String> 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<String>();
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<String>();
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<String>();
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;
}
}