1 /*
2  * Copyright (C) 2016 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 
17 package com.android.bugreport.inspector;
18 
19 import com.android.bugreport.anr.Anr;
20 import com.android.bugreport.anr.AnrParser;
21 import com.android.bugreport.bugreport.Bugreport;
22 import com.android.bugreport.bugreport.ProcessInfo;
23 import com.android.bugreport.bugreport.ThreadInfo;
24 import com.android.bugreport.logcat.Logcat;
25 import com.android.bugreport.logcat.LogcatParser;
26 import com.android.bugreport.logcat.LogLine;
27 import com.android.bugreport.stacks.ProcessSnapshot;
28 import com.android.bugreport.stacks.JavaStackFrameSnapshot;
29 import com.android.bugreport.stacks.LockSnapshot;
30 import com.android.bugreport.stacks.StackFrameSnapshot;
31 import com.android.bugreport.stacks.ThreadSnapshot;
32 import com.android.bugreport.stacks.VmTraces;
33 import com.android.bugreport.util.Utils;
34 import com.android.bugreport.util.Lines;
35 
36 import java.util.ArrayList;
37 import java.util.Calendar;
38 import java.util.GregorianCalendar;
39 import java.util.HashSet;
40 import java.util.Set;
41 import java.util.regex.Pattern;
42 import java.util.regex.Matcher;
43 
44 /**
45  * Inspects a raw parsed bugreport.  Makes connections between the different sections,
46  * and annotates the different parts.
47  *
48  * (This is the "smarts" of the app. The rendering is mostly just straightforward view code.)
49  */
50 public class Inspector {
51     private static final String[] NO_JAVA_METHODS = new String[0];
52     private static final String[] HANDWRITTEN_BINDER_SUFFIXES = new String[] { "Native", "Proxy" };
53 
54     private final Matcher mBufferBeginRe = LogcatParser.BUFFER_BEGIN_RE.matcher("");
55 
56     private final Bugreport mBugreport;
57 
58     /**
59      * Inspect a bugreport.
60      */
inspect(Bugreport bugreport)61     public static void inspect(Bugreport bugreport) {
62         (new Inspector(bugreport)).inspect();
63     }
64 
65     /**
66      * Constructor.
67      */
Inspector(Bugreport bugreport)68     private Inspector(Bugreport bugreport) {
69         mBugreport = bugreport;
70     }
71 
72     /**
73      * Do the inspection.  Calls to the various sub-functions to do the work.
74      */
inspect()75     private void inspect() {
76         makeProcessInfo();
77 
78         findAnr();
79 
80         inspectProcesses(mBugreport.vmTracesJustNow);
81         inspectProcesses(mBugreport.vmTracesLastAnr);
82 
83         if (mBugreport.anr != null) {
84             inspectProcesses(mBugreport.anr.vmTraces);
85             markDeadlocks(mBugreport.anr.vmTraces, mBugreport.anr.pid);
86         }
87 
88         inventLogcatTimes();
89         mergeLogcat();
90         makeInterestingLogcat();
91         markLogcatProcessesAndThreads();
92         markAnrLogcatRegions();
93         markBugreportRegions();
94         //trimLogcat();
95 
96         if (mBugreport.anr != null) {
97             makeInterestingProcesses(mBugreport.anr.vmTraces);
98         }
99     }
100 
101     /**
102      * Go through all our sources of information and figure out as many process
103      * and thread names as we can.
104      */
makeProcessInfo()105     private void makeProcessInfo() {
106         if (mBugreport.anr != null) {
107             makeProcessInfo(mBugreport.anr.vmTraces.processes);
108         }
109         if (mBugreport.vmTracesJustNow != null) {
110             makeProcessInfo(mBugreport.vmTracesJustNow.processes);
111         }
112         if (mBugreport.vmTracesLastAnr != null) {
113             makeProcessInfo(mBugreport.vmTracesLastAnr.processes);
114         }
115     }
116 
117     /**
118      * Sniff this VmTraces object for ProcessInfo and ThreadInfos that we need to create
119      * and add them to the Bugreport.
120      */
makeProcessInfo(ArrayList<ProcessSnapshot> processes)121     private void makeProcessInfo(ArrayList<ProcessSnapshot> processes) {
122         for (ProcessSnapshot process: processes) {
123             final ProcessInfo pi = makeProcessInfo(process.pid, process.cmdLine);
124             for (ThreadSnapshot thread: process.threads) {
125                 makeThreadInfo(pi, thread.sysTid, thread.name);
126             }
127         }
128     }
129 
130     /**
131      * If there isn't already one for this pid, make a ProcessInfo.  If one already
132      * exists, return that. If we now have a more complete cmdLine, fill that in too.
133      */
makeProcessInfo(int pid, String cmdLine)134     private ProcessInfo makeProcessInfo(int pid, String cmdLine) {
135         ProcessInfo pi = mBugreport.allKnownProcesses.get(pid);
136         if (pi == null) {
137             pi = new ProcessInfo(pid, cmdLine);
138             mBugreport.allKnownProcesses.put(pid, pi);
139         } else {
140             if (cmdLine.length() > pi.cmdLine.length()) {
141                 pi.cmdLine = cmdLine;
142             }
143         }
144         return pi;
145     }
146 
147     /**
148      * If there isn't already one for this tid, make a ThreadInfo.  If one already
149      * exists, return that. If we now have a more complete name, fill that in too.
150      */
makeThreadInfo(ProcessInfo pi, int tid, String name)151     private ThreadInfo makeThreadInfo(ProcessInfo pi, int tid, String name) {
152         ThreadInfo ti = pi.threads.get(tid);
153         if (ti == null) {
154             ti = new ThreadInfo(pi, tid, name);
155             pi.threads.put(tid, ti);
156         } else {
157             if (name.length() > ti.name.length()) {
158                 ti.name = name;
159             }
160         }
161         return ti;
162     }
163 
164     /**
165      * If there isn't already an ANR set on the bugreport (e.g. from monkeys), find
166      * one in the logcat.
167      */
findAnr()168     private void findAnr() {
169         // TODO: It would be better to restructure the whole triage thing into a more
170         // modular "suggested problem" format, rather than it all being centered around
171         // there being an anr.  More thoughts on this later...
172         if (mBugreport.anr != null) {
173             return;
174         }
175         final ArrayList<LogLine> logLines = mBugreport.systemLog.filter("ActivityManager", "E");
176         final AnrParser parser = new AnrParser();
177         final ArrayList<Anr> anrs = parser.parse(new Lines<LogLine>(logLines), false);
178         if (anrs.size() > 0) {
179             mBugreport.anr = anrs.get(0);
180             // TODO: This is LAST anr, not FIRST anr, so it might not actually match.
181             // We really should find a better way of recording the traces.
182             mBugreport.anr.vmTraces = mBugreport.vmTracesLastAnr;
183         }
184     }
185 
186     /**
187      * Do all the process inspection.  Works on any list of processes, not just ANRs.
188      */
inspectProcesses(VmTraces vmTraces)189     private void inspectProcesses(VmTraces vmTraces) {
190         combineLocks(vmTraces.processes);
191         markBinderThreads(vmTraces.processes);
192         markBlockedThreads(vmTraces.processes);
193         markInterestingThreads(vmTraces.processes);
194     }
195 
196     /**
197      * Pulls the locks out of the individual stack frames and tags the threads
198      * with which locks are being held or blocked on.
199      */
combineLocks(ArrayList<ProcessSnapshot> processes)200     private void combineLocks(ArrayList<ProcessSnapshot> processes) {
201         for (ProcessSnapshot process: processes) {
202             for (ThreadSnapshot thread: process.threads) {
203                 for (StackFrameSnapshot frame: thread.frames) {
204                     if (frame.frameType == StackFrameSnapshot.FRAME_TYPE_JAVA) {
205                         final JavaStackFrameSnapshot f = (JavaStackFrameSnapshot)frame;
206                         for (LockSnapshot lock: f.locks) {
207                             final LockSnapshot prev = thread.locks.get(lock.address);
208                             if (prev != null) {
209                                 prev.type |= lock.type;
210                             } else {
211                                 thread.locks.put(lock.address, lock.clone());
212                             }
213                         }
214                     }
215                 }
216             }
217         }
218     }
219 
220     /**
221      * Mark the threads that are doing binder transactions.
222      */
markBinderThreads(ArrayList<ProcessSnapshot> processes)223     private void markBinderThreads(ArrayList<ProcessSnapshot> processes) {
224         for (ProcessSnapshot process: processes) {
225             for (ThreadSnapshot thread: process.threads) {
226                 markOutgoingBinderThread(thread);
227                 markIncomingBinderThread(thread);
228             }
229         }
230     }
231 
232     /**
233      * Sniff a thread thread stack for whether it is doing an outgoing binder
234      * transaction (at the top of the stack).
235      */
markOutgoingBinderThread(ThreadSnapshot thread)236     private boolean markOutgoingBinderThread(ThreadSnapshot thread) {
237         // If top of the stack is android.os.BinderProxy.transactNative...
238         int i;
239         final int N = thread.frames.size();
240         StackFrameSnapshot frame = null;
241         for (i=0; i<N; i++) {
242             frame = thread.frames.get(i);
243             if (frame.frameType == StackFrameSnapshot.FRAME_TYPE_JAVA) {
244                 break;
245             }
246         }
247         if (i >= N) {
248             return false;
249         }
250         JavaStackFrameSnapshot f = (JavaStackFrameSnapshot)frame;
251         if (!("android.os".equals(f.packageName)
252                 && "BinderProxy".equals(f.className)
253                 && "transactNative".equals(f.methodName))) {
254             return false;
255         }
256 
257         // And the next one is android.os.BinderProxy.transact...
258         i++;
259         if (i >= N) {
260             return false;
261         }
262         frame = thread.frames.get(i);
263         if (frame.frameType != StackFrameSnapshot.FRAME_TYPE_JAVA) {
264             return false;
265         }
266         f = (JavaStackFrameSnapshot)frame;
267         if (!("android.os".equals(f.packageName)
268                 && "BinderProxy".equals(f.className)
269                 && "transact".equals(f.methodName))) {
270             return false;
271         }
272 
273         // Then the one after that is the glue code for that IPC.
274         i++;
275         if (i >= N) {
276             return false;
277         }
278         frame = thread.frames.get(i);
279         if (frame.frameType != StackFrameSnapshot.FRAME_TYPE_JAVA) {
280             return false;
281         }
282         f = (JavaStackFrameSnapshot)frame;
283         thread.outboundBinderPackage = f.packageName;
284         thread.outboundBinderClass = fixBinderClass(f.className);
285         thread.outboundBinderMethod = f.methodName;
286         return true;
287     }
288 
289     /**
290      * Sniff a thread thread stack for whether it is doing an inbound binder
291      * transaction (at the bottom of the stack).
292      */
markIncomingBinderThread(ThreadSnapshot thread)293     private boolean markIncomingBinderThread(ThreadSnapshot thread) {
294         // If bottom of the stack is android.os.Binder.execTransact...
295         int i;
296         final int N = thread.frames.size();
297         StackFrameSnapshot frame = null;
298         for (i=N-1; i>=0; i--) {
299             frame = thread.frames.get(i);
300             if (frame.frameType == StackFrameSnapshot.FRAME_TYPE_JAVA) {
301                 break;
302             }
303         }
304         if (i < 0) {
305             return false;
306         }
307         JavaStackFrameSnapshot f = (JavaStackFrameSnapshot)frame;
308         if (!("android.os".equals(f.packageName)
309                 && "Binder".equals(f.className)
310                 && "execTransact".equals(f.methodName))) {
311             return false;
312         }
313 
314         // The next one will be the binder glue, which has the package and interface
315         i--;
316         if (i < 0) {
317             return false;
318         }
319         frame = thread.frames.get(i);
320         if (frame.frameType != StackFrameSnapshot.FRAME_TYPE_JAVA) {
321             return false;
322         }
323         f = (JavaStackFrameSnapshot)frame;
324         thread.inboundBinderPackage = f.packageName;
325         thread.inboundBinderClass = fixBinderClass(f.className);
326 
327         // And the one after that will be the implementation, which has the method.
328         // If it got inlined, e.g. by proguard, we might not get a method.
329         i--;
330         if (i < 0) {
331             return true;
332         }
333         frame = thread.frames.get(i);
334         if (frame.frameType != StackFrameSnapshot.FRAME_TYPE_JAVA) {
335             return true;
336         }
337         f = (JavaStackFrameSnapshot)frame;
338         thread.inboundBinderMethod = f.methodName;
339         return true;
340     }
341 
342     /**
343      * Try to clean up the bomder class name by removing the aidl inner classes
344      * and sniffing out the older manually written binder glue convention of
345      * calling the functions "Native."
346      */
fixBinderClass(String className)347     private String fixBinderClass(String className) {
348         if (className == null) {
349             return null;
350         }
351 
352         final String stubProxySuffix = "$Stub$Proxy";
353         if (className.endsWith(stubProxySuffix)) {
354             return className.substring(0, className.length() - stubProxySuffix.length());
355         }
356 
357         final String stubSuffix = "$Stub";
358         if (className.endsWith(stubSuffix)) {
359             return className.substring(0, className.length() - stubSuffix.length());
360         }
361 
362         for (String suffix: HANDWRITTEN_BINDER_SUFFIXES) {
363             if (className.length() > suffix.length() + 2) {
364                 if (className.endsWith(suffix)) {
365                     final char first = className.charAt(0);
366                     final char second = className.charAt(1);
367                     if (className.endsWith(suffix)) {
368                         if (first == 'I' && Character.isUpperCase(second)) {
369                             return className.substring(0, className.length()-suffix.length());
370                         } else {
371                             return "I" + className.substring(0, className.length()-suffix.length());
372                         }
373                     }
374                 }
375             }
376         }
377 
378         return className;
379     }
380 
381     /**
382      * Sniff the threads that are blocked on other things.
383      */
markBlockedThreads(ArrayList<ProcessSnapshot> processes)384     private void markBlockedThreads(ArrayList<ProcessSnapshot> processes) {
385         for (ProcessSnapshot process: processes) {
386             for (ThreadSnapshot thread: process.threads) {
387                 // These threads are technically blocked, but it's expected so don't report it.
388                 if (matchesJavaStack(thread, "HeapTaskDaemon", new String[] {
389                             "dalvik.system.VMRuntime.runHeapTasks",
390                             "java.lang.Daemons$HeapTaskDaemon.run",
391                             "java.lang.Thread.run",
392                         })) {
393                     continue;
394                 }
395 
396                 thread.blocked = isThreadBlocked(thread);
397             }
398         }
399     }
400 
401     /**
402      * Sniff whether a thread is blocked on at least one java lock.
403      */
isThreadBlocked(ThreadSnapshot thread)404     private boolean isThreadBlocked(ThreadSnapshot thread) {
405         for (LockSnapshot lock: thread.locks.values()) {
406             if ((lock.type & LockSnapshot.BLOCKED) != 0) {
407                 return true;
408             }
409         }
410         return false;
411     }
412 
413     /**
414      * Mark threads to be flagged in the bugreport view.
415      */
markInterestingThreads(ArrayList<ProcessSnapshot> processes)416     private void markInterestingThreads(ArrayList<ProcessSnapshot> processes) {
417         for (ProcessSnapshot process: processes) {
418             for (ThreadSnapshot thread: process.threads) {
419                 thread.interesting = isThreadInteresting(thread);
420             }
421         }
422     }
423 
424     /**
425      * Clone the "interesting" processes and filter out any threads that aren't
426      * marked "interesting" and any processes without any interesting threads.
427      */
makeInterestingProcesses(VmTraces vmTraces)428     private void makeInterestingProcesses(VmTraces vmTraces) {
429         for (ProcessSnapshot process: vmTraces.processes) {
430             // Make a deep copy of the process
431             process = process.clone();
432 
433             // Filter out the threads that aren't interesting
434             for (int i=process.threads.size()-1; i>=0; i--) {
435                 if (!process.threads.get(i).interesting) {
436                     process.threads.remove(i);
437                 }
438             }
439 
440             // If there is anything interesting about the process itself, add it
441             if (isProcessInteresting(process)) {
442                 vmTraces.interestingProcesses.add(process);
443             }
444         }
445     }
446 
447     /**
448      * Determine whether there is anything worth noting about this process.
449      */
isProcessInteresting(ProcessSnapshot process)450     private boolean isProcessInteresting(ProcessSnapshot process) {
451         // This is the Process mentioned by the ANR report
452         if (mBugreport.anr != null && mBugreport.anr.pid == process.pid) {
453             return true;
454         }
455 
456         // There are > 1 threads that are interesting in this process
457         if (process.threads.size() > 0) {
458             return true;
459         }
460 
461         // TODO: The CPU usage for this process is > 10%
462         if (false) {
463             return true;
464         }
465 
466         // Otherwise it's boring
467         return false;
468     }
469 
470     /**
471      * Determine whether there is anything worth noting about this thread.
472      */
isThreadInteresting(ThreadSnapshot thread)473     private boolean isThreadInteresting(ThreadSnapshot thread) {
474         // The thread that dumps the stack traces is boring
475         if (matchesJavaStack(thread, "Signal Catcher", NO_JAVA_METHODS)) {
476             return false;
477         }
478 
479         // The thread is marked runnable
480         if (thread.runnable) {
481             return true;
482         }
483 
484         // TODO: It's holding a mutex that's not "mutator lock"(shared held)?
485 
486         // Binder threads are interesting
487         if (thread.isBinder()) {
488             return true;
489         }
490 
491         // Otherwise it's boring
492         return false;
493     }
494 
495     /**
496      * Return whether the java stack for a thread is the same as the signature supplied.
497      * Skips non-java stack frames.
498      */
matchesJavaStack(ThreadSnapshot thread, String name, String[] signature)499     private boolean matchesJavaStack(ThreadSnapshot thread, String name, String[] signature) {
500         // Check the name
501         if (name != null && !name.equals(thread.name)) {
502             return false;
503         }
504 
505         final ArrayList<StackFrameSnapshot> frames = thread.frames;
506         int i = 0;
507         final int N = frames.size();
508         int j = 0;
509         final int M = signature.length;
510 
511         while (i<N && j<M) {
512             final StackFrameSnapshot frame = frames.get(i);
513             if (frame.frameType != JavaStackFrameSnapshot.FRAME_TYPE_JAVA) {
514                 // Not java, keep advancing.
515                 i++;
516                 continue;
517             }
518             final JavaStackFrameSnapshot f = (JavaStackFrameSnapshot)frame;
519             final String full = (f.packageName != null ? f.packageName + "." : "")
520                     + f.className + "." + f.methodName;
521             if (!full.equals(signature[j])) {
522                 // This java frame doesn't match the expected signature element,
523                 // so it's not a match.
524                 return false;
525             }
526             // Advance both
527             i++;
528             j++;
529         }
530 
531         // If we didn't get through the signature, it's not a match
532         if (j != M) {
533             return false;
534         }
535 
536         // If there are more java frames, it's not a match
537         for (; i<N; i++) {
538             if (frames.get(i).frameType == JavaStackFrameSnapshot.FRAME_TYPE_JAVA) {
539                 return false;
540             }
541         }
542 
543         // We have a winner.
544         return true;
545     }
546 
547     /**
548      * Traverse the threads looking for cyclical dependencies of blocked threads.
549      *
550      * TODO: If pid isn't provided, we should run it for all main threads.  And show all of
551      * the deadlock cycles, with the one from the anr at the top if possible.
552      *
553      * @see DeadlockDetector
554      */
markDeadlocks(VmTraces vmTraces, int pid)555     private void markDeadlocks(VmTraces vmTraces, int pid) {
556         final Set<ProcessSnapshot> deadlock = DeadlockDetector.detectDeadlocks(vmTraces, pid);
557         vmTraces.deadlockedProcesses.addAll(deadlock);
558     }
559 
560     /**
561      * Fill in times for the logcat section log lines that don't have one (like
562      * the beginning of buffer lines).
563      */
inventLogcatTimes()564     private void inventLogcatTimes() {
565         inventLogcatTimes(mBugreport.systemLog.lines);
566         inventLogcatTimes(mBugreport.eventLog.lines);
567         if (mBugreport.logcat != null) {
568             inventLogcatTimes(mBugreport.logcat.lines);
569         }
570     }
571 
572     /**
573      * Fill in times for a logcat section by taking the time from an adjacent line.
574      * Prefers to get the time from a line after the log line.
575      */
inventLogcatTimes(ArrayList<LogLine> lines)576     private void inventLogcatTimes(ArrayList<LogLine> lines) {
577         GregorianCalendar time = null;
578         final int N = lines.size();
579         int i;
580         // Going backwards first makes most missing ones get the next time
581         // which will pair it with the next log line in the merge, which is
582         // what we want.
583         for (i=N-1; i>=0; i--) {
584             final LogLine line = lines.get(i);
585             if (line.time == null) {
586                 line.time = time;
587             } else {
588                 time = line.time;
589             }
590         }
591 
592         // Then go find the last one that's null, and get it a time.
593         // If none have times, then... oh well.
594         for (i=N-1; i>=0; i--) {
595             final LogLine line = lines.get(i);
596             if (line.time != null) {
597                 time = line.time;
598                 break;
599             }
600         }
601         for (; i<N && i>=0; i++) {
602             final LogLine line = lines.get(i);
603             line.time = time;
604         }
605     }
606 
607     /**
608      * Merge the system and event logs by timestamp.
609      */
mergeLogcat()610     private void mergeLogcat() {
611         // Only do this if they haven't already supplied a logcat.
612         if (mBugreport.logcat != null) {
613             return;
614         }
615 
616         // Renumber the logcat lines.  We mess up the other lists, but that
617         // saves the work of making copies of the logcat lines.  If this
618         // really becomes a problem, then it's not too much work to add
619         // LogLine.clone().
620         int lineno = 1;
621         final Logcat result = mBugreport.logcat =  new Logcat();
622         final ArrayList<LogLine> system = mBugreport.systemLog.lines;
623         final ArrayList<LogLine> event = mBugreport.eventLog.lines;
624 
625         final int systemSize = system != null ? system.size() : 0;
626         final int eventSize = event != null ? event.size() : 0;
627 
628         int systemIndex = 0;
629         int eventIndex = 0;
630 
631         // The event log doesn't have a beginning of marker.  Make up one
632         // when we see the first event line.
633         boolean seenEvent = false;
634 
635         while (systemIndex < systemSize && eventIndex < eventSize) {
636             final LogLine systemLine = system.get(systemIndex);
637             final LogLine eventLine = event.get(eventIndex);
638 
639             if (systemLine.time == null) {
640                 systemLine.lineno = lineno++;
641                 result.lines.add(systemLine);
642                 systemIndex++;
643                 continue;
644             }
645 
646             if (eventLine.time == null) {
647                 eventLine.lineno = lineno++;
648                 result.lines.add(eventLine);
649                 eventIndex++;
650                 seenEvent = true;
651                 continue;
652             }
653 
654             if (systemLine.time.compareTo(eventLine.time) <= 0) {
655                 systemLine.lineno = lineno++;
656                 result.lines.add(systemLine);
657                 systemIndex++;
658             } else {
659                 if (!seenEvent) {
660                     final LogLine synthetic = new LogLine();
661                     synthetic.lineno = lineno++;
662                     synthetic.rawText = synthetic.text = "--------- beginning of event";
663                     synthetic.bufferBegin = "event";
664                     synthetic.time = eventLine.time;
665                     result.lines.add(synthetic);
666                     seenEvent = true;
667                 }
668                 eventLine.lineno = lineno++;
669                 result.lines.add(eventLine);
670                 eventIndex++;
671             }
672         }
673 
674         for (; systemIndex < systemSize; systemIndex++) {
675             final LogLine systemLine = system.get(systemIndex);
676             systemLine.lineno = lineno++;
677             result.lines.add(systemLine);
678         }
679 
680         for (; eventIndex < eventSize; eventIndex++) {
681             final LogLine eventLine = event.get(eventIndex);
682             if (!seenEvent) {
683                 final LogLine synthetic = new LogLine();
684                 synthetic.lineno = lineno++;
685                 synthetic.rawText = synthetic.text = "--------- beginning of event";
686                 synthetic.bufferBegin = "event";
687                 synthetic.time = eventLine.time;
688                 result.lines.add(synthetic);
689                 seenEvent = true;
690             }
691             eventLine.lineno = lineno++;
692             result.lines.add(eventLine);
693         }
694     }
695 
696     /**
697      * Utility class to match log lines that are "interesting" and will
698      * be called out with links at the top of the log and triage sections.
699      */
700     private class InterestingLineMatcher {
701         private String mTag;
702         protected Matcher mMatcher;
703 
704         /**
705          * Construct the helper object with the log tag that must be an
706          * exact match and a message which is a regex pattern.
707          */
InterestingLineMatcher(String tag, String regex)708         public InterestingLineMatcher(String tag, String regex) {
709             mTag = tag;
710             mMatcher = Pattern.compile(regex).matcher("");
711         }
712 
713         /**
714          * Return whether the LogLine text matches the patterns supplied in the
715          * constructor.
716          */
match(LogLine line)717         public boolean match(LogLine line) {
718             return mTag.equals(line.tag)
719                     && Utils.matches(mMatcher, line.text);
720         }
721     }
722 
723     /**
724      * The matchers to use to detect interesting log lines.
725      */
726     private final InterestingLineMatcher[] mInterestingLineMatchers
727             = new InterestingLineMatcher[] {
728                 // ANR logcat
729                 new InterestingLineMatcher("ActivityManager",
730                         "ANR in \\S+.*"),
731             };
732 
733     /**
734      * Mark the log lines to be called out with links at the top of the
735      * log and triage sections.
736      */
makeInterestingLogcat()737     private void makeInterestingLogcat() {
738         final Logcat logcat = mBugreport.logcat;
739         Matcher m;
740 
741         for (LogLine line: logcat.lines) {
742             // Beginning of buffer
743             if ((m = Utils.match(mBufferBeginRe, line.rawText)) != null) {
744                 mBugreport.interestingLogLines.add(line);
745             }
746 
747 
748             // Regular log lines
749             for (InterestingLineMatcher ilm: mInterestingLineMatchers) {
750                 if (ilm.match(line)) {
751                     mBugreport.interestingLogLines.add(line);
752                 }
753             }
754         }
755     }
756 
757     /**
758      * For each of the log lines, attach a process and a thread.
759      */
markLogcatProcessesAndThreads()760     private void markLogcatProcessesAndThreads() {
761         final Logcat logcat = mBugreport.logcat;
762 
763         final Matcher inputDispatcherRe = Pattern.compile(
764                 "Application is not responding: .* It has been (\\d+\\.?\\d*)ms since event,"
765                 + " (\\d+\\.?\\d*)ms since wait started.*").matcher("");
766 
767         for (LogLine line: logcat.lines) {
768             line.process = mBugreport.allKnownProcesses.get(line.pid);
769             if (line.process != null) {
770                 line.thread = line.process.threads.get(line.tid);
771             }
772         }
773     }
774 
775     /**
776      * For each of the log lines that indicate a time range between the beginning
777      * of an anr timer and when it went off, mark that range.
778      */
markAnrLogcatRegions()779     private void markAnrLogcatRegions() {
780         final Logcat logcat = mBugreport.logcat;
781 
782         final Matcher inputDispatcherRe = Pattern.compile(
783                 "Application is not responding: .* It has been (\\d+\\.?\\d*)ms since event,"
784                 + " (\\d+\\.?\\d*)ms since wait started.*").matcher("");
785 
786         for (LogLine line: logcat.lines) {
787             if ("InputDispatcher".equals(line.tag)
788                     && Utils.matches(inputDispatcherRe, line.text)) {
789                 float f = Float.parseFloat(inputDispatcherRe.group(2));
790                 int seconds = (int)(f / 1000);
791                 int milliseconds = Math.round(f % 1000);
792                 final Calendar begin = (Calendar)line.time.clone();
793                 begin.add(Calendar.SECOND, -seconds);
794                 begin.add(Calendar.MILLISECOND, -milliseconds);
795                 markAnrRegion(begin, line.time);
796             }
797         }
798     }
799 
800     /**
801      * Mark the log lines that happened between the begin and end timestamps
802      * as during the period between when an ANR timer is set and when it goes
803      * off.
804      */
markAnrRegion(Calendar begin, Calendar end)805     private void markAnrRegion(Calendar begin, Calendar end) {
806         for (LogLine line: mBugreport.logcat.lines) {
807             if (line.time.compareTo(begin) >= 0
808                     && line.time.compareTo(end) < 0) {
809                 line.regionAnr = true;
810             }
811         }
812     }
813 
814     /**
815      * Mark the log lines that were captured while this bugreport was being
816      * taken. Those tend to be less reliable, and are also an indicator of
817      * when the user saw the bug that caused them to take a bugreport.
818      */
markBugreportRegions()819     private void markBugreportRegions() {
820         final Calendar begin = mBugreport.startTime;
821         final Calendar end = mBugreport.endTime;
822         for (LogLine line: mBugreport.logcat.lines) {
823             if (line.time != null) {
824                 if (line.time.compareTo(begin) >= 0
825                         && line.time.compareTo(end) < 0) {
826                     line.regionBugreport = true;
827                 }
828             }
829         }
830     }
831 
832     /**
833      * Trim the logcat to show no more than 3 seconds after the beginning of
834      * the bugreport, and no more than 5000 lines before the beginning of the bugreport.
835      */
trimLogcat()836     private void trimLogcat() {
837         final Calendar end = (Calendar)mBugreport.startTime.clone();
838         end.add(Calendar.SECOND, 3);
839 
840         final ArrayList<LogLine> lines = mBugreport.logcat.lines;
841         int i;
842 
843         // Trim the ones at the end
844         int endIndex = lines.size() - 1;
845         for (i=lines.size()-1; i>=0; i--) {
846             final LogLine line = lines.get(i);
847             if (line.time != null) {
848                 // If we've gotten to 3s after when the bugreport started getting taken, stop.
849                 if (line.time.compareTo(end) > 0) {
850                     endIndex = i;
851                     break;
852                 }
853             }
854         }
855 
856         // Trim the ones at the beginning
857         int startIndex = 0;
858         int count = 0;
859         for (; i>=0; i--) {
860             final LogLine line = lines.get(i);
861             count++;
862             if (count >= 5000) {
863                 startIndex = i;
864                 break;
865             }
866         }
867 
868         mBugreport.logcat.lines = new ArrayList<LogLine>(lines.subList(startIndex, endIndex));
869     }
870 }
871