blob: b540052697debdcd25465c502d00792d54178116 [file] [log] [blame]
Craig Tillerbd9f9242015-05-01 16:24:41 -07001#!/usr/bin/env python
2# 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
31"""
32Read GRPC basic profiles, analyze the data.
33
34Usage:
35 bins/basicprof/qps_smoke_test > log
36 cat log | tools/profile_analyzer/profile_analyzer.py
37"""
38
39
40import collections
David Garcia Quintas5b2ea292015-05-06 09:57:49 -070041import itertools
David Garcia Quintas776075a2015-05-06 12:59:23 -070042import math
Craig Tillerbd9f9242015-05-01 16:24:41 -070043import re
44import sys
45
46# Create a regex to parse output of the C core basic profiler,
47# as defined in src/core/profiling/basic_timers.c.
48_RE_LINE = re.compile(r'GRPC_LAT_PROF ' +
David Garcia Quintas5b2ea292015-05-06 09:57:49 -070049 r'([0-9]+\.[0-9]+) 0x([0-9a-f]+) ([{}.!]) ([0-9]+) ' +
Craig Tillerbd9f9242015-05-01 16:24:41 -070050 r'([^ ]+) ([^ ]+) ([0-9]+)')
51
52Entry = collections.namedtuple(
53 'Entry',
54 ['time', 'thread', 'type', 'tag', 'id', 'file', 'line'])
55
David Garcia Quintas5b2ea292015-05-06 09:57:49 -070056
57class ImportantMark(object):
58 def __init__(self, entry, stack):
59 self._entry = entry
60 self._pre_stack = stack
61 self._post_stack = list()
62 self._n = len(stack) # we'll also compute times to that many closing }s
63
64 @property
65 def entry(self):
66 return self._entry
67
68 def append_post_entry(self, entry):
69 if self._n > 0:
70 self._post_stack.append(entry)
71 self._n -= 1
72
73 def get_deltas(self):
74 pre_and_post_stacks = itertools.chain(self._pre_stack, self._post_stack)
75 return collections.OrderedDict((stack_entry,
David Garcia Quintas776075a2015-05-06 12:59:23 -070076 abs(self._entry.time - stack_entry.time))
David Garcia Quintas5b2ea292015-05-06 09:57:49 -070077 for stack_entry in pre_and_post_stacks)
78
David Garcia Quintas776075a2015-05-06 12:59:23 -070079
80def print_grouped_imark_statistics(group_key, imarks_group):
81 values = collections.OrderedDict()
82 for imark in imarks_group:
83 deltas = imark.get_deltas()
84 for relative_entry, time_delta_us in deltas.iteritems():
85 key = '{tag} {type} ({file}:{line})'.format(**relative_entry._asdict())
86 l = values.setdefault(key, list())
87 l.append(time_delta_us)
88
89 print group_key
90 print '{:>40s}: {:>15s} {:>15s} {:>15s} {:>15s}'.format(
91 'Relative mark', '50th p.', '90th p.', '95th p.', '99th p.')
92 for key, time_values in values.iteritems():
93 print '{:>40s}: {:>15.3f} {:>15.3f} {:>15.3f} {:>15.3f}'.format(
94 key, percentile(time_values, 50), percentile(time_values, 90),
95 percentile(time_values, 95), percentile(time_values, 99))
96 print
97
98
Craig Tillerbd9f9242015-05-01 16:24:41 -070099def entries():
100 for line in sys.stdin:
101 m = _RE_LINE.match(line)
102 if not m: continue
103 yield Entry(time=float(m.group(1)),
104 thread=m.group(2),
105 type=m.group(3),
106 tag=int(m.group(4)),
107 id=m.group(5),
108 file=m.group(6),
109 line=m.group(7))
110
111threads = collections.defaultdict(lambda: collections.defaultdict(list))
112times = collections.defaultdict(list)
David Garcia Quintas5b2ea292015-05-06 09:57:49 -0700113important_marks = collections.defaultdict(list)
114
Craig Tillerbd9f9242015-05-01 16:24:41 -0700115for entry in entries():
116 thread = threads[entry.thread]
117 if entry.type == '{':
118 thread[entry.tag].append(entry)
David Garcia Quintas5b2ea292015-05-06 09:57:49 -0700119 if entry.type == '!':
120 # Save a snapshot of the current stack inside a new ImportantMark instance.
121 # Get all entries with type '{' from "thread".
122 stack = [e for entries_for_tag in thread.values()
123 for e in entries_for_tag if e.type == '{']
David Garcia Quintas776075a2015-05-06 12:59:23 -0700124 imark_group_key = '{tag}@{file}:{line}'.format(**entry._asdict())
125 important_marks[imark_group_key].append(ImportantMark(entry, stack))
Craig Tillerbd9f9242015-05-01 16:24:41 -0700126 elif entry.type == '}':
127 last = thread[entry.tag].pop()
128 times[entry.tag].append(entry.time - last.time)
David Garcia Quintas5b2ea292015-05-06 09:57:49 -0700129 # Update accounting for important marks.
David Garcia Quintas776075a2015-05-06 12:59:23 -0700130 for imarks_group in important_marks.itervalues():
131 for imark in imarks_group:
David Garcia Quintas5b2ea292015-05-06 09:57:49 -0700132 imark.append_post_entry(entry)
Craig Tillerbd9f9242015-05-01 16:24:41 -0700133
David Garcia Quintas776075a2015-05-06 12:59:23 -0700134def percentile(vals, percent):
135 """ Calculates the interpolated percentile given a (possibly unsorted sequence)
136 and a percent (in the usual 0-100 range)."""
137 assert vals, "Empty input sequence."
138 vals = sorted(vals)
139 percent /= 100.0
140 k = (len(vals)-1) * percent
141 f = math.floor(k)
142 c = math.ceil(k)
143 if f == c:
144 return vals[int(k)]
145 # else, interpolate
146 d0 = vals[int(f)] * (c-k)
147 d1 = vals[int(c)] * (k-f)
148 return d0 + d1
Craig Tillerbd9f9242015-05-01 16:24:41 -0700149
150print 'tag 50%/90%/95%/99% us'
151for tag in sorted(times.keys()):
152 vals = times[tag]
David Garcia Quintas5b2ea292015-05-06 09:57:49 -0700153 print '%d %.2f/%.2f/%.2f/%.2f' % (tag,
Craig Tillerbd9f9242015-05-01 16:24:41 -0700154 percentile(vals, 50),
155 percentile(vals, 90),
156 percentile(vals, 95),
157 percentile(vals, 99))
David Garcia Quintas5b2ea292015-05-06 09:57:49 -0700158
159print
160print 'Important marks:'
161print '================'
David Garcia Quintas776075a2015-05-06 12:59:23 -0700162for group_key, imarks_group in important_marks.iteritems():
163 print_grouped_imark_statistics(group_key, imarks_group)