Home | History | Annotate | Download | only in html
      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 
     58     public Renderer() {
     59     }
     60 
     61     /**
     62      * Render the Bugreport into the html file.
     63      */
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    245     private void makeCpuUsageHdf(Data hdf, CpuUsage cpuUsage) {
    246     }
    247 
    248     /**
    249      * Build the hdf for a ProcessSnapshot.
    250      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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