1 #!/usr/bin/env python2.7
2 # Copyright 2015 gRPC authors.
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
27 TIME_FROM_SCOPE_START = object()
28 TIME_TO_SCOPE_END = object()
29 TIME_FROM_STACK_START = object()
30 TIME_TO_STACK_END = object()
31 TIME_FROM_LAST_IMPORTANT = object()
33 argp = argparse.ArgumentParser(
34 description='Process output of basic_prof builds')
35 argp.add_argument('--source', default='latency_trace.txt', type=str)
36 argp.add_argument('--fmt', choices=tabulate.tabulate_formats, default='simple')
37 argp.add_argument('--out', default='-', type=str)
38 args = argp.parse_args()
41 class LineItem(object):
43 def __init__(self, line, indent):
44 self.tag = line['tag']
46 self.start_time = line['t']
48 self.important = line['imp']
49 self.filename = line['file']
50 self.fileline = line['line']
54 class ScopeBuilder(object):
56 def __init__(self, call_stack_builder, line):
57 self.call_stack_builder = call_stack_builder
58 self.indent = len(call_stack_builder.stk)
59 self.top_line = LineItem(line, self.indent)
60 call_stack_builder.lines.append(self.top_line)
61 self.first_child_pos = len(call_stack_builder.lines)
64 line_item = LineItem(line, self.indent + 1)
65 line_item.end_time = line_item.start_time
66 self.call_stack_builder.lines.append(line_item)
68 def finish(self, line):
69 assert line['tag'] == self.top_line.tag, (
70 'expected %s, got %s; thread=%s; t0=%f t1=%f' %
71 (self.top_line.tag, line['tag'], line['thd'],
72 self.top_line.start_time, line['t']))
73 final_time_stamp = line['t']
74 assert self.top_line.end_time is None
75 self.top_line.end_time = final_time_stamp
76 self.top_line.important = self.top_line.important or line['imp']
77 assert SELF_TIME not in self.top_line.times
79 SELF_TIME] = final_time_stamp - self.top_line.start_time
80 for line in self.call_stack_builder.lines[self.first_child_pos:]:
81 if TIME_FROM_SCOPE_START not in line.times:
83 TIME_FROM_SCOPE_START] = line.start_time - self.top_line.start_time
84 line.times[TIME_TO_SCOPE_END] = final_time_stamp - line.end_time
87 class CallStackBuilder(object):
91 self.signature = hashlib.md5()
95 start_time = self.lines[0].start_time
96 end_time = self.lines[0].end_time
97 self.signature = self.signature.hexdigest()
98 last_important = start_time
99 for line in self.lines:
100 line.times[TIME_FROM_STACK_START] = line.start_time - start_time
101 line.times[TIME_TO_STACK_END] = end_time - line.end_time
103 TIME_FROM_LAST_IMPORTANT] = line.start_time - last_important
105 last_important = line.end_time
106 last_important = end_time
109 line_type = line['type']
110 self.signature.update(line_type)
111 self.signature.update(line['tag'])
113 self.stk.append(ScopeBuilder(self, line))
115 elif line_type == '}':
117 'expected non-empty stack for closing %s; thread=%s; t=%f' %
118 (line['tag'], line['thd'], line['t']))
119 self.stk.pop().finish(line)
124 elif line_type == '.' or line_type == '!':
126 self.stk[-1].mark(line)
129 raise Exception('Unknown line type: \'%s\'' % line_type)
132 class CallStack(object):
134 def __init__(self, initial_call_stack_builder):
136 self.signature = initial_call_stack_builder.signature
137 self.lines = initial_call_stack_builder.lines
138 for line in self.lines:
139 for key, val in line.times.items():
140 line.times[key] = [val]
142 def add(self, call_stack_builder):
143 assert self.signature == call_stack_builder.signature
145 assert len(self.lines) == len(call_stack_builder.lines)
146 for lsum, line in itertools.izip(self.lines, call_stack_builder.lines):
147 assert lsum.tag == line.tag
148 assert lsum.times.keys() == line.times.keys()
149 for k, lst in lsum.times.iteritems():
150 lst.append(line.times[k])
153 for line in self.lines:
154 for lst in line.times.itervalues():
158 builder = collections.defaultdict(CallStackBuilder)
159 call_stacks = collections.defaultdict(CallStack)
163 with open(args.source) as f:
166 inf = json.loads(line)
170 if cs.signature in call_stacks:
171 call_stacks[cs.signature].add(cs)
173 call_stacks[cs.signature] = CallStack(cs)
175 time_taken = time.time() - start
177 call_stacks = sorted(call_stacks.values(),
178 key=lambda cs: cs.count,
181 for cs in call_stacks:
182 total_stacks += cs.count
186 def percentile(N, percent, key=lambda x: x):
188 Find the percentile of an already sorted list of values.
190 @parameter N - is a list of values. MUST be already sorted.
191 @parameter percent - a float value from [0.0,1.0].
192 @parameter key - optional key function to compute value from each element of N.
194 @return - the percentile of the values
198 float_idx = (len(N) - 1) * percent
202 # interpolate with the next element's value
203 result += (float_idx - idx) * (key(N[idx + 1]) - key(N[idx]))
208 if tag[0:10] == 'GRPC_PTAG_':
213 def time_string(values):
214 num_values = len(values)
215 return '%.1f/%.1f/%.1f' % (1e6 * percentile(values, 0.5), 1e6 * percentile(
216 values, 0.9), 1e6 * percentile(values, 0.99))
219 def time_format(idx):
221 def ent(line, idx=idx):
222 if idx in line.times:
223 return time_string(line.times[idx])
229 BANNER = {'simple': 'Count: %(count)d', 'html': '<h1>Count: %(count)d</h1>'}
232 ('TAG', lambda line: '..' * line.indent + tidy_tag(line.tag)),
233 ('LOC', lambda line: '%s:%d' %
234 (line.filename[line.filename.rfind('/') + 1:], line.fileline)),
235 ('IMP', lambda line: '*' if line.important else ''),
236 ('FROM_IMP', time_format(TIME_FROM_LAST_IMPORTANT)),
237 ('FROM_STACK_START', time_format(TIME_FROM_STACK_START)),
238 ('SELF', time_format(SELF_TIME)),
239 ('TO_STACK_END', time_format(TIME_TO_STACK_END)),
240 ('FROM_SCOPE_START', time_format(TIME_FROM_SCOPE_START)),
241 ('SELF', time_format(SELF_TIME)),
242 ('TO_SCOPE_END', time_format(TIME_TO_SCOPE_END)),
247 out = open(args.out, 'w')
249 if args.fmt == 'html':
250 print >> out, '<html>'
251 print >> out, '<head>'
252 print >> out, '<title>Profile Report</title>'
253 print >> out, '</head>'
256 for cs in call_stacks:
258 if args.fmt in BANNER:
259 print >> out, BANNER[args.fmt] % {
262 header, _ = zip(*FORMAT)
264 for line in cs.lines:
267 fields.append(fn(line))
269 print >> out, tabulate.tabulate(table, header, tablefmt=args.fmt)
270 accounted_for += cs.count
271 if accounted_for > .99 * total_stacks:
274 if args.fmt == 'html':