Home | History | Annotate | Download | only in inspector
      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      */
     61     public static void inspect(Bugreport bugreport) {
     62         (new Inspector(bugreport)).inspect();
     63     }
     64 
     65     /**
     66      * Constructor.
     67      */
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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          */
    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          */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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