1*c8dee2aaSAndroid Build Coastguard Worker#!/usr/bin/env python 2*c8dee2aaSAndroid Build Coastguard Worker 3*c8dee2aaSAndroid Build Coastguard Worker# Copyright 2016 Google Inc. 4*c8dee2aaSAndroid Build Coastguard Worker# 5*c8dee2aaSAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 6*c8dee2aaSAndroid Build Coastguard Worker# found in the LICENSE file. 7*c8dee2aaSAndroid Build Coastguard Worker 8*c8dee2aaSAndroid Build Coastguard Workerfrom __future__ import print_function 9*c8dee2aaSAndroid Build Coastguard Workerfrom _benchresult import BenchResult 10*c8dee2aaSAndroid Build Coastguard Workerfrom argparse import ArgumentParser 11*c8dee2aaSAndroid Build Coastguard Workerfrom collections import defaultdict, namedtuple 12*c8dee2aaSAndroid Build Coastguard Workerfrom datetime import datetime 13*c8dee2aaSAndroid Build Coastguard Workerimport operator 14*c8dee2aaSAndroid Build Coastguard Workerimport os 15*c8dee2aaSAndroid Build Coastguard Workerimport sys 16*c8dee2aaSAndroid Build Coastguard Workerimport tempfile 17*c8dee2aaSAndroid Build Coastguard Workerimport urllib 18*c8dee2aaSAndroid Build Coastguard Workerimport urlparse 19*c8dee2aaSAndroid Build Coastguard Workerimport webbrowser 20*c8dee2aaSAndroid Build Coastguard Worker 21*c8dee2aaSAndroid Build Coastguard Worker__argparse = ArgumentParser(description=""" 22*c8dee2aaSAndroid Build Coastguard Worker 23*c8dee2aaSAndroid Build Coastguard WorkerFormats skpbench.py outputs as csv. 24*c8dee2aaSAndroid Build Coastguard Worker 25*c8dee2aaSAndroid Build Coastguard WorkerThis script can also be used to generate a Google sheet: 26*c8dee2aaSAndroid Build Coastguard Worker 27*c8dee2aaSAndroid Build Coastguard Worker(1) Install the "Office Editing for Docs, Sheets & Slides" Chrome extension: 28*c8dee2aaSAndroid Build Coastguard Worker https://chrome.google.com/webstore/detail/office-editing-for-docs-s/gbkeegbaiigmenfmjfclcdgdpimamgkj 29*c8dee2aaSAndroid Build Coastguard Worker 30*c8dee2aaSAndroid Build Coastguard Worker(2) Update your global OS file associations to use Chrome for .csv files. 31*c8dee2aaSAndroid Build Coastguard Worker 32*c8dee2aaSAndroid Build Coastguard Worker(3) Run parseskpbench.py with the --open flag. 33*c8dee2aaSAndroid Build Coastguard Worker 34*c8dee2aaSAndroid Build Coastguard Worker""") 35*c8dee2aaSAndroid Build Coastguard Worker 36*c8dee2aaSAndroid Build Coastguard Worker__argparse.add_argument('-r', '--result', 37*c8dee2aaSAndroid Build Coastguard Worker choices=['accum', 'median', 'max', 'min'], default='accum', 38*c8dee2aaSAndroid Build Coastguard Worker help="result to use for cell values") 39*c8dee2aaSAndroid Build Coastguard Worker__argparse.add_argument('-f', '--force', 40*c8dee2aaSAndroid Build Coastguard Worker action='store_true', help='silently ignore warnings') 41*c8dee2aaSAndroid Build Coastguard Worker__argparse.add_argument('-o', '--open', 42*c8dee2aaSAndroid Build Coastguard Worker action='store_true', 43*c8dee2aaSAndroid Build Coastguard Worker help="generate a temp file and open it (theoretically in a web browser)") 44*c8dee2aaSAndroid Build Coastguard Worker__argparse.add_argument('-n', '--name', 45*c8dee2aaSAndroid Build Coastguard Worker default='skpbench_%s' % datetime.now().strftime('%Y-%m-%d_%H.%M.%S.csv'), 46*c8dee2aaSAndroid Build Coastguard Worker help="if using --open, a name for the temp file") 47*c8dee2aaSAndroid Build Coastguard Worker__argparse.add_argument('sources', 48*c8dee2aaSAndroid Build Coastguard Worker nargs='+', help="source files that contain skpbench results ('-' for stdin)") 49*c8dee2aaSAndroid Build Coastguard Worker 50*c8dee2aaSAndroid Build Coastguard WorkerFLAGS = __argparse.parse_args() 51*c8dee2aaSAndroid Build Coastguard Worker 52*c8dee2aaSAndroid Build Coastguard WorkerRESULT_QUALIFIERS = ('sample_ms', 'clock', 'metric') 53*c8dee2aaSAndroid Build Coastguard Worker 54*c8dee2aaSAndroid Build Coastguard Workerclass FullConfig(namedtuple('fullconfig', ('config',) + RESULT_QUALIFIERS)): 55*c8dee2aaSAndroid Build Coastguard Worker def qualified_name(self, qualifiers=RESULT_QUALIFIERS): 56*c8dee2aaSAndroid Build Coastguard Worker return get_qualified_name(self.config.replace(',', ' '), 57*c8dee2aaSAndroid Build Coastguard Worker {x:getattr(self, x) for x in qualifiers}) 58*c8dee2aaSAndroid Build Coastguard Worker 59*c8dee2aaSAndroid Build Coastguard Workerdef get_qualified_name(name, qualifiers): 60*c8dee2aaSAndroid Build Coastguard Worker if not qualifiers: 61*c8dee2aaSAndroid Build Coastguard Worker return name 62*c8dee2aaSAndroid Build Coastguard Worker else: 63*c8dee2aaSAndroid Build Coastguard Worker args = ('%s=%s' % (k,v) for k,v in qualifiers.iteritems()) 64*c8dee2aaSAndroid Build Coastguard Worker return '%s (%s)' % (name, ' '.join(args)) 65*c8dee2aaSAndroid Build Coastguard Worker 66*c8dee2aaSAndroid Build Coastguard Workerclass Parser: 67*c8dee2aaSAndroid Build Coastguard Worker def __init__(self): 68*c8dee2aaSAndroid Build Coastguard Worker self.sheet_qualifiers = {x:None for x in RESULT_QUALIFIERS} 69*c8dee2aaSAndroid Build Coastguard Worker self.config_qualifiers = set() 70*c8dee2aaSAndroid Build Coastguard Worker self.fullconfigs = list() # use list to preserve the order. 71*c8dee2aaSAndroid Build Coastguard Worker self.rows = defaultdict(dict) 72*c8dee2aaSAndroid Build Coastguard Worker self.cols = defaultdict(dict) 73*c8dee2aaSAndroid Build Coastguard Worker 74*c8dee2aaSAndroid Build Coastguard Worker def parse_file(self, infile): 75*c8dee2aaSAndroid Build Coastguard Worker for line in infile: 76*c8dee2aaSAndroid Build Coastguard Worker match = BenchResult.match(line) 77*c8dee2aaSAndroid Build Coastguard Worker if not match: 78*c8dee2aaSAndroid Build Coastguard Worker continue 79*c8dee2aaSAndroid Build Coastguard Worker 80*c8dee2aaSAndroid Build Coastguard Worker fullconfig = FullConfig(*(match.get_string(x) 81*c8dee2aaSAndroid Build Coastguard Worker for x in FullConfig._fields)) 82*c8dee2aaSAndroid Build Coastguard Worker if not fullconfig in self.fullconfigs: 83*c8dee2aaSAndroid Build Coastguard Worker self.fullconfigs.append(fullconfig) 84*c8dee2aaSAndroid Build Coastguard Worker 85*c8dee2aaSAndroid Build Coastguard Worker for qualifier, value in self.sheet_qualifiers.items(): 86*c8dee2aaSAndroid Build Coastguard Worker if value is None: 87*c8dee2aaSAndroid Build Coastguard Worker self.sheet_qualifiers[qualifier] = match.get_string(qualifier) 88*c8dee2aaSAndroid Build Coastguard Worker elif value != match.get_string(qualifier): 89*c8dee2aaSAndroid Build Coastguard Worker del self.sheet_qualifiers[qualifier] 90*c8dee2aaSAndroid Build Coastguard Worker self.config_qualifiers.add(qualifier) 91*c8dee2aaSAndroid Build Coastguard Worker 92*c8dee2aaSAndroid Build Coastguard Worker self.rows[match.bench][fullconfig] = match.get_string(FLAGS.result) 93*c8dee2aaSAndroid Build Coastguard Worker self.cols[fullconfig][match.bench] = getattr(match, FLAGS.result) 94*c8dee2aaSAndroid Build Coastguard Worker 95*c8dee2aaSAndroid Build Coastguard Worker def print_csv(self, outfile=sys.stdout): 96*c8dee2aaSAndroid Build Coastguard Worker # Write the title. 97*c8dee2aaSAndroid Build Coastguard Worker print(get_qualified_name(FLAGS.result, self.sheet_qualifiers), file=outfile) 98*c8dee2aaSAndroid Build Coastguard Worker 99*c8dee2aaSAndroid Build Coastguard Worker # Write the header. 100*c8dee2aaSAndroid Build Coastguard Worker outfile.write('bench,') 101*c8dee2aaSAndroid Build Coastguard Worker for fullconfig in self.fullconfigs: 102*c8dee2aaSAndroid Build Coastguard Worker outfile.write('%s,' % fullconfig.qualified_name(self.config_qualifiers)) 103*c8dee2aaSAndroid Build Coastguard Worker outfile.write('\n') 104*c8dee2aaSAndroid Build Coastguard Worker 105*c8dee2aaSAndroid Build Coastguard Worker # Write the rows. 106*c8dee2aaSAndroid Build Coastguard Worker for bench, row in self.rows.iteritems(): 107*c8dee2aaSAndroid Build Coastguard Worker outfile.write('%s,' % bench) 108*c8dee2aaSAndroid Build Coastguard Worker for fullconfig in self.fullconfigs: 109*c8dee2aaSAndroid Build Coastguard Worker if fullconfig in row: 110*c8dee2aaSAndroid Build Coastguard Worker outfile.write('%s,' % row[fullconfig]) 111*c8dee2aaSAndroid Build Coastguard Worker elif FLAGS.force: 112*c8dee2aaSAndroid Build Coastguard Worker outfile.write('NULL,') 113*c8dee2aaSAndroid Build Coastguard Worker else: 114*c8dee2aaSAndroid Build Coastguard Worker raise ValueError("%s: missing value for %s. (use --force to ignore)" % 115*c8dee2aaSAndroid Build Coastguard Worker (bench, 116*c8dee2aaSAndroid Build Coastguard Worker fullconfig.qualified_name(self.config_qualifiers))) 117*c8dee2aaSAndroid Build Coastguard Worker outfile.write('\n') 118*c8dee2aaSAndroid Build Coastguard Worker 119*c8dee2aaSAndroid Build Coastguard Worker # Add simple, literal averages. 120*c8dee2aaSAndroid Build Coastguard Worker if len(self.rows) > 1: 121*c8dee2aaSAndroid Build Coastguard Worker outfile.write('\n') 122*c8dee2aaSAndroid Build Coastguard Worker self._print_computed_row('MEAN', 123*c8dee2aaSAndroid Build Coastguard Worker lambda col: reduce(operator.add, col.values()) / len(col), 124*c8dee2aaSAndroid Build Coastguard Worker outfile=outfile) 125*c8dee2aaSAndroid Build Coastguard Worker self._print_computed_row('GEOMEAN', 126*c8dee2aaSAndroid Build Coastguard Worker lambda col: reduce(operator.mul, col.values()) ** (1.0 / len(col)), 127*c8dee2aaSAndroid Build Coastguard Worker outfile=outfile) 128*c8dee2aaSAndroid Build Coastguard Worker 129*c8dee2aaSAndroid Build Coastguard Worker def _print_computed_row(self, name, func, outfile=sys.stdout): 130*c8dee2aaSAndroid Build Coastguard Worker outfile.write('%s,' % name) 131*c8dee2aaSAndroid Build Coastguard Worker for fullconfig in self.fullconfigs: 132*c8dee2aaSAndroid Build Coastguard Worker if len(self.cols[fullconfig]) != len(self.rows): 133*c8dee2aaSAndroid Build Coastguard Worker outfile.write('NULL,') 134*c8dee2aaSAndroid Build Coastguard Worker continue 135*c8dee2aaSAndroid Build Coastguard Worker outfile.write('%.4g,' % func(self.cols[fullconfig])) 136*c8dee2aaSAndroid Build Coastguard Worker outfile.write('\n') 137*c8dee2aaSAndroid Build Coastguard Worker 138*c8dee2aaSAndroid Build Coastguard Workerdef main(): 139*c8dee2aaSAndroid Build Coastguard Worker parser = Parser() 140*c8dee2aaSAndroid Build Coastguard Worker 141*c8dee2aaSAndroid Build Coastguard Worker # Parse the input files. 142*c8dee2aaSAndroid Build Coastguard Worker for src in FLAGS.sources: 143*c8dee2aaSAndroid Build Coastguard Worker if src == '-': 144*c8dee2aaSAndroid Build Coastguard Worker parser.parse_file(sys.stdin) 145*c8dee2aaSAndroid Build Coastguard Worker else: 146*c8dee2aaSAndroid Build Coastguard Worker with open(src, mode='r') as infile: 147*c8dee2aaSAndroid Build Coastguard Worker parser.parse_file(infile) 148*c8dee2aaSAndroid Build Coastguard Worker 149*c8dee2aaSAndroid Build Coastguard Worker # Print the csv. 150*c8dee2aaSAndroid Build Coastguard Worker if not FLAGS.open: 151*c8dee2aaSAndroid Build Coastguard Worker parser.print_csv() 152*c8dee2aaSAndroid Build Coastguard Worker else: 153*c8dee2aaSAndroid Build Coastguard Worker dirname = tempfile.mkdtemp() 154*c8dee2aaSAndroid Build Coastguard Worker basename = FLAGS.name 155*c8dee2aaSAndroid Build Coastguard Worker if os.path.splitext(basename)[1] != '.csv': 156*c8dee2aaSAndroid Build Coastguard Worker basename += '.csv'; 157*c8dee2aaSAndroid Build Coastguard Worker pathname = os.path.join(dirname, basename) 158*c8dee2aaSAndroid Build Coastguard Worker with open(pathname, mode='w') as tmpfile: 159*c8dee2aaSAndroid Build Coastguard Worker parser.print_csv(outfile=tmpfile) 160*c8dee2aaSAndroid Build Coastguard Worker fileuri = urlparse.urljoin('file:', urllib.pathname2url(pathname)) 161*c8dee2aaSAndroid Build Coastguard Worker print('opening %s' % fileuri) 162*c8dee2aaSAndroid Build Coastguard Worker webbrowser.open(fileuri) 163*c8dee2aaSAndroid Build Coastguard Worker 164*c8dee2aaSAndroid Build Coastguard Worker 165*c8dee2aaSAndroid Build Coastguard Workerif __name__ == '__main__': 166*c8dee2aaSAndroid Build Coastguard Worker main() 167