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