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