1 /* 2 * Copyright (C) 2012 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.AnrItem; 19 import com.android.loganalysis.item.MiscLogcatItem; 20 import com.android.loganalysis.item.MonkeyLogItem; 21 import com.android.loganalysis.item.MonkeyLogItem.DroppedCategory; 22 import com.android.loganalysis.item.NativeCrashItem; 23 import com.android.loganalysis.item.TracesItem; 24 25 import java.io.BufferedReader; 26 import java.io.IOException; 27 import java.text.ParseException; 28 import java.text.SimpleDateFormat; 29 import java.util.Date; 30 import java.util.LinkedList; 31 import java.util.List; 32 import java.util.regex.Matcher; 33 import java.util.regex.Pattern; 34 35 /** 36 * A {@link IParser} to parse monkey logs. 37 */ 38 public class MonkeyLogParser implements IParser { 39 private static final Pattern THROTTLE = Pattern.compile( 40 "adb shell monkey.* --throttle (\\d+).*"); 41 private static final Pattern SEED_AND_TARGET_COUNT = Pattern.compile( 42 ":Monkey: seed=(\\d+) count=(\\d+)"); 43 private static final Pattern SECURITY_EXCEPTIONS = Pattern.compile( 44 "adb shell monkey.* --ignore-security-exceptions.*"); 45 46 private static final Pattern PACKAGES = Pattern.compile(":AllowPackage: (\\S+)"); 47 private static final Pattern CATEGORIES = Pattern.compile(":IncludeCategory: (\\S+)"); 48 49 private static final Pattern START_UPTIME = Pattern.compile( 50 "# (.*) - device uptime = (\\d+\\.\\d+): Monkey command used for this test:"); 51 private static final Pattern STOP_UPTIME = Pattern.compile( 52 "# (.*) - device uptime = (\\d+\\.\\d+): Monkey command ran for: " + 53 "(\\d+):(\\d+) \\(mm:ss\\)"); 54 55 private static final Pattern INTERMEDIATE_COUNT = Pattern.compile( 56 "\\s+// Sending event #(\\d+)"); 57 private static final Pattern FINISHED = Pattern.compile("// Monkey finished"); 58 private static final Pattern FINAL_COUNT = Pattern.compile("Events injected: (\\d+)"); 59 private static final Pattern NO_ACTIVITIES = Pattern.compile( 60 "\\*\\* No activities found to run, monkey aborted."); 61 62 private static final Pattern DROPPED_KEYS = Pattern.compile(":Dropped: .*keys=(\\d+).*"); 63 private static final Pattern DROPPED_POINTERS = Pattern.compile( 64 ":Dropped: .*pointers=(\\d+).*"); 65 private static final Pattern DROPPED_TRACKBALLS = Pattern.compile( 66 ":Dropped: .*trackballs=(\\d+).*"); 67 private static final Pattern DROPPED_FLIPS = Pattern.compile(":Dropped: .*flips=(\\d+).*"); 68 private static final Pattern DROPPED_ROTATIONS = Pattern.compile( 69 ":Dropped: .*rotations=(\\d+).*"); 70 71 // Log messages can get intermixed in crash message, ignore those in the crash context. 72 private static final Pattern MONKEY_LOG_MESSAGE = Pattern.compile("$(:|Sleeping| //)"); 73 74 private static final Pattern ANR = Pattern.compile( 75 "// NOT RESPONDING: (\\S+) \\(pid (\\d+)\\)"); 76 private static final Pattern CRASH = Pattern.compile( 77 "// CRASH: (\\S+) \\(pid (\\d+)\\)"); 78 private static final Pattern EMPTY_NATIVE_CRASH = Pattern.compile("" + 79 "\\*\\* New native crash detected."); 80 private static final Pattern ABORTED = Pattern.compile("\\*\\* Monkey aborted due to error."); 81 82 private static final Pattern TRACES_START = Pattern.compile("anr traces:"); 83 private static final Pattern TRACES_STOP = Pattern.compile("// anr traces status was \\d+"); 84 85 private boolean mMatchingAnr = false; 86 private boolean mMatchingCrash = false; 87 private boolean mMatchingJavaCrash = false; 88 private boolean mMatchingNativeCrash = false; 89 private boolean mMatchingTraces = false; 90 private boolean mMatchedTrace = false; 91 private List<String> mBlock = null; 92 private String mApp = null; 93 private Integer mPid = null; 94 95 private MonkeyLogItem mMonkeyLog = new MonkeyLogItem(); 96 97 /** 98 * Parse a monkey log from a {@link BufferedReader} into an {@link MonkeyLogItem} object. 99 * 100 * @param input a {@link BufferedReader}. 101 * @return The {@link MonkeyLogItem}. 102 * @see #parse(List) 103 */ parse(BufferedReader input)104 public MonkeyLogItem parse(BufferedReader input) throws IOException { 105 String line; 106 while ((line = input.readLine()) != null) { 107 parseLine(line); 108 } 109 110 return mMonkeyLog; 111 } 112 113 /** 114 * {@inheritDoc} 115 * 116 * @return The {@link MonkeyLogItem}. 117 */ 118 @Override parse(List<String> lines)119 public MonkeyLogItem parse(List<String> lines) { 120 for (String line : lines) { 121 parseLine(line); 122 } 123 124 return mMonkeyLog; 125 } 126 127 /** 128 * Parse a line of input. 129 */ parseLine(String line)130 private void parseLine(String line) { 131 Matcher m; 132 133 if (mMatchingAnr) { 134 if ("".equals(line)) { 135 AnrItem crash = new AnrParser().parse(mBlock); 136 addCrashAndReset(crash); 137 } else { 138 m = MONKEY_LOG_MESSAGE.matcher(line); 139 if (!m.matches()) { 140 mBlock.add(line); 141 } 142 return; 143 } 144 } 145 146 if (mMatchingCrash) { 147 if (!mMatchingJavaCrash && !mMatchingNativeCrash && line.startsWith("// Short Msg: ")) { 148 if (line.contains("Native crash")) { 149 mMatchingNativeCrash = true; 150 } else { 151 mMatchingJavaCrash = true; 152 } 153 } 154 m = ABORTED.matcher(line); 155 if (m.matches()) { 156 MiscLogcatItem crash = null; 157 if (mMatchingJavaCrash) { 158 crash = new JavaCrashParser().parse(mBlock); 159 } else if (mMatchingNativeCrash) { 160 crash = new NativeCrashParser().parse(mBlock); 161 } 162 addCrashAndReset(crash); 163 } else { 164 m = MONKEY_LOG_MESSAGE.matcher(line); 165 if (!m.matches() && line.startsWith("// ") && !line.startsWith("// ** ")) { 166 line = line.replace("// ", ""); 167 mBlock.add(line); 168 } 169 return; 170 } 171 } 172 173 if (mMatchingTraces) { 174 m = TRACES_STOP.matcher(line); 175 if (m.matches()) { 176 TracesItem traces = new TracesParser().parse(mBlock); 177 178 // Set the trace if the crash is an ANR and if the app for the crash and trace match 179 if (traces != null && traces.getApp() != null && traces.getStack() != null && 180 mMonkeyLog.getCrash() instanceof AnrItem && 181 traces.getApp().equals(mMonkeyLog.getCrash().getApp())) { 182 ((AnrItem) mMonkeyLog.getCrash()).setTrace(traces.getStack()); 183 } 184 185 reset(); 186 mMatchedTrace = true; 187 } else { 188 m = MONKEY_LOG_MESSAGE.matcher(line); 189 if (!m.matches()) { 190 mBlock.add(line); 191 } 192 return; 193 } 194 } 195 196 m = THROTTLE.matcher(line); 197 if (m.matches()) { 198 mMonkeyLog.setThrottle(Integer.parseInt(m.group(1))); 199 } 200 m = SEED_AND_TARGET_COUNT.matcher(line); 201 if (m.matches()) { 202 mMonkeyLog.setSeed(Long.parseLong(m.group(1))); 203 mMonkeyLog.setTargetCount(Integer.parseInt(m.group(2))); 204 } 205 m = SECURITY_EXCEPTIONS.matcher(line); 206 if (m.matches()) { 207 mMonkeyLog.setIgnoreSecurityExceptions(true); 208 } 209 m = PACKAGES.matcher(line); 210 if (m.matches()) { 211 mMonkeyLog.addPackage(m.group(1)); 212 } 213 m = CATEGORIES.matcher(line); 214 if (m.matches()) { 215 mMonkeyLog.addCategory(m.group(1)); 216 } 217 m = START_UPTIME.matcher(line); 218 if (m.matches()) { 219 mMonkeyLog.setStartTime(parseTime(m.group(1))); 220 mMonkeyLog.setStartUptimeDuration((long) (Double.parseDouble(m.group(2)) * 1000)); 221 } 222 m = STOP_UPTIME.matcher(line); 223 if (m.matches()) { 224 mMonkeyLog.setStopTime(parseTime(m.group(1))); 225 mMonkeyLog.setStopUptimeDuration((long) (Double.parseDouble(m.group(2)) * 1000)); 226 mMonkeyLog.setTotalDuration(60 * 1000 * Integer.parseInt(m.group(3)) + 227 1000 *Integer.parseInt(m.group(4))); 228 } 229 m = INTERMEDIATE_COUNT.matcher(line); 230 if (m.matches()) { 231 mMonkeyLog.setIntermediateCount(Integer.parseInt(m.group(1))); 232 } 233 m = FINAL_COUNT.matcher(line); 234 if (m.matches()) { 235 mMonkeyLog.setFinalCount(Integer.parseInt(m.group(1))); 236 } 237 m = FINISHED.matcher(line); 238 if (m.matches()) { 239 mMonkeyLog.setIsFinished(true); 240 } 241 m = NO_ACTIVITIES.matcher(line); 242 if (m.matches()) { 243 mMonkeyLog.setNoActivities(true); 244 } 245 m = DROPPED_KEYS.matcher(line); 246 if (m.matches()) { 247 mMonkeyLog.setDroppedCount(DroppedCategory.KEYS, Integer.parseInt(m.group(1))); 248 } 249 m = DROPPED_POINTERS.matcher(line); 250 if (m.matches()) { 251 mMonkeyLog.setDroppedCount(DroppedCategory.POINTERS, Integer.parseInt(m.group(1))); 252 } 253 m = DROPPED_TRACKBALLS.matcher(line); 254 if (m.matches()) { 255 mMonkeyLog.setDroppedCount(DroppedCategory.TRACKBALLS, Integer.parseInt(m.group(1))); 256 } 257 m = DROPPED_FLIPS.matcher(line); 258 if (m.matches()) { 259 mMonkeyLog.setDroppedCount(DroppedCategory.FLIPS, Integer.parseInt(m.group(1))); 260 } 261 m = DROPPED_ROTATIONS.matcher(line); 262 if (m.matches()) { 263 mMonkeyLog.setDroppedCount(DroppedCategory.ROTATIONS, Integer.parseInt(m.group(1))); 264 } 265 m = ANR.matcher(line); 266 if (mMonkeyLog.getCrash() == null && m.matches()) { 267 mApp = m.group(1); 268 mPid = Integer.parseInt(m.group(2)); 269 mBlock = new LinkedList<String>(); 270 mMatchingAnr = true; 271 } 272 m = CRASH.matcher(line); 273 if (mMonkeyLog.getCrash() == null && m.matches()) { 274 mApp = m.group(1); 275 mPid = Integer.parseInt(m.group(2)); 276 mBlock = new LinkedList<String>(); 277 mMatchingCrash = true; 278 } 279 m = EMPTY_NATIVE_CRASH.matcher(line); 280 if (mMonkeyLog.getCrash() == null && m.matches()) { 281 MiscLogcatItem crash = new NativeCrashItem(); 282 crash.setStack(""); 283 addCrashAndReset(crash); 284 } 285 m = TRACES_START.matcher(line); 286 if (!mMatchedTrace && m.matches()) { 287 mBlock = new LinkedList<String>(); 288 mMatchingTraces = true; 289 } 290 } 291 292 /** 293 * Add a crash to the monkey log item and reset the parser state for crashes. 294 */ addCrashAndReset(MiscLogcatItem crash)295 private void addCrashAndReset(MiscLogcatItem crash) { 296 if (crash != null) { 297 if (crash.getPid() == null) { 298 crash.setPid(mPid); 299 } 300 if (crash.getApp() == null) { 301 crash.setApp(mApp); 302 } 303 mMonkeyLog.setCrash(crash); 304 } 305 306 reset(); 307 } 308 309 /** 310 * Reset the parser state for crashes. 311 */ reset()312 private void reset() { 313 mApp = null; 314 mPid = null; 315 mMatchingAnr = false; 316 mMatchingCrash = false; 317 mMatchingJavaCrash = false; 318 mMatchingNativeCrash = false; 319 mMatchingTraces = false; 320 mBlock = null; 321 } 322 323 /** 324 * Parse the timestamp and return a date. 325 * 326 * @param timeStr The timestamp in the format {@code E, MM/dd/yyyy hh:mm:ss a} or 327 * {@code EEE MMM dd HH:mm:ss zzz yyyy}. 328 * @return The {@link Date}. 329 */ parseTime(String timeStr)330 private Date parseTime(String timeStr) { 331 try { 332 return new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy").parse(timeStr); 333 } catch (ParseException e) { 334 // CLog.v("Could not parse date %s with format EEE MMM dd HH:mm:ss zzz yyyy", timeStr); 335 } 336 337 try { 338 return new SimpleDateFormat("E, MM/dd/yyyy hh:mm:ss a").parse(timeStr); 339 } catch (ParseException e) { 340 // CLog.v("Could not parse date %s with format E, MM/dd/yyyy hh:mm:ss a", timeStr); 341 } 342 343 // CLog.e("Could not parse date %s", timeStr); 344 return null; 345 } 346 347 } 348