perf_to_tracing.py revision 5f1c94371a64b3196d4be9466099bb892df9b88e
1# Script for converting perf script events into tracing JSON.
2#
3# Generated by perf script -g python
4# Licensed under the terms of the GNU GPL License version 2
5
6import json
7import os
8import sys
9
10from collections import deque
11
12
13# Categorize DSOs by component.
14dso_to_comp = {
15    'libdvm.so': 'Java',
16    'libart.so': 'Java',
17    'libjavacore.so': 'Java',
18    'libandroid_runtime.so': 'Android',
19    'libgui.so': 'Android',
20    'libui.so': 'Android',
21    'libbinder.so': 'Android',
22    'libmemalloc.so': 'Android',
23    'libcrypto.so': 'Android',
24    'libcutils.so':'Android',
25    'libutils.so': 'Android',
26    '[kernel.kallsyms]': 'Kernel',
27    'libc.so': 'Standard Lib',
28    'libstdc++.so': 'Standard Lib',
29    'libm.so':'Standard Lib',
30    'libGLESv2_adreno.so': 'GPU Driver',
31    'libGLESv2_adreno200.so': 'GPU Driver',
32    'libq3dtools_adreno200.so': 'GPU Driver',
33    'libEGL_adreno.so': 'GPU Driver',
34    'libEGL_adreno200.so': 'GPU Driver',
35    'libEGL.so': 'GPU Driver',
36    'libgsl.so': 'GPU Driver',
37    'libGLESv2.so': 'GPU Driver',
38    'libsc-a3xx.so': 'GPU Driver',
39    'libadreno_utils.so': 'GPU Driver',
40    'eglsubAndroid.so': 'GPU Driver',
41    'gralloc.msm8960.so': 'GPU Driver',
42    'libadreno_utils': 'GPU Driver',
43    'libGLES_mali.so': 'GPU Driver',
44    'libchromeview.so': 'Chrome',
45    '[unknown]': '<unknown>',
46    '[UNKNOWN]': '<unknown>',
47}
48
49
50def FilterSymbolModule(module):
51  m = dso_to_comp.get(module, None)
52  if m:
53    return m
54  if module.find('libchrome.') == 0:
55    return 'Chrome'
56  if module.find('dalvik') >= 0 or module.find('@') >= 0:
57    return 'Java'
58  return module
59
60
61def FilterSymbolName(module, orign_module, name):
62  if module == 'Java':
63    return name
64  elif module == 'GPU Driver':
65    return name
66  if name == '':
67    return orign_module + ':unknown'
68  if name[0].isdigit() or name == '(nil)':
69    return orign_module + ':unknown'
70  return name
71
72
73class StackFrameNode:
74  def __init__(self, stack_id, name, category):
75    self.stack_id = stack_id
76    self.parent_id = 0
77    self.children = {}
78    self.category = category
79    self.name = name
80    self.samples = []
81    self.total_weight = 0.0
82    self.have_total_weight = False
83    self.parent = None
84
85  def ToDict(self, out_dict):
86    if self.stack_id:
87      node_dict = {}
88      node_dict['name'] = self.name
89      node_dict['category'] = self.category
90      if self.parent_id:
91        node_dict['parent'] = self.parent_id
92
93      out_dict[self.stack_id] = node_dict
94
95    for child in self.children.values():
96      child.ToDict(out_dict)
97    return out_dict
98
99  def GetTotalWeight(self):
100    if self.have_total_weight:
101      return self.total_weight
102    else:
103      # Sum up self samples weight, and children's total weights.
104      for s in self.samples:
105        self.total_weight += s.weight
106      for c in self.children.values():
107        self.total_weight += c.GetTotalWeight()
108      self.have_total_weight = True
109      return self.total_weight
110
111
112class PerfSample:
113  def __init__(self, stack_id, ts, cpu, tid, weight, samp_type, comm):
114    self.stack_id = stack_id
115    self.ts = ts
116    self.cpu = cpu
117    self.tid = tid
118    self.weight = weight
119    self.type = samp_type
120    self.comm = comm
121
122  def ToDict(self):
123    ret = {}
124    ret['ts'] = self.ts / 1000.0  # Timestamp in microseconds
125    ret['tid'] = self.tid  # Thread id
126    ret['cpu'] = self.cpu  # Sampled CPU
127    ret['weight'] = self.weight  # Sample weight
128    ret['name'] = self.type  # Sample type
129    ret['comm'] = self.comm  # Sample type
130    assert self.stack_id != 0
131    if self.stack_id:
132      ret['sf'] = self.stack_id  # Stack frame id
133    return ret
134
135
136samples = []
137root_chain = StackFrameNode(0, 'root', '[unknown]')
138next_stack_id = 1
139tot_period = 0
140saved_period = 0
141
142
143def process_event(param_dict):
144  global next_stack_id
145  global saved_period
146  global tot_period
147
148  samp_comm = param_dict['comm']
149  samp_tid = param_dict['tid']
150  samp_cpu = param_dict['cpu']
151  samp_ts = param_dict['time']
152  samp_period = param_dict['period']
153  samp_type = param_dict['ev_name']
154  tot_period += samp_period
155
156  # Parse call chain.
157  seen_syms = set()
158  chain = deque()
159  for cs in param_dict['cs']:
160    cs_name = cs[0]
161    cs_dso = os.path.basename(cs[1])
162    cs_category = FilterSymbolModule(cs_dso)
163    cs_name = FilterSymbolName(cs_category, cs_dso, cs_name)
164
165    if cs_category != '<unknown>' or len(chain) == 0:
166      sym = (cs_name, cs_category)
167      if sym in seen_syms:
168        while chain[0] != sym:
169          seen_syms.remove(chain[0])
170          chain.popleft()
171      else:
172        seen_syms.add(sym)
173        chain.appendleft(sym)
174
175      # Discard garbage stacktrace before __pthread_start()
176      if cs_name == '__pthread_start(void*)':
177        break
178
179  # Done reading call chain.  Add to stack frame tree.
180  stack_frame = root_chain
181  for call in chain:
182    if call in stack_frame.children:
183      stack_frame = stack_frame.children[call]
184    else:
185      new_node = StackFrameNode(next_stack_id, call[0], call[1])
186      next_stack_id += 1
187      new_node.parent_id = stack_frame.stack_id
188      stack_frame.children[call] = new_node
189      stack_frame = new_node
190
191  # Save sample.
192  sample = PerfSample(stack_frame.stack_id,
193                  samp_ts,
194                  samp_cpu,
195                  samp_tid,
196                  samp_period,
197                  samp_type,
198                  samp_comm)
199  samples.append(sample)
200  stack_frame.samples.append(sample)
201  saved_period += samp_period
202
203
204def trace_begin():
205  pass
206
207
208def trace_end():
209  # Return siblings of a call tree node.
210  def GetNodeSiblings(node):
211    if not node:
212      return []
213    if not node.parent:
214      return []
215    return node.parent.children.values()
216
217  # Try to reduce misplaced stack leaves by moving them up into sibling nodes.
218  def FixCallTree(node, parent):
219    # Get siblings of node's parent.
220    node.parent = parent
221    parent_siblings = GetNodeSiblings(parent)
222
223    # If parent's sibling has same node name, has no children and small weight,
224    # transplant sibling's samples into the current node.
225    for sibling in parent_siblings:
226      if sibling.name == node.name and \
227          len(sibling.children) == 0 and \
228          sibling.GetTotalWeight() <= node.GetTotalWeight() * 0.15:
229
230        # Transplant samples from sibling to current node.
231        for samp in sibling.samples:
232          samp.stack_id = node.stack_id
233          node.samples.append(samp)
234        sibling.samples = []
235        break
236
237    # Recurse child nodes.
238    for c in node.children.values():
239      FixCallTree(c, node)
240
241  FixCallTree(root_chain, None)
242
243  trace_dict = {}
244  trace_dict['samples'] = [s.ToDict() for s in samples]
245  trace_dict['stackFrames'] = root_chain.ToDict({})
246  trace_dict['traceEvents'] = []
247
248  json.dump(trace_dict, sys.stdout, indent=1)
249