sheet.py 5.3 KB

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