blob: dad0712d4028bf023a344e006219fc5866e5f379 [file] [log] [blame]
Nathaniel Manistaae4fbcd2015-09-23 16:29:44 +00001#!/usr/bin/env python2.7
Craig Tiller897e4fe2015-12-22 15:03:40 -08002# Copyright 2015, Google Inc.
3# All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
Craig Tillerdd256942015-10-08 14:42:47 -070031import argparse
Craig Tillerbd9f9242015-05-01 16:24:41 -070032import collections
Craig Tiller86253ca2015-10-08 13:31:02 -070033import hashlib
Craig Tiller9bb01612015-10-08 08:43:48 -070034import itertools
Craig Tiller86253ca2015-10-08 13:31:02 -070035import json
36import math
37import tabulate
38import time
Craig Tillerbd9f9242015-05-01 16:24:41 -070039
Craig Tiller9bb01612015-10-08 08:43:48 -070040
41SELF_TIME = object()
42TIME_FROM_SCOPE_START = object()
43TIME_TO_SCOPE_END = object()
44TIME_FROM_STACK_START = object()
45TIME_TO_STACK_END = object()
46
47
Craig Tillerdd256942015-10-08 14:42:47 -070048argp = argparse.ArgumentParser(description='Process output of basic_prof builds')
49argp.add_argument('--source', default='latency_trace.txt', type=str)
50argp.add_argument('--fmt', choices=tabulate.tabulate_formats, default='simple')
51args = argp.parse_args()
52
Craig Tiller9bb01612015-10-08 08:43:48 -070053class LineItem(object):
54
Craig Tiller86253ca2015-10-08 13:31:02 -070055 def __init__(self, line, indent):
56 self.tag = line['tag']
57 self.indent = indent
58 self.start_time = line['t']
59 self.end_time = None
60 self.important = line['imp']
Craig Tiller1f41b6b2015-10-09 15:07:02 -070061 self.filename = line['file']
62 self.fileline = line['line']
Craig Tiller86253ca2015-10-08 13:31:02 -070063 self.times = {}
Craig Tiller9bb01612015-10-08 08:43:48 -070064
65
66class ScopeBuilder(object):
67
Craig Tiller86253ca2015-10-08 13:31:02 -070068 def __init__(self, call_stack_builder, line):
69 self.call_stack_builder = call_stack_builder
70 self.indent = len(call_stack_builder.stk)
71 self.top_line = LineItem(line, self.indent)
72 call_stack_builder.lines.append(self.top_line)
73 self.first_child_pos = len(call_stack_builder.lines)
Craig Tiller9bb01612015-10-08 08:43:48 -070074
Craig Tiller86253ca2015-10-08 13:31:02 -070075 def mark(self, line):
76 line_item = LineItem(line, self.indent + 1)
77 line_item.end_time = line_item.start_time
78 self.call_stack_builder.lines.append(line_item)
Craig Tiller9bb01612015-10-08 08:43:48 -070079
Craig Tiller86253ca2015-10-08 13:31:02 -070080 def finish(self, line):
Craig Tiller61ead3e2015-11-03 11:03:48 -080081 assert line['tag'] == self.top_line.tag, 'expected %s, got %s; thread=%s; t0=%f t1=%f' % (self.top_line.tag, line['tag'], line['thd'], self.top_line.start_time, line['t'])
Craig Tiller86253ca2015-10-08 13:31:02 -070082 final_time_stamp = line['t']
83 assert self.top_line.end_time is None
84 self.top_line.end_time = final_time_stamp
85 assert SELF_TIME not in self.top_line.times
86 self.top_line.times[SELF_TIME] = final_time_stamp - self.top_line.start_time
87 for line in self.call_stack_builder.lines[self.first_child_pos:]:
88 if TIME_FROM_SCOPE_START not in line.times:
89 line.times[TIME_FROM_SCOPE_START] = line.start_time - self.top_line.start_time
90 line.times[TIME_TO_SCOPE_END] = final_time_stamp - line.end_time
Craig Tiller9bb01612015-10-08 08:43:48 -070091
92
93class CallStackBuilder(object):
Craig Tiller9bb01612015-10-08 08:43:48 -070094
Craig Tiller86253ca2015-10-08 13:31:02 -070095 def __init__(self):
96 self.stk = []
97 self.signature = hashlib.md5()
98 self.lines = []
99
100 def finish(self):
101 start_time = self.lines[0].start_time
102 end_time = self.lines[0].end_time
103 self.signature = self.signature.hexdigest()
104 for line in self.lines:
105 line.times[TIME_FROM_STACK_START] = line.start_time - start_time
106 line.times[TIME_TO_STACK_END] = end_time - line.end_time
107
108 def add(self, line):
109 line_type = line['type']
110 self.signature.update(line_type)
111 self.signature.update(line['tag'])
112 if line_type == '{':
113 self.stk.append(ScopeBuilder(self, line))
114 return False
115 elif line_type == '}':
Craig Tiller61ead3e2015-11-03 11:03:48 -0800116 assert self.stk, 'expected non-empty stack for closing %s; thread=%s; t=%f' % (line['tag'], line['thd'], line['t'])
Craig Tiller86253ca2015-10-08 13:31:02 -0700117 self.stk.pop().finish(line)
118 if not self.stk:
119 self.finish()
120 return True
121 return False
122 elif line_type == '.' or line_type == '!':
123 self.stk[-1].mark(line)
124 return False
125 else:
126 raise Exception('Unknown line type: \'%s\'' % line_type)
Craig Tiller9bb01612015-10-08 08:43:48 -0700127
128
129class CallStack(object):
130
Craig Tiller86253ca2015-10-08 13:31:02 -0700131 def __init__(self, initial_call_stack_builder):
132 self.count = 1
133 self.signature = initial_call_stack_builder.signature
134 self.lines = initial_call_stack_builder.lines
135 for line in self.lines:
136 for key, val in line.times.items():
137 line.times[key] = [val]
Craig Tiller9bb01612015-10-08 08:43:48 -0700138
Craig Tiller86253ca2015-10-08 13:31:02 -0700139 def add(self, call_stack_builder):
140 assert self.signature == call_stack_builder.signature
141 self.count += 1
142 assert len(self.lines) == len(call_stack_builder.lines)
143 for lsum, line in itertools.izip(self.lines, call_stack_builder.lines):
144 assert lsum.tag == line.tag
145 assert lsum.times.keys() == line.times.keys()
146 for k, lst in lsum.times.iteritems():
147 lst.append(line.times[k])
Craig Tiller9bb01612015-10-08 08:43:48 -0700148
Craig Tiller86253ca2015-10-08 13:31:02 -0700149 def finish(self):
150 for line in self.lines:
151 for lst in line.times.itervalues():
152 lst.sort()
Craig Tiller9bb01612015-10-08 08:43:48 -0700153
154builder = collections.defaultdict(CallStackBuilder)
155call_stacks = collections.defaultdict(CallStack)
156
Craig Tiller86253ca2015-10-08 13:31:02 -0700157lines = 0
158start = time.time()
Craig Tillerdd256942015-10-08 14:42:47 -0700159with open(args.source) as f:
David Garcia Quintas9b0a94b2015-05-06 20:50:49 -0700160 for line in f:
Craig Tiller86253ca2015-10-08 13:31:02 -0700161 lines += 1
Craig Tiller504ed592015-10-08 07:38:43 -0700162 inf = json.loads(line)
163 thd = inf['thd']
Craig Tiller9bb01612015-10-08 08:43:48 -0700164 cs = builder[thd]
165 if cs.add(inf):
Craig Tiller86253ca2015-10-08 13:31:02 -0700166 if cs.signature in call_stacks:
167 call_stacks[cs.signature].add(cs)
168 else:
169 call_stacks[cs.signature] = CallStack(cs)
170 del builder[thd]
171time_taken = time.time() - start
David Garcia Quintas5b2ea292015-05-06 09:57:49 -0700172
Craig Tiller9bb01612015-10-08 08:43:48 -0700173call_stacks = sorted(call_stacks.values(), key=lambda cs: cs.count, reverse=True)
Craig Tillerdd256942015-10-08 14:42:47 -0700174total_stacks = 0
Craig Tiller86253ca2015-10-08 13:31:02 -0700175for cs in call_stacks:
Craig Tillerdd256942015-10-08 14:42:47 -0700176 total_stacks += cs.count
Craig Tiller86253ca2015-10-08 13:31:02 -0700177 cs.finish()
178
Craig Tiller86253ca2015-10-08 13:31:02 -0700179def percentile(N, percent, key=lambda x:x):
180 """
181 Find the percentile of a list of values.
182
183 @parameter N - is a list of values. Note N MUST BE already sorted.
184 @parameter percent - a float value from 0.0 to 1.0.
185 @parameter key - optional key function to compute value from each element of N.
186
187 @return - the percentile of the values
188 """
189 if not N:
190 return None
191 k = (len(N)-1) * percent
192 f = math.floor(k)
193 c = math.ceil(k)
194 if f == c:
195 return key(N[int(k)])
196 d0 = key(N[int(f)]) * (c-k)
197 d1 = key(N[int(c)]) * (k-f)
198 return d0+d1
199
200def tidy_tag(tag):
201 if tag[0:10] == 'GRPC_PTAG_':
202 return tag[10:]
203 return tag
204
205def time_string(values):
206 num_values = len(values)
207 return '%.1f/%.1f/%.1f' % (
208 1e6 * percentile(values, 0.5),
209 1e6 * percentile(values, 0.9),
210 1e6 * percentile(values, 0.99))
211
212def time_format(idx):
213 def ent(line, idx=idx):
214 if idx in line.times:
215 return time_string(line.times[idx])
216 return ''
217 return ent
218
219FORMAT = [
220 ('TAG', lambda line: '..'*line.indent + tidy_tag(line.tag)),
Craig Tiller1f41b6b2015-10-09 15:07:02 -0700221 ('LOC', lambda line: '%s:%d' % (line.filename[line.filename.rfind('/')+1:], line.fileline)),
Craig Tiller86253ca2015-10-08 13:31:02 -0700222 ('FROM_STACK_START', time_format(TIME_FROM_STACK_START)),
223 ('SELF', time_format(SELF_TIME)),
224 ('TO_STACK_END', time_format(TIME_TO_STACK_END)),
225 ('FROM_SCOPE_START', time_format(TIME_FROM_SCOPE_START)),
226 ('SELF', time_format(SELF_TIME)),
227 ('TO_SCOPE_END', time_format(TIME_TO_SCOPE_END)),
228]
David Garcia Quintas9b0a94b2015-05-06 20:50:49 -0700229
Craig Tillerdd256942015-10-08 14:42:47 -0700230BANNER = {
231 'simple': 'Count: %(count)d',
232 'html': '<h1>Count: %(count)d</h1>'
233}
234
235if args.fmt == 'html':
236 print '<html>'
237 print '<head>'
238 print '<title>Profile Report</title>'
239 print '</head>'
240
241accounted_for = 0
Craig Tiller9bb01612015-10-08 08:43:48 -0700242for cs in call_stacks:
Craig Tillerdd256942015-10-08 14:42:47 -0700243 if args.fmt in BANNER:
244 print BANNER[args.fmt] % {
245 'count': cs.count,
246 }
Craig Tiller86253ca2015-10-08 13:31:02 -0700247 header, _ = zip(*FORMAT)
248 table = []
249 for line in cs.lines:
250 fields = []
251 for _, fn in FORMAT:
252 fields.append(fn(line))
253 table.append(fields)
Craig Tillerdd256942015-10-08 14:42:47 -0700254 print tabulate.tabulate(table, header, tablefmt=args.fmt)
255 accounted_for += cs.count
256 if accounted_for > .99 * total_stacks:
257 break
258
259if args.fmt == 'html':
260 print '</html>'
261