blob: 632aeffd3c397ab6d0ba6836730b482bf0bebb04 [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():
David Garcia Quintas975efdc2015-05-06 14:42:46 -070093 time_values = sorted(time_values)
David Garcia Quintas776075a2015-05-06 12:59:23 -070094 print '{:>40s}: {:>15.3f} {:>15.3f} {:>15.3f} {:>15.3f}'.format(
95 key, percentile(time_values, 50), percentile(time_values, 90),
96 percentile(time_values, 95), percentile(time_values, 99))
97 print
98
99
Craig Tillerbd9f9242015-05-01 16:24:41 -0700100def entries():
101 for line in sys.stdin:
102 m = _RE_LINE.match(line)
103 if not m: continue
104 yield Entry(time=float(m.group(1)),
105 thread=m.group(2),
106 type=m.group(3),
107 tag=int(m.group(4)),
108 id=m.group(5),
109 file=m.group(6),
110 line=m.group(7))
111
112threads = collections.defaultdict(lambda: collections.defaultdict(list))
113times = collections.defaultdict(list)
David Garcia Quintas5b2ea292015-05-06 09:57:49 -0700114important_marks = collections.defaultdict(list)
115
Craig Tillerbd9f9242015-05-01 16:24:41 -0700116for entry in entries():
117 thread = threads[entry.thread]
118 if entry.type == '{':
119 thread[entry.tag].append(entry)
David Garcia Quintas5b2ea292015-05-06 09:57:49 -0700120 if entry.type == '!':
121 # Save a snapshot of the current stack inside a new ImportantMark instance.
122 # Get all entries with type '{' from "thread".
123 stack = [e for entries_for_tag in thread.values()
124 for e in entries_for_tag if e.type == '{']
David Garcia Quintas776075a2015-05-06 12:59:23 -0700125 imark_group_key = '{tag}@{file}:{line}'.format(**entry._asdict())
126 important_marks[imark_group_key].append(ImportantMark(entry, stack))
Craig Tillerbd9f9242015-05-01 16:24:41 -0700127 elif entry.type == '}':
128 last = thread[entry.tag].pop()
129 times[entry.tag].append(entry.time - last.time)
David Garcia Quintas5b2ea292015-05-06 09:57:49 -0700130 # Update accounting for important marks.
David Garcia Quintas776075a2015-05-06 12:59:23 -0700131 for imarks_group in important_marks.itervalues():
132 for imark in imarks_group:
David Garcia Quintas5b2ea292015-05-06 09:57:49 -0700133 imark.append_post_entry(entry)
Craig Tillerbd9f9242015-05-01 16:24:41 -0700134
David Garcia Quintas776075a2015-05-06 12:59:23 -0700135def percentile(vals, percent):
David Garcia Quintas975efdc2015-05-06 14:42:46 -0700136 """ Calculates the interpolated percentile given a sorted sequence and a
137 percent (in the usual 0-100 range)."""
David Garcia Quintas776075a2015-05-06 12:59:23 -0700138 assert vals, "Empty input sequence."
David Garcia Quintas776075a2015-05-06 12:59:23 -0700139 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()):
David Garcia Quintas975efdc2015-05-06 14:42:46 -0700152 vals = sorted(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)