#!/usr/bin/env python # Copyright 2016 Google Inc. # # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. from __future__ import print_function from _benchresult import BenchResult from argparse import ArgumentParser from collections import defaultdict, namedtuple from datetime import datetime import operator import os import sys import tempfile import urllib import urlparse import webbrowser __argparse = ArgumentParser(description=""" Formats skpbench.py outputs as csv. This script can also be used to generate a Google sheet: (1) Install the "Office Editing for Docs, Sheets & Slides" Chrome extension: https://chrome.google.com/webstore/detail/office-editing-for-docs-s/gbkeegbaiigmenfmjfclcdgdpimamgkj (2) Update your global OS file associations to use Chrome for .csv files. (3) Run parseskpbench.py with the --open flag. """) __argparse.add_argument('-r', '--result', choices=['accum', 'median', 'max', 'min'], default='accum', help="result to use for cell values") __argparse.add_argument('-f', '--force', action='store_true', help='silently ignore warnings') __argparse.add_argument('-o', '--open', action='store_true', help="generate a temp file and open it (theoretically in a web browser)") __argparse.add_argument('-n', '--name', default='skpbench_%s' % datetime.now().strftime('%Y-%m-%d_%H.%M.%S.csv'), help="if using --open, a name for the temp file") __argparse.add_argument('sources', nargs='+', help="source files that contain skpbench results ('-' for stdin)") FLAGS = __argparse.parse_args() RESULT_QUALIFIERS = ('sample_ms', 'clock', 'metric') class FullConfig(namedtuple('fullconfig', ('config',) + RESULT_QUALIFIERS)): def qualified_name(self, qualifiers=RESULT_QUALIFIERS): return get_qualified_name(self.config.replace(',', ' '), {x:getattr(self, x) for x in qualifiers}) def get_qualified_name(name, qualifiers): if not qualifiers: return name else: args = ('%s=%s' % (k,v) for k,v in qualifiers.iteritems()) return '%s (%s)' % (name, ' '.join(args)) class Parser: def __init__(self): self.sheet_qualifiers = {x:None for x in RESULT_QUALIFIERS} self.config_qualifiers = set() self.fullconfigs = list() # use list to preserve the order. self.rows = defaultdict(dict) self.cols = defaultdict(dict) def parse_file(self, infile): for line in infile: match = BenchResult.match(line) if not match: continue fullconfig = FullConfig(*(match.get_string(x) for x in FullConfig._fields)) if not fullconfig in self.fullconfigs: self.fullconfigs.append(fullconfig) for qualifier, value in self.sheet_qualifiers.items(): if value is None: self.sheet_qualifiers[qualifier] = match.get_string(qualifier) elif value != match.get_string(qualifier): del self.sheet_qualifiers[qualifier] self.config_qualifiers.add(qualifier) self.rows[match.bench][fullconfig] = match.get_string(FLAGS.result) self.cols[fullconfig][match.bench] = getattr(match, FLAGS.result) def print_csv(self, outfile=sys.stdout): # Write the title. print(get_qualified_name(FLAGS.result, self.sheet_qualifiers), file=outfile) # Write the header. outfile.write('bench,') for fullconfig in self.fullconfigs: outfile.write('%s,' % fullconfig.qualified_name(self.config_qualifiers)) outfile.write('\n') # Write the rows. for bench, row in self.rows.iteritems(): outfile.write('%s,' % bench) for fullconfig in self.fullconfigs: if fullconfig in row: outfile.write('%s,' % row[fullconfig]) elif FLAGS.force: outfile.write('NULL,') else: raise ValueError("%s: missing value for %s. (use --force to ignore)" % (bench, fullconfig.qualified_name(self.config_qualifiers))) outfile.write('\n') # Add simple, literal averages. if len(self.rows) > 1: outfile.write('\n') self._print_computed_row('MEAN', lambda col: reduce(operator.add, col.values()) / len(col), outfile=outfile) self._print_computed_row('GEOMEAN', lambda col: reduce(operator.mul, col.values()) ** (1.0 / len(col)), outfile=outfile) def _print_computed_row(self, name, func, outfile=sys.stdout): outfile.write('%s,' % name) for fullconfig in self.fullconfigs: if len(self.cols[fullconfig]) != len(self.rows): outfile.write('NULL,') continue outfile.write('%.4g,' % func(self.cols[fullconfig])) outfile.write('\n') def main(): parser = Parser() # Parse the input files. for src in FLAGS.sources: if src == '-': parser.parse_file(sys.stdin) else: with open(src, mode='r') as infile: parser.parse_file(infile) # Print the csv. if not FLAGS.open: parser.print_csv() else: dirname = tempfile.mkdtemp() basename = FLAGS.name if os.path.splitext(basename)[1] != '.csv': basename += '.csv'; pathname = os.path.join(dirname, basename) with open(pathname, mode='w') as tmpfile: parser.print_csv(outfile=tmpfile) fileuri = urlparse.urljoin('file:', urllib.pathname2url(pathname)) print('opening %s' % fileuri) webbrowser.open(fileuri) if __name__ == '__main__': main()