blob: 511ab2bcdf8b2d0586e4ff64adf56b396b0ea79b [file] [log] [blame]
Leon Clarkef7060e22010-06-03 12:02:55 +01001#!/usr/bin/env python
2#
3# Copyright 2010 the V8 project authors. All rights reserved.
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met:
7#
8# * Redistributions of source code must retain the above copyright
9# notice, this list of conditions and the following disclaimer.
10# * Redistributions in binary form must reproduce the above
11# copyright notice, this list of conditions and the following
12# disclaimer in the documentation and/or other materials provided
13# with the distribution.
14# * Neither the name of Google Inc. nor the names of its
15# contributors may be used to endorse or promote products derived
16# from this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29#
30
31#
32# This is an utility for plotting charts based on GC traces produced by V8 when
33# run with flags --trace-gc --trace-gc-nvp. Relies on gnuplot for actual
34# plotting.
35#
36# Usage: gc-nvp-trace-processor.py <GC-trace-filename>
37#
38
39
40from __future__ import with_statement
Iain Merrick75681382010-08-19 15:07:18 +010041import sys, types, re, subprocess, math
Leon Clarkef7060e22010-06-03 12:02:55 +010042
43def flatten(l):
44 flat = []
45 for i in l: flat.extend(i)
46 return flat
47
48def split_nvp(s):
49 t = {}
Kristian Monsen50ef84f2010-07-29 15:18:00 +010050 for (name, value) in re.findall(r"(\w+)=([-\w]+)", s):
51 try:
52 t[name] = int(value)
53 except ValueError:
54 t[name] = value
55
Leon Clarkef7060e22010-06-03 12:02:55 +010056 return t
57
58def parse_gc_trace(input):
59 trace = []
60 with open(input) as f:
61 for line in f:
62 info = split_nvp(line)
63 if info and 'pause' in info and info['pause'] > 0:
64 info['i'] = len(trace)
65 trace.append(info)
66 return trace
67
68def extract_field_names(script):
69 fields = { 'data': true, 'in': true }
70
71 for m in re.finditer(r"$(\w+)", script):
72 field_name = m.group(1)
73 if field_name not in fields:
74 fields[field] = field_count
75 field_count = field_count + 1
76
77 return fields
78
79def gnuplot(script):
80 gnuplot = subprocess.Popen(["gnuplot"], stdin=subprocess.PIPE)
81 gnuplot.stdin.write(script)
82 gnuplot.stdin.close()
83 gnuplot.wait()
84
85x1y1 = 'x1y1'
86x1y2 = 'x1y2'
87x2y1 = 'x2y1'
88x2y2 = 'x2y2'
89
90class Item(object):
91 def __init__(self, title, field, axis = x1y1, **keywords):
92 self.title = title
93 self.axis = axis
94 self.props = keywords
95 if type(field) is types.ListType:
96 self.field = field
97 else:
98 self.field = [field]
99
100 def fieldrefs(self):
101 return self.field
102
103 def to_gnuplot(self, context):
104 args = ['"%s"' % context.datafile,
105 'using %s' % context.format_fieldref(self.field),
106 'title "%s"' % self.title,
107 'axis %s' % self.axis]
108 if 'style' in self.props:
109 args.append('with %s' % self.props['style'])
110 if 'lc' in self.props:
111 args.append('lc rgb "%s"' % self.props['lc'])
112 if 'fs' in self.props:
113 args.append('fs %s' % self.props['fs'])
114 return ' '.join(args)
115
116class Plot(object):
117 def __init__(self, *items):
118 self.items = items
119
120 def fieldrefs(self):
121 return flatten([item.fieldrefs() for item in self.items])
122
123 def to_gnuplot(self, ctx):
124 return 'plot ' + ', '.join([item.to_gnuplot(ctx) for item in self.items])
125
126class Set(object):
127 def __init__(self, value):
128 self.value = value
129
130 def to_gnuplot(self, ctx):
131 return 'set ' + self.value
132
133 def fieldrefs(self):
134 return []
135
136class Context(object):
137 def __init__(self, datafile, field_to_index):
138 self.datafile = datafile
139 self.field_to_index = field_to_index
140
141 def format_fieldref(self, fieldref):
142 return ':'.join([str(self.field_to_index[field]) for field in fieldref])
143
144def collect_fields(plot):
145 field_to_index = {}
146 fields = []
147
148 def add_field(field):
149 if field not in field_to_index:
150 fields.append(field)
151 field_to_index[field] = len(fields)
152
153 for field in flatten([item.fieldrefs() for item in plot]):
154 add_field(field)
155
156 return (fields, field_to_index)
157
158def is_y2_used(plot):
159 for subplot in plot:
160 if isinstance(subplot, Plot):
161 for item in subplot.items:
162 if item.axis == x1y2 or item.axis == x2y2:
163 return True
164 return False
165
166def get_field(trace_line, field):
167 t = type(field)
168 if t is types.StringType:
169 return trace_line[field]
170 elif t is types.FunctionType:
171 return field(trace_line)
172
173def generate_datafile(datafile_name, trace, fields):
174 with open(datafile_name, 'w') as datafile:
175 for line in trace:
176 data_line = [str(get_field(line, field)) for field in fields]
177 datafile.write('\t'.join(data_line))
178 datafile.write('\n')
179
180def generate_script_and_datafile(plot, trace, datafile, output):
181 (fields, field_to_index) = collect_fields(plot)
182 generate_datafile(datafile, trace, fields)
183 script = [
184 'set terminal png',
185 'set output "%s"' % output,
186 'set autoscale',
187 'set ytics nomirror',
188 'set xtics nomirror',
189 'set key below'
190 ]
191
192 if is_y2_used(plot):
193 script.append('set autoscale y2')
194 script.append('set y2tics')
195
196 context = Context(datafile, field_to_index)
197
198 for item in plot:
199 script.append(item.to_gnuplot(context))
200
201 return '\n'.join(script)
202
203def plot_all(plots, trace, prefix):
204 charts = []
205
206 for plot in plots:
207 outfilename = "%s_%d.png" % (prefix, len(charts))
208 charts.append(outfilename)
209 script = generate_script_and_datafile(plot, trace, '~datafile', outfilename)
210 print 'Plotting %s...' % outfilename
211 gnuplot(script)
212
213 return charts
214
215def reclaimed_bytes(row):
216 return row['total_size_before'] - row['total_size_after']
217
Kristian Monsen50ef84f2010-07-29 15:18:00 +0100218def other_scope(r):
Ben Murdoch257744e2011-11-30 15:57:28 +0000219 if r['gc'] == 's':
220 # there is no 'other' scope for scavenging collections.
221 return 0
222 return r['pause'] - r['mark'] - r['sweep'] - r['compact'] - r['external']
223
224def scavenge_scope(r):
225 if r['gc'] == 's':
226 return r['pause'] - r['external']
227 return 0
Kristian Monsen50ef84f2010-07-29 15:18:00 +0100228
Leon Clarkef7060e22010-06-03 12:02:55 +0100229plots = [
230 [
231 Set('style fill solid 0.5 noborder'),
232 Set('style histogram rowstacked'),
233 Set('style data histograms'),
Ben Murdoch257744e2011-11-30 15:57:28 +0000234 Plot(Item('Scavenge', scavenge_scope, lc = 'green'),
235 Item('Marking', 'mark', lc = 'purple'),
Leon Clarkef7060e22010-06-03 12:02:55 +0100236 Item('Sweep', 'sweep', lc = 'blue'),
237 Item('Compaction', 'compact', lc = 'red'),
Ben Murdoch257744e2011-11-30 15:57:28 +0000238 Item('External', 'external', lc = '#489D43'),
Kristian Monsen50ef84f2010-07-29 15:18:00 +0100239 Item('Other', other_scope, lc = 'grey'))
Leon Clarkef7060e22010-06-03 12:02:55 +0100240 ],
241 [
242 Set('style histogram rowstacked'),
243 Set('style data histograms'),
244 Plot(Item('Heap Size (before GC)', 'total_size_before', x1y2,
245 fs = 'solid 0.4 noborder',
246 lc = 'green'),
247 Item('Total holes (after GC)', 'holes_size_before', x1y2,
248 fs = 'solid 0.4 noborder',
249 lc = 'red'),
250 Item('GC Time', ['i', 'pause'], style = 'lines', lc = 'red'))
251 ],
252 [
253 Set('style histogram rowstacked'),
254 Set('style data histograms'),
255 Plot(Item('Heap Size (after GC)', 'total_size_after', x1y2,
256 fs = 'solid 0.4 noborder',
257 lc = 'green'),
258 Item('Total holes (after GC)', 'holes_size_after', x1y2,
259 fs = 'solid 0.4 noborder',
260 lc = 'red'),
261 Item('GC Time', ['i', 'pause'],
262 style = 'lines',
263 lc = 'red'))
264 ],
265 [
266 Set('style fill solid 0.5 noborder'),
267 Set('style data histograms'),
268 Plot(Item('Allocated', 'allocated'),
269 Item('Reclaimed', reclaimed_bytes),
270 Item('Promoted', 'promoted', style = 'lines', lc = 'black'))
271 ],
272]
273
Iain Merrick75681382010-08-19 15:07:18 +0100274def freduce(f, field, trace, init):
275 return reduce(lambda t,r: f(t, r[field]), trace, init)
276
Kristian Monsen50ef84f2010-07-29 15:18:00 +0100277def calc_total(trace, field):
Iain Merrick75681382010-08-19 15:07:18 +0100278 return freduce(lambda t,v: t + v, field, trace, 0)
Kristian Monsen50ef84f2010-07-29 15:18:00 +0100279
280def calc_max(trace, field):
Iain Merrick75681382010-08-19 15:07:18 +0100281 return freduce(lambda t,r: max(t, r), field, trace, 0)
282
283def count_nonzero(trace, field):
284 return freduce(lambda t,r: t if r == 0 else t + 1, field, trace, 0)
285
Kristian Monsen50ef84f2010-07-29 15:18:00 +0100286
Leon Clarkef7060e22010-06-03 12:02:55 +0100287def process_trace(filename):
288 trace = parse_gc_trace(filename)
Leon Clarkef7060e22010-06-03 12:02:55 +0100289
Iain Merrick75681382010-08-19 15:07:18 +0100290 marksweeps = filter(lambda r: r['gc'] == 'ms', trace)
291 markcompacts = filter(lambda r: r['gc'] == 'mc', trace)
Kristian Monsen50ef84f2010-07-29 15:18:00 +0100292 scavenges = filter(lambda r: r['gc'] == 's', trace)
Kristian Monsen50ef84f2010-07-29 15:18:00 +0100293
Leon Clarkef7060e22010-06-03 12:02:55 +0100294 charts = plot_all(plots, trace, filename)
295
Iain Merrick75681382010-08-19 15:07:18 +0100296 def stats(out, prefix, trace, field):
297 n = len(trace)
298 total = calc_total(trace, field)
299 max = calc_max(trace, field)
300 if n > 0:
301 avg = total / n
302 else:
303 avg = 0
304 if n > 1:
305 dev = math.sqrt(freduce(lambda t,r: (r - avg) ** 2, field, trace, 0) /
306 (n - 1))
307 else:
308 dev = 0
309
310 out.write('<tr><td>%s</td><td>%d</td><td>%d</td>'
311 '<td>%d</td><td>%d [dev %f]</td></tr>' %
312 (prefix, n, total, max, avg, dev))
313
314
Leon Clarkef7060e22010-06-03 12:02:55 +0100315 with open(filename + '.html', 'w') as out:
316 out.write('<html><body>')
Iain Merrick75681382010-08-19 15:07:18 +0100317 out.write('<table>')
318 out.write('<tr><td>Phase</td><td>Count</td><td>Time (ms)</td>')
319 out.write('<td>Max</td><td>Avg</td></tr>')
320 stats(out, 'Total in GC', trace, 'pause')
321 stats(out, 'Scavenge', scavenges, 'pause')
322 stats(out, 'MarkSweep', marksweeps, 'pause')
323 stats(out, 'MarkCompact', markcompacts, 'pause')
324 stats(out, 'Mark', filter(lambda r: r['mark'] != 0, trace), 'mark')
325 stats(out, 'Sweep', filter(lambda r: r['sweep'] != 0, trace), 'sweep')
326 stats(out, 'Compact', filter(lambda r: r['compact'] != 0, trace), 'compact')
Ben Murdoch257744e2011-11-30 15:57:28 +0000327 stats(out,
328 'External',
329 filter(lambda r: r['external'] != 0, trace),
330 'external')
Iain Merrick75681382010-08-19 15:07:18 +0100331 out.write('</table>')
Leon Clarkef7060e22010-06-03 12:02:55 +0100332 for chart in charts:
333 out.write('<img src="%s">' % chart)
334 out.write('</body></html>')
335
336 print "%s generated." % (filename + '.html')
337
338if len(sys.argv) != 2:
339 print "Usage: %s <GC-trace-filename>" % sys.argv[0]
340 sys.exit(1)
341
342process_trace(sys.argv[1])