1 #!/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 40 from __future__ import with_statement 41 import sys, types, subprocess, math 42 import gc_nvp_common 43 44 def flatten(l): 45 flat = [] 46 for i in l: flat.extend(i) 47 return flat 48 49 def gnuplot(script): 50 gnuplot = subprocess.Popen(["gnuplot"], stdin=subprocess.PIPE) 51 gnuplot.stdin.write(script) 52 gnuplot.stdin.close() 53 gnuplot.wait() 54 55 x1y1 = 'x1y1' 56 x1y2 = 'x1y2' 57 x2y1 = 'x2y1' 58 x2y2 = 'x2y2' 59 60 class Item(object): 61 def __init__(self, title, field, axis = x1y1, **keywords): 62 self.title = title 63 self.axis = axis 64 self.props = keywords 65 if type(field) is types.ListType: 66 self.field = field 67 else: 68 self.field = [field] 69 70 def fieldrefs(self): 71 return self.field 72 73 def to_gnuplot(self, context): 74 args = ['"%s"' % context.datafile, 75 'using %s' % context.format_fieldref(self.field), 76 'title "%s"' % self.title, 77 'axis %s' % self.axis] 78 if 'style' in self.props: 79 args.append('with %s' % self.props['style']) 80 if 'lc' in self.props: 81 args.append('lc rgb "%s"' % self.props['lc']) 82 if 'fs' in self.props: 83 args.append('fs %s' % self.props['fs']) 84 return ' '.join(args) 85 86 class Plot(object): 87 def __init__(self, *items): 88 self.items = items 89 90 def fieldrefs(self): 91 return flatten([item.fieldrefs() for item in self.items]) 92 93 def to_gnuplot(self, ctx): 94 return 'plot ' + ', '.join([item.to_gnuplot(ctx) for item in self.items]) 95 96 class Set(object): 97 def __init__(self, value): 98 self.value = value 99 100 def to_gnuplot(self, ctx): 101 return 'set ' + self.value 102 103 def fieldrefs(self): 104 return [] 105 106 class Context(object): 107 def __init__(self, datafile, field_to_index): 108 self.datafile = datafile 109 self.field_to_index = field_to_index 110 111 def format_fieldref(self, fieldref): 112 return ':'.join([str(self.field_to_index[field]) for field in fieldref]) 113 114 def collect_fields(plot): 115 field_to_index = {} 116 fields = [] 117 118 def add_field(field): 119 if field not in field_to_index: 120 fields.append(field) 121 field_to_index[field] = len(fields) 122 123 for field in flatten([item.fieldrefs() for item in plot]): 124 add_field(field) 125 126 return (fields, field_to_index) 127 128 def is_y2_used(plot): 129 for subplot in plot: 130 if isinstance(subplot, Plot): 131 for item in subplot.items: 132 if item.axis == x1y2 or item.axis == x2y2: 133 return True 134 return False 135 136 def get_field(trace_line, field): 137 t = type(field) 138 if t is types.StringType: 139 return trace_line[field] 140 elif t is types.FunctionType: 141 return field(trace_line) 142 143 def generate_datafile(datafile_name, trace, fields): 144 with open(datafile_name, 'w') as datafile: 145 for line in trace: 146 data_line = [str(get_field(line, field)) for field in fields] 147 datafile.write('\t'.join(data_line)) 148 datafile.write('\n') 149 150 def generate_script_and_datafile(plot, trace, datafile, output): 151 (fields, field_to_index) = collect_fields(plot) 152 generate_datafile(datafile, trace, fields) 153 script = [ 154 'set terminal png', 155 'set output "%s"' % output, 156 'set autoscale', 157 'set ytics nomirror', 158 'set xtics nomirror', 159 'set key below' 160 ] 161 162 if is_y2_used(plot): 163 script.append('set autoscale y2') 164 script.append('set y2tics') 165 166 context = Context(datafile, field_to_index) 167 168 for item in plot: 169 script.append(item.to_gnuplot(context)) 170 171 return '\n'.join(script) 172 173 def plot_all(plots, trace, prefix): 174 charts = [] 175 176 for plot in plots: 177 outfilename = "%s_%d.png" % (prefix, len(charts)) 178 charts.append(outfilename) 179 script = generate_script_and_datafile(plot, trace, '~datafile', outfilename) 180 print 'Plotting %s...' % outfilename 181 gnuplot(script) 182 183 return charts 184 185 def reclaimed_bytes(row): 186 return row['total_size_before'] - row['total_size_after'] 187 188 def other_scope(r): 189 if r['gc'] == 's': 190 # there is no 'other' scope for scavenging collections. 191 return 0 192 return r['pause'] - r['mark'] - r['sweep'] - r['external'] 193 194 def scavenge_scope(r): 195 if r['gc'] == 's': 196 return r['pause'] - r['external'] 197 return 0 198 199 200 def real_mutator(r): 201 return r['mutator'] - r['steps_took'] 202 203 plots = [ 204 [ 205 Set('style fill solid 0.5 noborder'), 206 Set('style histogram rowstacked'), 207 Set('style data histograms'), 208 Plot(Item('Scavenge', scavenge_scope, lc = 'green'), 209 Item('Marking', 'mark', lc = 'purple'), 210 Item('Sweep', 'sweep', lc = 'blue'), 211 Item('External', 'external', lc = '#489D43'), 212 Item('Other', other_scope, lc = 'grey'), 213 Item('IGC Steps', 'steps_took', lc = '#FF6347')) 214 ], 215 [ 216 Set('style fill solid 0.5 noborder'), 217 Set('style histogram rowstacked'), 218 Set('style data histograms'), 219 Plot(Item('Scavenge', scavenge_scope, lc = 'green'), 220 Item('Marking', 'mark', lc = 'purple'), 221 Item('Sweep', 'sweep', lc = 'blue'), 222 Item('External', 'external', lc = '#489D43'), 223 Item('Other', other_scope, lc = '#ADD8E6'), 224 Item('External', 'external', lc = '#D3D3D3')) 225 ], 226 227 [ 228 Plot(Item('Mutator', real_mutator, lc = 'black', style = 'lines')) 229 ], 230 [ 231 Set('style histogram rowstacked'), 232 Set('style data histograms'), 233 Plot(Item('Heap Size (before GC)', 'total_size_before', x1y2, 234 fs = 'solid 0.4 noborder', 235 lc = 'green'), 236 Item('Total holes (after GC)', 'holes_size_before', x1y2, 237 fs = 'solid 0.4 noborder', 238 lc = 'red'), 239 Item('GC Time', ['i', 'pause'], style = 'lines', lc = 'red')) 240 ], 241 [ 242 Set('style histogram rowstacked'), 243 Set('style data histograms'), 244 Plot(Item('Heap Size (after GC)', 'total_size_after', x1y2, 245 fs = 'solid 0.4 noborder', 246 lc = 'green'), 247 Item('Total holes (after GC)', 'holes_size_after', x1y2, 248 fs = 'solid 0.4 noborder', 249 lc = 'red'), 250 Item('GC Time', ['i', 'pause'], 251 style = 'lines', 252 lc = 'red')) 253 ], 254 [ 255 Set('style fill solid 0.5 noborder'), 256 Set('style data histograms'), 257 Plot(Item('Allocated', 'allocated'), 258 Item('Reclaimed', reclaimed_bytes), 259 Item('Promoted', 'promoted', style = 'lines', lc = 'black')) 260 ], 261 ] 262 263 def freduce(f, field, trace, init): 264 return reduce(lambda t,r: f(t, r[field]), trace, init) 265 266 def calc_total(trace, field): 267 return freduce(lambda t,v: t + long(v), field, trace, long(0)) 268 269 def calc_max(trace, field): 270 return freduce(lambda t,r: max(t, r), field, trace, 0) 271 272 def count_nonzero(trace, field): 273 return freduce(lambda t,r: t if r == 0 else t + 1, field, trace, 0) 274 275 276 def process_trace(filename): 277 trace = gc_nvp_common.parse_gc_trace(filename) 278 279 marksweeps = filter(lambda r: r['gc'] == 'ms', trace) 280 scavenges = filter(lambda r: r['gc'] == 's', trace) 281 globalgcs = filter(lambda r: r['gc'] != 's', trace) 282 283 284 charts = plot_all(plots, trace, filename) 285 286 def stats(out, prefix, trace, field): 287 n = len(trace) 288 total = calc_total(trace, field) 289 max = calc_max(trace, field) 290 if n > 0: 291 avg = total / n 292 else: 293 avg = 0 294 if n > 1: 295 dev = math.sqrt(freduce(lambda t,r: t + (r - avg) ** 2, field, trace, 0) / 296 (n - 1)) 297 else: 298 dev = 0 299 300 out.write('<tr><td>%s</td><td>%d</td><td>%d</td>' 301 '<td>%d</td><td>%d [dev %f]</td></tr>' % 302 (prefix, n, total, max, avg, dev)) 303 304 def HumanReadable(size): 305 suffixes = ['bytes', 'kB', 'MB', 'GB'] 306 power = 1 307 for i in range(len(suffixes)): 308 if size < power*1024: 309 return "%.1f" % (float(size) / power) + " " + suffixes[i] 310 power *= 1024 311 312 def throughput(name, trace): 313 total_live_after = calc_total(trace, 'total_size_after') 314 total_live_before = calc_total(trace, 'total_size_before') 315 total_gc = calc_total(trace, 'pause') 316 if total_gc == 0: 317 return 318 out.write('GC %s Throughput (after): %s / %s ms = %s/ms<br/>' % 319 (name, 320 HumanReadable(total_live_after), 321 total_gc, 322 HumanReadable(total_live_after / total_gc))) 323 out.write('GC %s Throughput (before): %s / %s ms = %s/ms<br/>' % 324 (name, 325 HumanReadable(total_live_before), 326 total_gc, 327 HumanReadable(total_live_before / total_gc))) 328 329 330 with open(filename + '.html', 'w') as out: 331 out.write('<html><body>') 332 out.write('<table>') 333 out.write('<tr><td>Phase</td><td>Count</td><td>Time (ms)</td>') 334 out.write('<td>Max</td><td>Avg</td></tr>') 335 stats(out, 'Total in GC', trace, 'pause') 336 stats(out, 'Scavenge', scavenges, 'pause') 337 stats(out, 'MarkSweep', marksweeps, 'pause') 338 stats(out, 'Mark', filter(lambda r: r['mark'] != 0, trace), 'mark') 339 stats(out, 'Sweep', filter(lambda r: r['sweep'] != 0, trace), 'sweep') 340 stats(out, 341 'External', 342 filter(lambda r: r['external'] != 0, trace), 343 'external') 344 out.write('</table>') 345 throughput('TOTAL', trace) 346 throughput('MS', marksweeps) 347 throughput('OLDSPACE', globalgcs) 348 out.write('<br/>') 349 for chart in charts: 350 out.write('<img src="%s">' % chart) 351 out.write('</body></html>') 352 353 print "%s generated." % (filename + '.html') 354 355 if len(sys.argv) != 2: 356 print "Usage: %s <GC-trace-filename>" % sys.argv[0] 357 sys.exit(1) 358 359 process_trace(sys.argv[1]) 360