xref: /aosp_15_r20/external/skia/tools/skpbench/sheet.py (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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