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.html; 18 19 import com.android.bugreport.anr.Anr; 20 import com.android.bugreport.bugreport.Bugreport; 21 import com.android.bugreport.cpuinfo.CpuUsage; 22 import com.android.bugreport.cpuinfo.CpuUsageSnapshot; 23 import com.android.bugreport.logcat.Logcat; 24 import com.android.bugreport.logcat.LogLine; 25 import com.android.bugreport.stacks.ProcessSnapshot; 26 import com.android.bugreport.stacks.JavaStackFrameSnapshot; 27 import com.android.bugreport.stacks.KernelStackFrameSnapshot; 28 import com.android.bugreport.stacks.LockSnapshot; 29 import com.android.bugreport.stacks.NativeStackFrameSnapshot; 30 import com.android.bugreport.stacks.StackFrameSnapshot; 31 import com.android.bugreport.stacks.ThreadSnapshot; 32 import com.android.bugreport.stacks.VmTraces; 33 34 import com.google.clearsilver.jsilver.JSilver; 35 import com.google.clearsilver.jsilver.JSilverOptions; 36 import com.google.clearsilver.jsilver.autoescape.EscapeMode; 37 import com.google.clearsilver.jsilver.data.Data; 38 import com.google.clearsilver.jsilver.resourceloader.ClassResourceLoader; 39 40 import java.io.File; 41 import java.io.FileWriter; 42 import java.io.IOException; 43 import java.util.ArrayList; 44 import java.util.Collection; 45 import java.util.HashMap; 46 import java.util.HashSet; 47 import java.util.List; 48 49 /** 50 * Formats a bugreport as html and writes the file. 51 */ 52 public class Renderer { 53 /** 54 * The next id of the panel to use. 55 */ 56 private int mNextPanelId; 57 Renderer()58 public Renderer() { 59 } 60 61 /** 62 * Render the Bugreport into the html file. 63 */ render(File outFile, Bugreport bugreport)64 public void render(File outFile, Bugreport bugreport) throws IOException { 65 // Load the template and renderer 66 final JSilverOptions options = new JSilverOptions(); 67 options.setEscapeMode(EscapeMode.ESCAPE_HTML); 68 final JSilver jsilver = new JSilver(new ClassResourceLoader(getClass()), options); 69 final Data hdf = jsilver.createData(); 70 71 // Build the hierarchical data format data structure 72 makeHdf(hdf, bugreport); 73 74 if (false) { 75 System.out.println(hdf); 76 } 77 78 // Render it 79 final FileWriter writer = new FileWriter(outFile); 80 try { 81 jsilver.render("anr-template.html", hdf, writer); 82 writer.close(); 83 } catch (IOException ex) { 84 // Delete the file so we don't leave half-written files laying around. 85 try { 86 writer.close(); 87 } catch (IOException e) { 88 } 89 outFile.delete(); 90 // And rethrow the exception. 91 throw ex; 92 } 93 } 94 95 /** 96 * Build the hdf for a Bugreport. 97 */ makeHdf(Data hdf, Bugreport bugreport)98 private void makeHdf(Data hdf, Bugreport bugreport) { 99 // Triage 100 makeTriageHdf(hdf, bugreport); 101 102 // Logcat 103 makeLogcatHdf(hdf.createChild("logcat"), bugreport); 104 105 // Monkey Anr 106 if (bugreport.monkeyAnr != null) { 107 makeAnrHdf(hdf, bugreport.monkeyAnr); 108 } 109 110 // VM Traces Last ANR 111 makeVmTracesHdf(hdf.createChild("vmTracesLastAnr"), bugreport.anr, 112 bugreport.vmTracesLastAnr); 113 114 // VM Traces Just Now 115 makeVmTracesHdf(hdf.createChild("vmTracesJustNow"), bugreport.anr, 116 bugreport.vmTracesJustNow); 117 } 118 119 /** 120 * Build the hdf for an Anr. 121 */ makeAnrHdf(Data hdf, Anr anr)122 private void makeAnrHdf(Data hdf, Anr anr) { 123 // CPU Usage 124 final int N = anr.cpuUsages.size(); 125 for (int i=0; i<N; i++) { 126 makeCpuUsageSnapshotHdf(hdf.createChild("monkey.cpuUsage." + i), anr.cpuUsages.get(i)); 127 } 128 129 // Processes 130 makeVmTracesHdf(hdf.createChild("monkey"), anr, anr.vmTraces); 131 } 132 133 /** 134 * Build the hdf for a set of vm traces. Sorts them by likelihood based on the anr. 135 */ makeVmTracesHdf(Data hdf, Anr anr, VmTraces vmTraces)136 private void makeVmTracesHdf(Data hdf, Anr anr, VmTraces vmTraces) { 137 // Process List 138 final Data processesHdf = hdf.createChild("processes"); 139 sortProcesses(anr, vmTraces.processes); 140 final int N = vmTraces.processes.size(); 141 for (int i=0; i<N; i++) { 142 makeProcessSnapshotHdf(processesHdf.createChild(Integer.toString(i)), 143 vmTraces.processes.get(i)); 144 } 145 } 146 147 148 /** 149 * Make the HDF for the triaged panel. 150 * 151 * Any single thread will only appear once in the triage panel, at the topmost 152 * position. 153 */ makeTriageHdf(Data hdf, Bugreport bugreport)154 private void makeTriageHdf(Data hdf, Bugreport bugreport) { 155 final Anr anr = bugreport.anr; 156 157 int N; 158 final HashMap<Integer,HashSet<Integer>> visited = new HashMap<Integer,HashSet<Integer>>(); 159 160 // General information 161 hdf.setValue("triage.processName", anr.processName); 162 hdf.setValue("triage.componentPackage", anr.componentPackage); 163 hdf.setValue("triage.componentClass", anr.componentClass); 164 hdf.setValue("triage.pid", Integer.toString(anr.pid)); 165 hdf.setValue("triage.reason", anr.reason); 166 167 final ProcessSnapshot offendingProcess = anr.vmTraces.getProcess(anr.pid); 168 final ThreadSnapshot offendingThread = anr.vmTraces.getThread(anr.pid, "main"); 169 if (offendingThread != null) { 170 makeThreadSnapshotHdf(hdf.createChild("triage.mainThread"), offendingProcess, 171 offendingThread); 172 173 HashSet<Integer> visitedThreads = new HashSet<Integer>(); 174 visitedThreads.add(offendingThread.tid); 175 visited.put(offendingProcess.pid, visitedThreads); 176 } 177 178 // Deadlocked Processes 179 final ArrayList<ProcessSnapshot> deadlockedProcesses = cloneAndFilter(visited, 180 anr.vmTraces.deadlockedProcesses); 181 sortProcesses(anr, deadlockedProcesses); 182 N = deadlockedProcesses.size(); 183 for (int i=0; i<N; i++) { 184 makeProcessSnapshotHdf(hdf.createChild("triage.deadlockedProcesses." + i), 185 deadlockedProcesses.get(i)); 186 } 187 188 // Interesting Processes 189 final ArrayList<ProcessSnapshot> interestingProcesses = cloneAndFilter(visited, 190 anr.vmTraces.interestingProcesses); 191 sortProcesses(anr, interestingProcesses); 192 N = interestingProcesses.size(); 193 for (int i=0; i<N; i++) { 194 makeProcessSnapshotHdf(hdf.createChild("triage.interestingProcesses." + i), 195 interestingProcesses.get(i)); 196 } 197 } 198 199 /** 200 * Makes a copy of the process and threads, removing ones that have accumulated in the 201 * visited list (probably from previous sections on the current page). 202 * 203 * @see #makeTriageHdf 204 */ cloneAndFilter(HashMap<Integer,HashSet<Integer>> visited, Collection<ProcessSnapshot> list)205 private ArrayList<ProcessSnapshot> cloneAndFilter(HashMap<Integer,HashSet<Integer>> visited, 206 Collection<ProcessSnapshot> list) { 207 final ArrayList<ProcessSnapshot> result = new ArrayList<ProcessSnapshot>(); 208 for (ProcessSnapshot process: list) { 209 final ProcessSnapshot cloneProcess = process.clone(); 210 HashSet<Integer> visitedThreads = visited.get(process.pid); 211 if (visitedThreads == null) { 212 visitedThreads = new HashSet<Integer>(); 213 visited.put(process.pid, visitedThreads); 214 } 215 final int N = cloneProcess.threads.size(); 216 for (int i=N-1; i>=0; i--) { 217 final ThreadSnapshot cloneThread = cloneProcess.threads.get(i); 218 if (visitedThreads.contains(cloneThread.tid)) { 219 cloneProcess.threads.remove(i); 220 } 221 visitedThreads.add(cloneThread.tid); 222 } 223 if (cloneProcess.threads.size() > 0) { 224 result.add(cloneProcess); 225 } 226 } 227 return result; 228 } 229 230 /** 231 * Build the hdf for a CpuUsageSnapshot. 232 */ makeCpuUsageSnapshotHdf(Data hdf, CpuUsageSnapshot snapshot)233 private void makeCpuUsageSnapshotHdf(Data hdf, CpuUsageSnapshot snapshot) { 234 int N; 235 236 N = snapshot.cpuUsage.size(); 237 for (int i=0; i<N; i++) { 238 makeCpuUsageHdf(hdf.createChild(Integer.toString(i)), snapshot.cpuUsage.get(i)); 239 } 240 } 241 242 /** 243 * Build the hdf for a CpuUsage. 244 */ makeCpuUsageHdf(Data hdf, CpuUsage cpuUsage)245 private void makeCpuUsageHdf(Data hdf, CpuUsage cpuUsage) { 246 } 247 248 /** 249 * Build the hdf for a ProcessSnapshot. 250 */ makeProcessSnapshotHdf(Data hdf, ProcessSnapshot process)251 private void makeProcessSnapshotHdf(Data hdf, ProcessSnapshot process) { 252 int N; 253 254 hdf.setValue("panelId", Integer.toString(mNextPanelId++)); 255 256 hdf.setValue("pid", Integer.toString(process.pid)); 257 hdf.setValue("cmdLine", process.cmdLine); 258 hdf.setValue("date", process.date); 259 260 N = process.threads.size(); 261 for (int i=0; i<N; i++) { 262 makeThreadSnapshotHdf(hdf.createChild("threads." + i), process, process.threads.get(i)); 263 } 264 } 265 266 /** 267 * Build the hdf for a ThreadSnapshot. 268 */ makeThreadSnapshotHdf(Data hdf, ProcessSnapshot process, ThreadSnapshot thread)269 private void makeThreadSnapshotHdf(Data hdf, ProcessSnapshot process, ThreadSnapshot thread) { 270 int N, M; 271 272 hdf.setValue("name", thread.name); 273 hdf.setValue("daemon", thread.daemon); 274 hdf.setValue("priority", Integer.toString(thread.priority)); 275 hdf.setValue("tid", Integer.toString(thread.tid)); 276 hdf.setValue("sysTid", Integer.toString(thread.sysTid)); 277 hdf.setValue("vmState", thread.vmState); 278 hdf.setValue("runnable", thread.runnable ? "1" : "0"); 279 hdf.setValue("blocked", thread.blocked ? "1" : "0"); 280 hdf.setValue("interesting", thread.interesting ? "1" : "0"); 281 hdf.setValue("binder", thread.isBinder() ? "1" : "0"); 282 hdf.setValue("outboundBinderCall", buildFunctionName(thread.outboundBinderPackage, 283 thread.outboundBinderClass, thread.outboundBinderMethod)); 284 hdf.setValue("inboundBinderCall", buildFunctionName(thread.inboundBinderPackage, 285 thread.inboundBinderClass, thread.inboundBinderMethod)); 286 287 N = thread.attributeText.size(); 288 for (int i=0; i<N; i++) { 289 hdf.setValue("attributes." + i, thread.attributeText.get(i)); 290 } 291 292 hdf.setValue("heldMutexes", thread.heldMutexes); 293 294 N = thread.frames.size(); 295 for (int i=0; i<N; i++) { 296 makeStackFrameSnapshotHdf(hdf.createChild("frames." + i), process, 297 thread.frames.get(i)); 298 } 299 } 300 301 /** 302 * Combine package, class and method into fully qualified name. 303 */ buildFunctionName(String pkg, String cls, String meth)304 private String buildFunctionName(String pkg, String cls, String meth) { 305 final StringBuilder result = new StringBuilder(); 306 if (pkg != null && pkg.length() > 0) { 307 result.append(pkg); 308 result.append('.'); 309 } 310 if (cls != null && cls.length() > 0) { 311 result.append(cls); 312 result.append('.'); 313 } 314 if (meth != null && meth.length() > 0) { 315 result.append(meth); 316 } 317 return result.toString(); 318 } 319 320 /** 321 * Build the hdf for a StackFrameSnapshot. 322 */ makeStackFrameSnapshotHdf(Data hdf, ProcessSnapshot process, StackFrameSnapshot frame)323 private void makeStackFrameSnapshotHdf(Data hdf, ProcessSnapshot process, 324 StackFrameSnapshot frame) { 325 hdf.setValue("text", frame.text); 326 327 if (frame.frameType == StackFrameSnapshot.FRAME_TYPE_NATIVE) { 328 final NativeStackFrameSnapshot f = (NativeStackFrameSnapshot)frame; 329 hdf.setValue("frameType", "native"); 330 hdf.setValue("symbol", f.symbol); 331 hdf.setValue("library", f.library); 332 hdf.setValue("offset", Integer.toString(f.offset)); 333 334 } else if (frame.frameType == StackFrameSnapshot.FRAME_TYPE_KERNEL) { 335 final KernelStackFrameSnapshot f = (KernelStackFrameSnapshot)frame; 336 hdf.setValue("frameType", "kernel"); 337 hdf.setValue("syscall", f.syscall); 338 hdf.setValue("offset0", Integer.toString(f.offset0)); 339 hdf.setValue("offset1", Integer.toString(f.offset1)); 340 341 } else if (frame.frameType == StackFrameSnapshot.FRAME_TYPE_JAVA) { 342 final JavaStackFrameSnapshot f = (JavaStackFrameSnapshot)frame; 343 hdf.setValue("frameType", "java"); 344 hdf.setValue("packageName", f.packageName); 345 hdf.setValue("className", f.className); 346 hdf.setValue("methodName", f.methodName); 347 hdf.setValue("sourceFile", f.sourceFile); 348 hdf.setValue("sourceLine", Integer.toString(f.sourceLine)); 349 hdf.setValue("language", 350 (f.language == JavaStackFrameSnapshot.LANGUAGE_JAVA ? "java" : "jni")); 351 final int N = f.locks.size(); 352 for (int i=0; i<N; i++) { 353 final LockSnapshot lock = f.locks.get(i); 354 final Data lockHdf = hdf.createChild("locks." + i); 355 if (lock.type == LockSnapshot.LOCKED) { 356 lockHdf.setValue("type", "locked"); 357 } else if (lock.type == LockSnapshot.WAITING) { 358 lockHdf.setValue("type", "waiting"); 359 } else if (lock.type == LockSnapshot.BLOCKED) { 360 lockHdf.setValue("type", "blocked"); 361 } 362 lockHdf.setValue("address", lock.address); 363 lockHdf.setValue("packageName", lock.packageName); 364 lockHdf.setValue("className", lock.className); 365 lockHdf.setValue("threadId", Integer.toString(lock.threadId)); 366 if (lock.threadId >= 0) { 367 final ThreadSnapshot referenced = process.getThread(lock.threadId); 368 if (referenced != null) { 369 lockHdf.setValue("threadName", referenced.name); 370 } 371 } 372 } 373 } else { 374 hdf.setValue("frameType", "other"); 375 } 376 } 377 378 /** 379 * Sort processes so the more interesting ones are at the top. 380 */ sortProcesses(Anr anr, List<ProcessSnapshot> processes)381 private void sortProcesses(Anr anr, List<ProcessSnapshot> processes) { 382 final int N = processes.size(); 383 384 // Last is alphabetical 385 processes.sort(new java.util.Comparator<ProcessSnapshot>() { 386 @Override 387 public int compare(ProcessSnapshot a, ProcessSnapshot b) { 388 return a.cmdLine.compareTo(b.cmdLine); 389 } 390 391 @Override 392 public boolean equals(Object that) { 393 return this == that; 394 } 395 }); 396 397 // Move the ones that start with / to the end. They're typically not interesting 398 for (int i=0, j=0; i<N; i++) { 399 final ProcessSnapshot process = processes.get(j); 400 if (process.cmdLine.length() > 0 && process.cmdLine.charAt(0) == '/') { 401 processes.remove(j); 402 processes.add(process); 403 } else { 404 j++; 405 } 406 } 407 408 // TODO: Next is by CPU % 409 410 // The system process always goes second 411 for (int i=0; i<N; i++) { 412 final ProcessSnapshot process = processes.get(i); 413 if ("system_server".equals(process.cmdLine)) { 414 processes.remove(i); 415 processes.add(0, process); 416 break; 417 } 418 } 419 420 // The blamed process always goes first 421 for (int i=0; i<N; i++) { 422 final ProcessSnapshot process = processes.get(i); 423 if (process.pid == anr.pid) { 424 processes.remove(i); 425 processes.add(0, process); 426 break; 427 } 428 } 429 430 // And do the threads too. 431 sortThreads(processes); 432 } 433 434 /** 435 * Sort threads so the more interesting ones are at the top. 436 */ sortThreads(List<ProcessSnapshot> processes)437 private void sortThreads(List<ProcessSnapshot> processes) { 438 for (ProcessSnapshot process: processes) { 439 final int N = process.threads.size(); 440 441 final ArrayList<ThreadSnapshot> mainThreads = new ArrayList<ThreadSnapshot>(); 442 final ArrayList<ThreadSnapshot> blockedThreads = new ArrayList<ThreadSnapshot>(); 443 final ArrayList<ThreadSnapshot> binderThreads = new ArrayList<ThreadSnapshot>(); 444 final ArrayList<ThreadSnapshot> interestingThreads = new ArrayList<ThreadSnapshot>(); 445 final ArrayList<ThreadSnapshot> otherThreads = new ArrayList<ThreadSnapshot>(); 446 447 int insertAt = 0; // in case there are more than one called "main" 448 for (int i=0; i<N; i++) { 449 final ThreadSnapshot thread = process.threads.get(i); 450 if ("main".equals(thread.name)) { 451 mainThreads.add(thread); 452 } else if (thread.blocked) { 453 blockedThreads.add(thread); 454 } else if (thread.isBinder()) { 455 binderThreads.add(thread); 456 } else if (thread.interesting) { 457 interestingThreads.add(thread); 458 } else { 459 otherThreads.add(thread); 460 } 461 } 462 463 // Within those groups, sort by name. 464 final java.util.Comparator<ThreadSnapshot> cmp 465 = new java.util.Comparator<ThreadSnapshot>() { 466 @Override 467 public int compare(ThreadSnapshot a, ThreadSnapshot b) { 468 return a.name.compareTo(b.name); 469 } 470 471 @Override 472 public boolean equals(Object that) { 473 return this == that; 474 } 475 }; 476 mainThreads.sort(cmp); 477 blockedThreads.sort(cmp); 478 binderThreads.sort(cmp); 479 interestingThreads.sort(cmp); 480 otherThreads.sort(cmp); 481 482 process.threads = mainThreads; 483 process.threads.addAll(blockedThreads); 484 process.threads.addAll(binderThreads); 485 process.threads.addAll(interestingThreads); 486 process.threads.addAll(otherThreads); 487 } 488 } 489 490 /** 491 * Make the hdf for the logcat panel. 492 */ makeLogcatHdf(Data hdf, Bugreport bugreport)493 private void makeLogcatHdf(Data hdf, Bugreport bugreport) { 494 int N; 495 496 final Data interestingHdf = hdf.createChild("interesting"); 497 N = bugreport.interestingLogLines.size(); 498 for (int i=0; i<N; i++) { 499 final LogLine line = bugreport.interestingLogLines.get(i); 500 makeLogcatLineHdf(interestingHdf.createChild(Integer.toString(i)), line); 501 } 502 503 final Logcat logcat = bugreport.logcat; 504 final Data linesHdf = hdf.createChild("lines"); 505 N = logcat.lines.size(); 506 for (int i=0; i<N; i++) { 507 final LogLine line = logcat.lines.get(i); 508 makeLogcatLineHdf(linesHdf.createChild(Integer.toString(i)), line); 509 } 510 } 511 512 /** 513 * Make hdf for a line of logcat. 514 */ makeLogcatLineHdf(Data hdf, LogLine line)515 private void makeLogcatLineHdf(Data hdf, LogLine line) { 516 hdf.setValue("lineno", Integer.toString(line.lineno)); 517 if (line.bufferBegin != null) { 518 hdf.setValue("bufferBegin", line.bufferBegin); 519 hdf.setValue("rawText", line.rawText); 520 } else { 521 hdf.setValue("header", line.header); 522 hdf.setValue("level", Character.toString(line.level)); 523 hdf.setValue("tag", line.tag); 524 hdf.setValue("text", line.text); 525 if (line.regionAnr) { 526 hdf.setValue("regionAnr", "1"); 527 } 528 if (line.regionBugreport) { 529 hdf.setValue("regionBugreport", "1"); 530 } 531 532 String title = "Process: ??"; 533 if (line.process != null) { 534 title = "Process: " + line.process.cmdLine; 535 if (line.thread != null) { 536 title += "\nThread: " + line.thread.name; 537 } 538 } 539 hdf.setValue("title", title); 540 } 541 } 542 } 543