| # 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 |
| |
| 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 |
| elif module == 'GPU Driver': |
| return name |
| 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_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 moving them up into sibling nodes. |
| 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) |