Index: tools/telemetry/telemetry/core/platform/profiler/perf_vis/perf_vis.py |
diff --git a/tools/telemetry/telemetry/core/platform/profiler/perf_vis/perf_vis.py b/tools/telemetry/telemetry/core/platform/profiler/perf_vis/perf_vis.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..e060640b6e7092bc610709b6d943269c000a60aa |
--- /dev/null |
+++ b/tools/telemetry/telemetry/core/platform/profiler/perf_vis/perf_vis.py |
@@ -0,0 +1,264 @@ |
+#!/usr/bin/python |
+import sys |
+import os |
+import json |
+from datetime import date |
+ |
+from collections import deque |
+from optparse import OptionParser |
+ |
+parser = OptionParser() |
+parser.add_option("-f", "--frames", dest="nframes", default=3600, |
+ type="int", help="Number of frames in input") |
+parser.add_option("-s", "--start", dest="start_ts", default=0.0, |
+ type="float", help="Start timestamp") |
+parser.add_option("-e", "--end", dest="end_ts", default=1000000000000.0, |
+ type="float", help="End timestamp") |
+parser.add_option("-m", "--meta", dest="meta", default=None, |
+ type="string", help="Trace metadata") |
+parser.add_option("-o", dest="output", default=None, |
+ type="string", help="Output filename") |
+parser.add_option("-c", "--cpu-freq", dest="cpu_freq", default=1574400000, |
+ type="int", help="CPU cycles per second") |
+ |
+def Process(options, args): |
+ thread_names = {} |
+ |
+ if options.meta: |
+ with open(options.meta) as meta_file: |
+ meta = json.load(meta_file) |
+ |
+ options.nframes = meta.get('num_frames', 1) |
+ options.start_ts = meta.get('start_ts', 0.0) |
+ options.end_ts = meta.get('end_ts', 1000000000000.0) |
+ thread_names = meta.get('tid', {}) |
+ |
+ class Symbol: |
+ def __init__(self, name): |
+ self.name = name |
+ |
+ class CallTreeNode: |
+ stack_id = 0 |
+ def __init__(self, name): |
+ CallTreeNode.stack_id += 1 |
+ self.stack_id = CallTreeNode.stack_id |
+ self.parent_id = 0 |
+ self.parent = None |
+ self.name = name |
+ self.self_time = 0.0 |
+ self.tot_time = 0.0 |
+ self.have_tot_time = False |
+ self.children = {} |
+ |
+ def getTotTime(self): |
+ if self.have_tot_time: |
+ return self.tot_time |
+ else: |
+ self.tot_time = self.self_time |
+ for c in self.children.values(): |
+ self.tot_time += c.getTotTime() |
+ self.have_tot_time = True |
+ return self.tot_time |
+ |
+ class Thread: |
+ def __init__(self, comm, tid): |
+ self.comm = comm |
+ self.tid = tid |
+ self.symbols = {} |
+ self.name = "???" |
+ |
+ self.call_tree = CallTreeNode('root') |
+ self.sf_map = {} |
+ self.sf_map[0] = self.call_tree |
+ |
+ def getCtree(self, sf_id, name): |
+ if sf_id in self.sf_map: |
+ return (self.sf_map[sf_id], False) |
+ else: |
+ self.getSymbol(name) # tag symbol |
+ newNode = CallTreeNode(name) |
+ self.sf_map[sf_id] = newNode |
+ return (newNode, True) |
+ |
+ def getSymbol(self, name): |
+ if name in self.symbols: |
+ sym = self.symbols[name] |
+ else: |
+ sym = Symbol(name) |
+ self.symbols[name] = sym |
+ return sym |
+ |
+ # Derrive thread names based on existing symbols. |
+ thread_maps = [ ("Browser Main", ["cc::SingleThreadProxy::DoCommit(scoped_ptr<cc::ResourceUpdateQueue, base::DefaultDeleter<cc::ResourceUpdateQueue> >)@Chrome"]), |
+ ("Renderer Main", ["blink::WebViewImpl::layout()@Chrome"]), |
+ ("Browser InProcGpuThread", ["gpu::GpuScheduler::PutChanged()@Chrome"]), |
+ ("Browser AsyncTransferThread", ["gpu::(anonymous namespace)::TransferStateInternal::PerformAsyncTexImage2D(gpu::AsyncTexImage2DParams, gpu::AsyncMemoryParams, gpu::ScopedSafeSharedMemory*, scoped_refptr<gpu::AsyncPixelTransferUploadStats>)", "gpu::(anonymous namespace)::TransferStateInternal::PerformAsyncTexSubImage2D(gpu::AsyncTexSubImage2DParams, gpu::AsyncMemoryParams, gpu::ScopedSafeSharedMemory*, scoped_refptr<gpu::AsyncPixelTransferUploadStats>)@Chrome", "gpu::(anonymous namespace)::TransferStateInternal::PerformAsyncTexSubImage2D(gpu::AsyncTexSubImage2DParams, gpu::AsyncMemoryParams, scoped_refptr<gpu::AsyncPixelTransferUploadStats>)@Chrome"]), |
+ ("Renderer Compositor", ["cc::Scheduler::NotifyReadyToCommit()@Chrome"]), |
+ ("Browser IOThread", ["content::BrowserThreadImpl::IOThreadRun(base::MessageLoop*)@Chrome"]), |
+ ("Browser FileThread", ["content::BrowserThreadImpl::FileThreadRun(base::MessageLoop*)@Chrome"]), |
+ ("Browser DBThread", ["content::BrowserThreadImpl::DBThreadRun(base::MessageLoop*)@Chrome"]), |
+ ("Browser ChildIOThread", ["content::GpuChannelMessageFilter::OnMessageReceived(IPC::Message const&)@Chrome"]), |
+ ("Renderer ChildIOThread", ["IPC::SyncMessageFilter::SendOnIOThread(IPC::Message*)@Chrome"]), |
+ ("Renderer RasterWorker", ["cc::Picture::Raster(SkCanvas*, SkDrawPictureCallback*, cc::Region const&, float)@Chrome", "cc::(anonymous namespace)::RasterFinishedTaskImpl::RunOnWorkerThread()@Chrome"]), |
+ ("DVM Compiler", ["dvmCompilerAssembleLIR(CompilationUnit*, JitTranslationInfo*)@Java"]), |
+ ("DVM GC", ["dvmHeapBitmapScanWalk(HeapBitmap*, void (*)(Object*, void*, void*), void*)@Java"]), |
+ ("Adreno Driver", ["adreno_drawctxt_wait@Kernel"]), |
+ ] |
+ |
+ def filterSymbolModule(module): |
+ return module |
+ |
+ def filterSymbolName(module, orign_module, name): |
+ return name |
+ |
+ def outputPefVis(options, args): |
+ time_scale = 1000.0 / options.nframes / options.cpu_freq |
+ |
+ threads = {} |
+ |
+ def getThread(comm, tid): |
+ if tid in threads: |
+ thread = threads[tid] |
+ else: |
+ thread = Thread(comm, tid) |
+ threads[tid] = thread |
+ return thread |
+ |
+ fp = open(args[0]) |
+ trace = json.load(fp) |
+ fp.close() |
+ |
+ # Process samples. |
+ stackFrames = trace['stackFrames'] |
+ samples = trace['samples'] |
+ |
+ for s in samples: |
+ samp_ts = s['ts'] / 1000.0 |
+ if samp_ts < options.start_ts or samp_ts > options.end_ts: |
+ continue |
+ samp_time = float(s['weight']) * time_scale |
+ |
+ curr_thread = getThread(s['comm'], s['tid']) |
+ sf_id = s.get('sf') |
+ chain = deque() |
+ while sf_id != 0: |
+ sf = stackFrames[str(sf_id)] |
+ chain.appendleft((sf['name'], sf['category'])) |
+ sf_id = sf.get('parent', 0) |
+ |
+ if len(chain) >= 1: |
+ # Add an entry with the same category, and name = base_category. |
+ c = chain[0] |
+ chain.appendleft((c[1], c[1])) |
+ |
+ # Add to call tree. |
+ ctree_node = curr_thread.call_tree |
+ for c in chain: |
+ chain_name = c |
+ if chain_name in ctree_node.children: |
+ ctree_node = ctree_node.children[chain_name] |
+ else: |
+ new_node = CallTreeNode(chain_name) |
+ new_node.parent_id = ctree_node.stack_id |
+ ctree_node.children[chain_name] = new_node |
+ ctree_node = new_node |
+ curr_thread.getSymbol(chain_name[0] + '@' + chain_name[1]) # tag symbol |
+ ctree_node.self_time += samp_time |
+ |
+ # Map thread names. |
+ for t in threads.values(): |
+ if str(t.tid) in thread_names: |
+ t.name = thread_names[str(t.tid)] |
+ else: |
+ for m in thread_maps: |
+ match = False |
+ for s in m[1]: |
+ if s in t.symbols: |
+ match = True |
+ break |
+ if match: |
+ t.name = m[0] |
+ break |
+ |
+ def jsonCallTree(node, is_root = False): |
+ ret = {} |
+ name_comp = node.name |
+ ret['name'] = name_comp[0] |
+ if len(name_comp) > 1: |
+ ret['comp'] = name_comp[1] |
+ |
+ if len(node.children) > 0 or is_root: |
+ ret['children'] = [] |
+ for c in sorted(node.children.values(), key=lambda c: -c.getTotTime()): |
+ ret['children'].append(jsonCallTree(c)) |
+ if node.self_time > 0.0: |
+ ret['children'].append({'name': '<self>', 'comp': ret['comp'], 'size': node.self_time}) |
+ else: |
+ ret['size'] = node.self_time |
+ if is_root: |
+ return ret['children'] |
+ else: |
+ return ret |
+ |
+ threads_json = {'name': '<All Threads>', 'comp': 'root', 'children':[]} |
+ sorted_threads = sorted(threads.values(), key=lambda thread: -thread.call_tree.getTotTime()) |
+ for t in sorted_threads: |
+ if ('chrome' in t.comm) or ('ntent_shell' in t.comm) or ('dboxed' in t.comm): |
+ tjson = {} |
+ tjson['name'] = '<' + t.comm + ':' + t.name + '>' |
+ tjson['children'] = jsonCallTree(t.call_tree, True) |
+ tjson['comp'] = 'Thread' |
+ threads_json['children'].append(tjson) |
+ |
+ out_path = options.output |
+ if not out_path: |
+ out_base = os.path.basename(args[0]) |
+ today = date.today() |
+ out_path = out_base + '_%02d%02d%02d.html' % (today.day, today.month, today.year) |
+ |
+ vis_path = os.path.abspath(os.path.dirname(__file__)) |
+ # Load template |
+ with open(vis_path + '/perf-vis-template.html', 'r') as template_file: |
+ html_temp = template_file.read() |
+ |
+ # Add title |
+ html_temp = html_temp.replace('<page-title>', os.path.basename(out_path)) |
+ |
+ # Add perf data json |
+ html_temp = html_temp.replace('<data_json>', json.dumps(threads_json, indent=0)) |
+ |
+ # Add jquery-1.11.0.min.js |
+ with open(vis_path + '/jquery-1.11.0.min.js', 'r') as js: |
+ html_temp = html_temp.replace('<jquery-1.11.0.min.js>', js.read()) |
+ |
+ # Add sammy-latest.min.js script |
+ with open(vis_path + '/sammy-latest.min.js', 'r') as js: |
+ html_temp = html_temp.replace('<sammy-latest.min.js>', js.read()) |
+ |
+ # Add d3.v3.min.js script |
+ with open(vis_path + '/d3.v3.min.js', 'r') as js: |
+ html_temp = html_temp.replace('<d3.v3.min.js>', js.read()) |
+ |
+ # Add jquery.dataTables.min.js |
+ with open(vis_path + '/jquery.dataTables.min.js', 'r') as js: |
+ html_temp = html_temp.replace('<jquery.dataTables.min.js>', js.read()) |
+ |
+ # Add jquery.dataTables.min.css |
+ with open(vis_path + '/jquery.dataTables.css', 'r') as css: |
+ html_temp = html_temp.replace('<jquery.dataTables.css>', css.read()) |
+ |
+ # Add perf-vis.js script |
+ with open(vis_path + '/perf-vis.js', 'r') as js: |
+ html_temp = html_temp.replace('<perf-vis.js>', js.read()) |
+ |
+ # Write result |
+ print '### perf-vis output:', os.path.join(os.getcwd(), out_path) |
+ with open(out_path, 'w') as html_file: |
+ html_file.write(html_temp) |
+ |
+ |
+ outputPefVis(options, args) |
+ |
+if __name__ == "__main__": |
+ (options, arguments) = parser.parse_args() |
+ Process(options, arguments) |