profile_analyzer.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 # Copyright 2015 gRPC authors.
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 import argparse
17 import collections
18 import hashlib
19 import json
20 import math
21 import sys
22 import time
23 
24 from six.moves import zip
25 import tabulate
26 
27 SELF_TIME = object()
28 TIME_FROM_SCOPE_START = object()
29 TIME_TO_SCOPE_END = object()
30 TIME_FROM_STACK_START = object()
31 TIME_TO_STACK_END = object()
32 TIME_FROM_LAST_IMPORTANT = object()
33 
34 argp = argparse.ArgumentParser(
35  description='Process output of basic_prof builds')
36 argp.add_argument('--source', default='latency_trace.txt', type=str)
37 argp.add_argument('--fmt', choices=tabulate.tabulate_formats, default='simple')
38 argp.add_argument('--out', default='-', type=str)
39 args = argp.parse_args()
40 
41 
42 class LineItem(object):
43 
44  def __init__(self, line, indent):
45  self.tag = line['tag']
46  self.indent = indent
47  self.start_time = line['t']
48  self.end_time = None
49  self.important = line['imp']
50  self.filename = line['file']
51  self.fileline = line['line']
52  self.times = {}
53 
54 
55 class ScopeBuilder(object):
56 
57  def __init__(self, call_stack_builder, line):
58  self.call_stack_builder = call_stack_builder
59  self.indent = len(call_stack_builder.stk)
60  self.top_line = LineItem(line, self.indent)
61  call_stack_builder.lines.append(self.top_line)
62  self.first_child_pos = len(call_stack_builder.lines)
63 
64  def mark(self, line):
65  line_item = LineItem(line, self.indent + 1)
66  line_item.end_time = line_item.start_time
67  self.call_stack_builder.lines.append(line_item)
68 
69  def finish(self, line):
70  assert line['tag'] == self.top_line.tag, (
71  'expected %s, got %s; thread=%s; t0=%f t1=%f' %
72  (self.top_line.tag, line['tag'], line['thd'],
73  self.top_line.start_time, line['t']))
74  final_time_stamp = line['t']
75  assert self.top_line.end_time is None
76  self.top_line.end_time = final_time_stamp
77  self.top_line.important = self.top_line.important or line['imp']
78  assert SELF_TIME not in self.top_line.times
79  self.top_line.times[
80  SELF_TIME] = final_time_stamp - self.top_line.start_time
81  for line in self.call_stack_builder.lines[self.first_child_pos:]:
82  if TIME_FROM_SCOPE_START not in line.times:
83  line.times[
84  TIME_FROM_SCOPE_START] = line.start_time - self.top_line.start_time
85  line.times[TIME_TO_SCOPE_END] = final_time_stamp - line.end_time
86 
87 
88 class CallStackBuilder(object):
89 
90  def __init__(self):
91  self.stk = []
92  self.signature = hashlib.md5()
93  self.lines = []
94 
95  def finish(self):
96  start_time = self.lines[0].start_time
97  end_time = self.lines[0].end_time
98  self.signature = self.signature.hexdigest()
99  last_important = start_time
100  for line in self.lines:
101  line.times[TIME_FROM_STACK_START] = line.start_time - start_time
102  line.times[TIME_TO_STACK_END] = end_time - line.end_time
103  line.times[
104  TIME_FROM_LAST_IMPORTANT] = line.start_time - last_important
105  if line.important:
106  last_important = line.end_time
107  last_important = end_time
108 
109  def add(self, line):
110  line_type = line['type']
111  self.signature.update(line_type.encode('UTF-8'))
112  self.signature.update(line['tag'].encode('UTF-8'))
113  if line_type == '{':
114  self.stk.append(ScopeBuilder(self, line))
115  return False
116  elif line_type == '}':
117  assert self.stk, (
118  'expected non-empty stack for closing %s; thread=%s; t=%f' %
119  (line['tag'], line['thd'], line['t']))
120  self.stk.pop().finish(line)
121  if not self.stk:
122  self.finish()
123  return True
124  return False
125  elif line_type == '.' or line_type == '!':
126  if self.stk:
127  self.stk[-1].mark(line)
128  return False
129  else:
130  raise Exception('Unknown line type: \'%s\'' % line_type)
131 
132 
133 class CallStack(object):
134 
135  def __init__(self, initial_call_stack_builder):
136  self.count = 1
137  self.signature = initial_call_stack_builder.signature
138  self.lines = initial_call_stack_builder.lines
139  for line in self.lines:
140  for key, val in list(line.times.items()):
141  line.times[key] = [val]
142 
143  def add(self, call_stack_builder):
144  assert self.signature == call_stack_builder.signature
145  self.count += 1
146  assert len(self.lines) == len(call_stack_builder.lines)
147  for lsum, line in zip(self.lines, call_stack_builder.lines):
148  assert lsum.tag == line.tag
149  assert list(lsum.times.keys()) == list(line.times.keys())
150  for k, lst in list(lsum.times.items()):
151  lst.append(line.times[k])
152 
153  def finish(self):
154  for line in self.lines:
155  for lst in list(line.times.values()):
156  lst.sort()
157 
158 
159 builder = collections.defaultdict(CallStackBuilder)
160 call_stacks = collections.defaultdict(CallStack)
161 
162 lines = 0
163 start = time.time()
164 with open(args.source) as f:
165  for line in f:
166  lines += 1
167  inf = json.loads(line)
168  thd = inf['thd']
169  cs = builder[thd]
170  if cs.add(inf):
171  if cs.signature in call_stacks:
172  call_stacks[cs.signature].add(cs)
173  else:
174  call_stacks[cs.signature] = CallStack(cs)
175  del builder[thd]
176 time_taken = time.time() - start
177 
178 call_stacks = sorted(list(call_stacks.values()),
179  key=lambda cs: cs.count,
180  reverse=True)
181 total_stacks = 0
182 for cs in call_stacks:
183  total_stacks += cs.count
184  cs.finish()
185 
186 
187 def percentile(N, percent, key=lambda x: x):
188  """
189  Find the percentile of an already sorted list of values.
190 
191  @parameter N - is a list of values. MUST be already sorted.
192  @parameter percent - a float value from [0.0,1.0].
193  @parameter key - optional key function to compute value from each element of N.
194 
195  @return - the percentile of the values
196  """
197  if not N:
198  return None
199  float_idx = (len(N) - 1) * percent
200  idx = int(float_idx)
201  result = key(N[idx])
202  if idx < len(N) - 1:
203  # interpolate with the next element's value
204  result += (float_idx - idx) * (key(N[idx + 1]) - key(N[idx]))
205  return result
206 
207 
208 def tidy_tag(tag):
209  if tag[0:10] == 'GRPC_PTAG_':
210  return tag[10:]
211  return tag
212 
213 
214 def time_string(values):
215  num_values = len(values)
216  return '%.1f/%.1f/%.1f' % (1e6 * percentile(values, 0.5), 1e6 * percentile(
217  values, 0.9), 1e6 * percentile(values, 0.99))
218 
219 
220 def time_format(idx):
221 
222  def ent(line, idx=idx):
223  if idx in line.times:
224  return time_string(line.times[idx])
225  return ''
226 
227  return ent
228 
229 
230 BANNER = {'simple': 'Count: %(count)d', 'html': '<h1>Count: %(count)d</h1>'}
231 
232 FORMAT = [
233  ('TAG', lambda line: '..' * line.indent + tidy_tag(line.tag)),
234  ('LOC', lambda line: '%s:%d' %
235  (line.filename[line.filename.rfind('/') + 1:], line.fileline)),
236  ('IMP', lambda line: '*' if line.important else ''),
237  ('FROM_IMP', time_format(TIME_FROM_LAST_IMPORTANT)),
238  ('FROM_STACK_START', time_format(TIME_FROM_STACK_START)),
239  ('SELF', time_format(SELF_TIME)),
240  ('TO_STACK_END', time_format(TIME_TO_STACK_END)),
241  ('FROM_SCOPE_START', time_format(TIME_FROM_SCOPE_START)),
242  ('SELF', time_format(SELF_TIME)),
243  ('TO_SCOPE_END', time_format(TIME_TO_SCOPE_END)),
244 ]
245 
246 out = sys.stdout
247 if args.out != '-':
248  out = open(args.out, 'w')
249 
250 if args.fmt == 'html':
251  out.write('<html>')
252  out.write('<head>')
253  out.write('<title>Profile Report</title>')
254  out.write('</head>')
255 
256 accounted_for = 0
257 for cs in call_stacks:
258  out.write('\n')
259  if args.fmt in BANNER:
260  out.write(BANNER[args.fmt] % {
261  'count': cs.count,
262  })
263  header, _ = list(zip(*FORMAT))
264  table = []
265  for line in cs.lines:
266  fields = []
267  for _, fn in FORMAT:
268  fields.append(fn(line))
269  table.append(fields)
270  out.write(tabulate.tabulate(table, header, tablefmt=args.fmt))
271  accounted_for += cs.count
272  if accounted_for > .99 * total_stacks:
273  break
274 
275 if args.fmt == 'html':
276  print('</html>')
profile_analyzer.ScopeBuilder.call_stack_builder
call_stack_builder
Definition: profile_analyzer.py:58
profile_analyzer.CallStack.signature
signature
Definition: profile_analyzer.py:137
profile_analyzer.ScopeBuilder.__init__
def __init__(self, call_stack_builder, line)
Definition: profile_analyzer.py:57
profile_analyzer.LineItem.filename
filename
Definition: profile_analyzer.py:50
profile_analyzer.ScopeBuilder.indent
indent
Definition: profile_analyzer.py:59
profile_analyzer.CallStack.lines
lines
Definition: profile_analyzer.py:138
profile_analyzer.LineItem.times
times
Definition: profile_analyzer.py:52
profile_analyzer.LineItem.tag
tag
Definition: profile_analyzer.py:45
grpc._common.encode
def encode(s)
Definition: grpc/_common.py:68
profile_analyzer.percentile
def percentile(N, percent, key=lambda x:x)
Definition: profile_analyzer.py:187
profile_analyzer.CallStackBuilder.add
def add(self, line)
Definition: profile_analyzer.py:109
profile_analyzer.CallStack.add
def add(self, call_stack_builder)
Definition: profile_analyzer.py:143
xds_interop_client.int
int
Definition: xds_interop_client.py:113
generate-asm-lcov.fn
fn
Definition: generate-asm-lcov.py:146
profile_analyzer.CallStackBuilder.lines
lines
Definition: profile_analyzer.py:93
profile_analyzer.ScopeBuilder.top_line
top_line
Definition: profile_analyzer.py:60
profile_analyzer.CallStackBuilder
Definition: profile_analyzer.py:88
profile_analyzer.time_format
def time_format(idx)
Definition: profile_analyzer.py:220
profile_analyzer.LineItem.indent
indent
Definition: profile_analyzer.py:46
profile_analyzer.tidy_tag
def tidy_tag(tag)
Definition: profile_analyzer.py:208
profile_analyzer.CallStack.finish
def finish(self)
Definition: profile_analyzer.py:153
add
static void add(const char *beg, const char *end, char ***ss, size_t *ns)
Definition: debug/trace.cc:96
profile_analyzer.LineItem.start_time
start_time
Definition: profile_analyzer.py:47
profile_analyzer.CallStack.__init__
def __init__(self, initial_call_stack_builder)
Definition: profile_analyzer.py:135
profile_analyzer.LineItem
Definition: profile_analyzer.py:42
profile_analyzer.CallStackBuilder.stk
stk
Definition: profile_analyzer.py:91
key
const char * key
Definition: hpack_parser_table.cc:164
profile_analyzer.LineItem.end_time
end_time
Definition: profile_analyzer.py:48
open
#define open
Definition: test-fs.c:46
profile_analyzer.ScopeBuilder.mark
def mark(self, line)
Definition: profile_analyzer.py:64
profile_analyzer.ScopeBuilder.finish
def finish(self, line)
Definition: profile_analyzer.py:69
profile_analyzer.CallStackBuilder.finish
def finish(self)
Definition: profile_analyzer.py:95
profile_analyzer.time_string
def time_string(values)
Definition: profile_analyzer.py:214
profile_analyzer.CallStack.count
count
Definition: profile_analyzer.py:136
profile_analyzer.LineItem.__init__
def __init__(self, line, indent)
Definition: profile_analyzer.py:44
profile_analyzer.ScopeBuilder.first_child_pos
first_child_pos
Definition: profile_analyzer.py:62
profile_analyzer.LineItem.important
important
Definition: profile_analyzer.py:49
len
int len
Definition: abseil-cpp/absl/base/internal/low_level_alloc_test.cc:46
profile_analyzer.ScopeBuilder
Definition: profile_analyzer.py:55
profile_analyzer.CallStack
Definition: profile_analyzer.py:133
update
absl::optional< XdsClusterResource > update
Definition: cds.cc:150
profile_analyzer.CallStackBuilder.__init__
def __init__(self)
Definition: profile_analyzer.py:90
profile_analyzer.LineItem.fileline
fileline
Definition: profile_analyzer.py:51
profile_analyzer.CallStackBuilder.signature
signature
Definition: profile_analyzer.py:92


grpc
Author(s):
autogenerated on Thu Mar 13 2025 03:00:55