Index: tools/android/adb_profile_chrome/perf_to_tracing.py |
diff --git a/tools/android/adb_profile_chrome/perf_to_tracing.py b/tools/android/adb_profile_chrome/perf_to_tracing.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..36bf058f41e0688e1aaf3a956779bbbd54420109 |
--- /dev/null |
+++ b/tools/android/adb_profile_chrome/perf_to_tracing.py |
@@ -0,0 +1,249 @@ |
+# Script for converting perf script events into tracing JSON. |
+# |
+# Generated by perf script -g python |
+# Licensed under the terms of the GNU GPL License version 2 |
vmiura
2014/07/17 22:51:17
Should we replace this with Chrome's license?
Sami
2014/07/18 11:19:12
I'm not sure, but I think we need to keep the lice
|
+ |
+import json |
+import os |
+import sys |
+ |
+from collections import deque |
+ |
+ |
+# Categorize DSOs by component. |
+dso_to_comp = { |
+ 'libdvm.so': 'Java', |
+ 'libart.so': 'Java', |
+ 'libjavacore.so': 'Java', |
+ 'libandroid_runtime.so': 'Android', |
+ 'libgui.so': 'Android', |
+ 'libui.so': 'Android', |
+ 'libbinder.so': 'Android', |
+ 'libmemalloc.so': 'Android', |
+ 'libcrypto.so': 'Android', |
+ 'libcutils.so':'Android', |
+ 'libutils.so': 'Android', |
+ '[kernel.kallsyms]': 'Kernel', |
+ 'libc.so': 'Standard Lib', |
+ 'libstdc++.so': 'Standard Lib', |
+ 'libm.so':'Standard Lib', |
+ 'libGLESv2_adreno.so': 'GPU Driver', |
+ 'libGLESv2_adreno200.so': 'GPU Driver', |
+ 'libq3dtools_adreno200.so': 'GPU Driver', |
+ 'libEGL_adreno.so': 'GPU Driver', |
+ 'libEGL_adreno200.so': 'GPU Driver', |
+ 'libEGL.so': 'GPU Driver', |
+ 'libgsl.so': 'GPU Driver', |
+ 'libGLESv2.so': 'GPU Driver', |
+ 'libsc-a3xx.so': 'GPU Driver', |
+ 'libadreno_utils.so': 'GPU Driver', |
+ 'eglsubAndroid.so': 'GPU Driver', |
+ 'gralloc.msm8960.so': 'GPU Driver', |
+ 'libadreno_utils': 'GPU Driver', |
+ 'libGLES_mali.so': 'GPU Driver', |
+ 'libchromeview.so': 'Chrome', |
+ '[unknown]': '<unknown>', |
+ '[UNKNOWN]': '<unknown>', |
+} |
+ |
+ |
+def FilterSymbolModule(module): |
+ m = dso_to_comp.get(module, None) |
+ if m: |
+ return m |
+ if module.find('libchrome.') == 0: |
+ return 'Chrome' |
+ if module.find('dalvik') >= 0 or module.find('@') >= 0: |
+ return 'Java' |
+ return module |
+ |
+ |
+def FilterSymbolName(module, orign_module, name): |
+ if module == 'Java': |
+ return name #orign_module |
vmiura
2014/07/17 22:51:17
nit: remove comment
Sami
2014/07/18 11:19:13
Done.
|
+ elif module == 'GPU Driver': |
+ return name # orign_module |
vmiura
2014/07/17 22:51:17
nit: remove comment
Sami
2014/07/18 11:19:13
Done.
|
+ if name == '': |
+ return orign_module + ':unknown' |
+ if name[0].isdigit() or name == '(nil)': |
+ return orign_module + ':unknown' |
+ return name |
+ |
+ |
+class StackFrameNode: |
+ def __init__(self, stack_id, name, category): |
+ self.stack_id = stack_id |
+ self.parent_id = 0 |
+ self.children = {} |
+ self.category = category |
+ self.name = name |
+ self.samples = [] |
+ self.total_weight = 0.0 |
+ self.have_total_weight = False |
+ self.parent = None |
+ |
+ def ToDict(self, out_dict): |
+ if self.stack_id: |
+ node_dict = {} |
+ node_dict['name'] = self.name |
+ node_dict['category'] = self.category |
+ if self.parent_id: |
+ node_dict['parent'] = self.parent_id |
+ |
+ out_dict[self.stack_id] = node_dict |
+ |
+ for child in self.children.values(): |
+ child.ToDict(out_dict) |
+ return out_dict |
+ |
+ def GetTotalWeight(self): |
+ if self.have_total_weight: |
+ return self.total_weight |
+ else: |
+ # Sum up self samples weight, and children's total weights. |
+ for s in self.samples: |
+ self.total_weight += s.weight |
+ for c in self.children.values(): |
+ self.total_weight += c.GetTotalWeight() |
+ self.have_total_weight = True |
+ return self.total_weight |
+ |
+ |
+class PerfSample: |
+ def __init__(self, stack_id, ts, cpu, tid, weight, samp_type, comm): |
+ self.stack_id = stack_id |
+ self.ts = ts |
+ self.cpu = cpu |
+ self.tid = tid |
+ self.weight = weight |
+ self.type = samp_type |
+ self.comm = comm |
+ |
+ def ToDict(self): |
+ ret = {} |
+ ret['ts'] = self.ts / 1000.0 # Timestamp in microseconds |
+ ret['tid'] = self.tid # Thread id |
+ ret['cpu'] = self.cpu # Sampled CPU |
+ ret['weight'] = self.weight # Sample weight |
+ ret['name'] = self.type # Sample type |
+ ret['comm'] = self.comm # Sample type |
+ assert self.stack_id != 0 |
+ if self.stack_id: |
+ ret['sf'] = self.stack_id # Stack frame id |
+ return ret |
+ |
+ |
+samples = [] |
+root_chain = StackFrameNode(0, 'root', '[unknown]') |
+next_stack_id = 1 |
+tot_period = 0 |
+saved_period = 0 |
+ |
+ |
+def process_event(param_dict): |
+ global next_stack_id |
+ global saved_period |
+ global tot_period |
+ |
+ samp_comm = param_dict['comm'] |
+ #samp_pid = param_dict['pid'] |
vmiura
2014/07/17 22:51:17
nit: Remove commented line
Sami
2014/07/18 11:19:13
Done.
|
+ samp_tid = param_dict['tid'] |
+ samp_cpu = param_dict['cpu'] |
+ samp_ts = param_dict['time'] |
+ samp_period = param_dict['period'] |
+ samp_type = param_dict['ev_name'] |
+ tot_period += samp_period |
+ |
+ # Parse call chain. |
+ seen_syms = set() |
+ chain = deque() |
+ for cs in param_dict['cs']: |
+ cs_name = cs[0] |
+ cs_dso = os.path.basename(cs[1]) |
+ cs_category = FilterSymbolModule(cs_dso) |
+ cs_name = FilterSymbolName(cs_category, cs_dso, cs_name) |
+ |
+ if cs_category != '<unknown>' or len(chain) == 0: |
+ sym = (cs_name, cs_category) |
+ if sym in seen_syms: |
+ while chain[0] != sym: |
+ seen_syms.remove(chain[0]) |
+ chain.popleft() |
+ else: |
+ seen_syms.add(sym) |
+ chain.appendleft(sym) |
+ |
+ # Discard garbage stacktrace before __pthread_start() |
+ if cs_name == '__pthread_start(void*)': |
+ break |
+ |
+ # Done reading call chain. Add to stack frame tree. |
+ stack_frame = root_chain |
+ for call in chain: |
+ if call in stack_frame.children: |
+ stack_frame = stack_frame.children[call] |
+ else: |
+ new_node = StackFrameNode(next_stack_id, call[0], call[1]) |
+ next_stack_id += 1 |
+ new_node.parent_id = stack_frame.stack_id |
+ stack_frame.children[call] = new_node |
+ stack_frame = new_node |
+ |
+ # Save sample. |
+ sample = PerfSample(stack_frame.stack_id, |
+ samp_ts, |
+ samp_cpu, |
+ samp_tid, |
+ samp_period, |
+ samp_type, |
+ samp_comm) |
+ samples.append(sample) |
+ stack_frame.samples.append(sample) |
+ saved_period += samp_period |
+ |
+ |
+def trace_begin(): |
+ pass |
+ |
+ |
+def trace_end(): |
+ # Return siblings of a call tree node. |
+ def GetNodeSiblings(node): |
+ if not node: |
+ return [] |
+ if not node.parent: |
+ return [] |
+ return node.parent.children.values() |
+ |
+ # Try to reduce misplaced stack leaves by mobing them up into sibling nodes. |
vmiura
2014/07/17 22:51:17
nit: mobing -> moving
Sami
2014/07/18 11:19:13
Done.
|
+ def FixCallTree(node, parent): |
+ # Get siblings of node's parent. |
+ node.parent = parent |
+ parent_siblings = GetNodeSiblings(parent) |
+ |
+ # If parent's sibling has same node name, has no children and small weight, |
+ # transplant sibling's samples into the current node. |
+ for sibling in parent_siblings: |
+ if sibling.name == node.name and \ |
+ len(sibling.children) == 0 and \ |
+ sibling.GetTotalWeight() <= node.GetTotalWeight() * 0.15: |
+ |
+ # Transplant samples from sibling to current node. |
+ for samp in sibling.samples: |
+ samp.stack_id = node.stack_id |
+ node.samples.append(samp) |
+ sibling.samples = [] |
+ break |
+ |
+ # Recurse child nodes. |
+ for c in node.children.values(): |
+ FixCallTree(c, node) |
+ |
+ FixCallTree(root_chain, None) |
+ |
+ trace_dict = {} |
+ trace_dict['samples'] = [s.ToDict() for s in samples] |
+ trace_dict['stackFrames'] = root_chain.ToDict({}) |
+ trace_dict['traceEvents'] = [] |
+ |
+ json.dump(trace_dict, sys.stdout, indent=1) |