blob: a8b49c6b95579e86421c86d2d3b2c901a7ba38f1 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2013 The Flutter Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import csv
import json
import sys
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages as pdfp
class BenchmarkResult:
def __init__(self, name, backend, timeUnit, drawCallCount):
self.name = name
self.series = {}
self.seriesLabels = {}
self.backend = backend
self.largeYValues = False
self.yLimit = 200
self.timeUnit = timeUnit
self.drawCallCount = drawCallCount
self.optionalValues = {}
def __repr__(self):
return 'Name: % s\nBackend: % s\nSeries: % s\nSeriesLabels: % s\n' % (
self.name, self.backend, self.series, self.seriesLabels
)
def addDataPoint(self, family, x, y):
if family not in self.series:
self.series[family] = {'x': [], 'y': []}
self.series[family]['x'].append(x)
self.series[family]['y'].append(y)
if y > self.yLimit:
self.largeYValues = True
def addOptionalValue(self, name, x, y):
if name not in self.optionalValues:
self.optionalValues[name] = {}
self.optionalValues[name][x] = y
def setFamilyLabel(self, family, label):
# I'm not keying the main series dict off the family label
# just in case we get data where the two aren't a 1:1 mapping
if family in self.seriesLabels:
assert self.seriesLabels[family] == label
return
self.seriesLabels[family] = label
def plot(self):
figures = []
figures.append(plt.figure(dpi=1200, frameon=False, figsize=(11, 8.5)))
for family in self.series:
plt.plot(
self.series[family]['x'],
self.series[family]['y'],
label=self.seriesLabels[family]
)
plt.xlabel('Benchmark Seed')
plt.ylabel('Time (' + self.timeUnit + ')')
title = ''
# Crop the Y axis so that we can see what's going on at the lower end
if self.largeYValues:
plt.ylim((0, self.yLimit))
title = self.name + ' ' + self.backend + ' (Cropped)'
else:
title = self.name + ' ' + self.backend
if self.drawCallCount != -1:
title += '\nDraw Call Count: ' + str(int(self.drawCallCount))
plt.title(title)
plt.grid(which='both', axis='both')
plt.legend(fontsize='xx-small')
plt.plot()
if self.largeYValues:
# Plot again but with the full Y axis visible
figures.append(plt.figure(dpi=1200, frameon=False, figsize=(11, 8.5)))
for family in self.series:
plt.plot(
self.series[family]['x'],
self.series[family]['y'],
label=self.seriesLabels[family]
)
plt.xlabel('Benchmark Seed')
plt.ylabel('Time (' + self.timeUnit + ')')
title = self.name + ' ' + self.backend + ' (Complete)'
if self.drawCallCount != -1:
title += '\nDraw Call Count: ' + str(int(self.drawCallCount))
plt.title(title)
plt.grid(which='both', axis='both')
plt.legend(fontsize='xx-small')
plt.plot()
return figures
def writeCSV(self, writer):
# For now assume that all our series have the same x values
# this is true for now, but may differ in the future with benchmark changes
x_values = []
y_values = []
for family in self.series:
x_values = ['x'] + self.series[family]['x']
y_values.append([self.seriesLabels[family]] + self.series[family]['y'])
for name in self.optionalValues:
column = [name]
for key in self.optionalValues[name]:
column.append(self.optionalValues[name][key])
y_values.append(column)
writer.writerow([self.name, self.drawCallCount])
for line in range(len(x_values)):
row = [x_values[line]]
for series in range(len(y_values)):
row.append(y_values[series][line])
writer.writerow(row)
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'filename',
action='store',
help='Path to the JSON output from Google Benchmark'
)
parser.add_argument(
'-o',
'--output-pdf',
dest='outputPDF',
action='store',
default='output.pdf',
help='Filename to output the PDF of graphs to.'
)
parser.add_argument(
'-c',
'--output-csv',
dest='outputCSV',
action='store',
default='output.csv',
help='Filename to output the CSV data to.'
)
args = parser.parse_args()
jsonData = parseJSON(args.filename)
return processBenchmarkData(jsonData, args.outputPDF, args.outputCSV)
def error(message):
print(message)
exit(1)
def extractAttributesLabel(benchmarkResult):
# Possible attribute keys are:
# AntiAliasing
# HairlineStroke
# StrokedStyle
# FilledStyle
attributes = ['AntiAliasing', 'HairlineStroke', 'StrokedStyle', 'FilledStyle']
label = ''
for attr in attributes:
try:
if benchmarkResult[attr] != 0:
label += attr + ', '
except KeyError:
pass
return label[:-2]
def processBenchmarkData(benchmarkJSON, outputPDF, outputCSV):
benchmarkResultsData = {}
for benchmarkResult in benchmarkJSON:
# Skip aggregate results
if 'aggregate_name' in benchmarkResult:
continue
benchmarkVariant = benchmarkResult['name'].split('/')
# The final split is always `real_time` and can be discarded
benchmarkVariant.remove('real_time')
splits = len(benchmarkVariant)
# First split is always the benchmark function name
benchmarkName = benchmarkVariant[0]
# The last split is always the seeded value into the benchmark
benchmarkSeededValue = benchmarkVariant[splits - 1]
# The second last split is always the backend
benchmarkBackend = benchmarkVariant[splits - 2]
# Time taken (wall clock time) for benchmark to run
benchmarkRealTime = benchmarkResult['real_time']
benchmarkUnit = benchmarkResult['time_unit']
benchmarkFamilyIndex = benchmarkResult['family_index']
benchmarkFamilyLabel = ''
if splits > 3:
for i in range(1, splits - 2):
benchmarkFamilyLabel += benchmarkVariant[i] + ', '
benchmarkFamilyAttributes = extractAttributesLabel(benchmarkResult)
if benchmarkFamilyAttributes == '':
benchmarkFamilyLabel = benchmarkFamilyLabel[:-2]
else:
benchmarkFamilyLabel = benchmarkFamilyLabel + benchmarkFamilyAttributes
if 'DrawCallCount' in benchmarkResult:
benchmarkDrawCallCount = benchmarkResult['DrawCallCount']
else:
benchmarkDrawCallCount = -1
optional_keys = [
'DrawCallCount_Varies', 'VerbCount', 'PointCount', 'VertexCount',
'GlyphCount'
]
if benchmarkName not in benchmarkResultsData:
benchmarkResultsData[benchmarkName] = BenchmarkResult(
benchmarkName, benchmarkBackend, benchmarkUnit, benchmarkDrawCallCount
)
for key in optional_keys:
if key in benchmarkResult:
benchmarkResultsData[benchmarkName].addOptionalValue(
key, benchmarkSeededValue, benchmarkResult[key]
)
benchmarkResultsData[benchmarkName].addDataPoint(
benchmarkFamilyIndex, benchmarkSeededValue, benchmarkRealTime
)
benchmarkResultsData[benchmarkName].setFamilyLabel(
benchmarkFamilyIndex, benchmarkFamilyLabel
)
pp = pdfp(outputPDF)
csv_file = open(outputCSV, 'w')
csv_writer = csv.writer(csv_file)
for benchmark in benchmarkResultsData:
figures = benchmarkResultsData[benchmark].plot()
for fig in figures:
pp.savefig(fig)
benchmarkResultsData[benchmark].writeCSV(csv_writer)
pp.close()
def parseJSON(filename):
try:
jsonFile = open(filename, 'r')
except:
error('Unable to load file.')
try:
jsonData = json.load(jsonFile)
except JSONDecodeError:
error('Invalid JSON. Unable to parse.')
return jsonData['benchmarks']
if __name__ == '__main__':
sys.exit(main())