1#!/usr/bin/env python 2# -*- coding: ascii -*- 3r""" 4========================= 5 Write benchmark results 6========================= 7 8Write benchmark results. 9 10:Copyright: 11 12 Copyright 2014 - 2015 13 Andr\xe9 Malo or his licensors, as applicable 14 15:License: 16 17 Licensed under the Apache License, Version 2.0 (the "License"); 18 you may not use this file except in compliance with the License. 19 You may obtain a copy of the License at 20 21 http://www.apache.org/licenses/LICENSE-2.0 22 23 Unless required by applicable law or agreed to in writing, software 24 distributed under the License is distributed on an "AS IS" BASIS, 25 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 See the License for the specific language governing permissions and 27 limitations under the License. 28 29Usage:: 30 31 python -mbench.write [-p plain] [-t table] <pickled 32 33 -p plain Plain file to write to (like docs/BENCHMARKS). 34 -t table Table file to write to (like docs/_userdoc/benchmark.txt). 35 36""" 37if __doc__: 38 __doc__ = __doc__.encode('ascii').decode('unicode_escape') 39__author__ = r"Andr\xe9 Malo".encode('ascii').decode('unicode_escape') 40__docformat__ = "restructuredtext en" 41__license__ = "Apache License, Version 2.0" 42__version__ = "1.0.0" 43 44import os as _os 45import re as _re 46import sys as _sys 47 48 49try: 50 unicode 51except NameError: 52 def uni(v): 53 if hasattr(v, 'decode'): 54 return v.decode('latin-1') 55 return str(v) 56else: 57 def uni(v): 58 if isinstance(v, unicode): 59 return v.encode('utf-8') 60 return str(v) 61 62 63def write_table(filename, results): 64 """ 65 Output tabled benchmark results 66 67 :Parameters: 68 `filename` : ``str`` 69 Filename to write to 70 71 `results` : ``list`` 72 Results 73 """ 74 try: 75 next 76 except NameError: 77 next = lambda i: (getattr(i, 'next', None) or i.__next__)() 78 try: 79 cmp 80 except NameError: 81 cmp = lambda a, b: (a > b) - (a < b) 82 83 names = [ 84 ('simple_port', 'Simple Port'), 85 ('jsmin_2_0_9', 'jsmin 2.0.9'), 86 ('rjsmin', '|rjsmin|'), 87 ('_rjsmin', r'_\ |rjsmin|'), 88 ] 89 benched_per_table = 2 90 91 results = sorted(results, reverse=True) 92 93 # First we transform our data into a table (list of lists) 94 pythons, widths = [], [0] * (benched_per_table + 1) 95 last_version = None 96 for version, _, result in results: 97 version = uni(version) 98 if not(last_version is None or version.startswith('2.')): 99 continue 100 last_version = version 101 102 namesub = _re.compile(r'(?:-\d+(?:\.\d+)*)?\.js$').sub 103 result = iter(result) 104 tables = [] 105 106 # given our data it's easier to create the table transposed... 107 for benched in result: 108 rows = [['Name'] + [desc for _, desc in names]] 109 for _ in range(benched_per_table): 110 if _: 111 try: 112 benched = next(result) 113 except StopIteration: 114 rows.append([''] + ['' for _ in names]) 115 continue 116 117 times = dict(( 118 uni(port), (time, benched['sizes'][idx]) 119 ) for idx, (port, time) in enumerate(benched['times'])) 120 columns = ['%s (%.1f)' % ( 121 namesub('', _os.path.basename(uni(benched['filename']))), 122 benched['size'] / 1024.0, 123 )] 124 for idx, (port, _) in enumerate(names): 125 if port not in times: 126 columns.append('n/a') 127 continue 128 time, size = times[port] 129 if time is None: 130 columns.append('(failed)') 131 continue 132 columns.append('%s%.2f ms (%.1f %s)' % ( 133 idx == 0 and ' ' or '', 134 time, 135 size / 1024.0, 136 idx == 0 and '\\*' or ['=', '>', '<'][ 137 cmp(size, benched['sizes'][0]) 138 ], 139 )) 140 rows.append(columns) 141 142 # calculate column widths (global for all tables) 143 for idx, row in enumerate(rows): 144 widths[idx] = max(widths[idx], max(map(len, row))) 145 146 # ... and transpose it back. 147 tables.append(zip(*rows)) 148 pythons.append((version, tables)) 149 150 if last_version.startswith('2.'): 151 break 152 153 # Second we create a rest table from it 154 lines = [] 155 separator = lambda c='-': '+'.join([''] + [ 156 c * (width + 2) for width in widths 157 ] + ['']) 158 159 for idx, (version, tables) in enumerate(pythons): 160 if idx: 161 lines.append('') 162 lines.append('') 163 164 line = 'Python %s' % (version,) 165 lines.append(line) 166 lines.append('~' * len(line)) 167 168 for table in tables: 169 lines.append('') 170 lines.append('.. rst-class:: benchmark') 171 lines.append('') 172 173 for idx, row in enumerate(table): 174 if idx == 0: 175 # header 176 lines.append(separator()) 177 lines.append('|'.join([''] + [ 178 ' %s%*s ' % (col, len(col) - width, '') 179 for width, col in zip(widths, row) 180 ] + [''])) 181 lines.append(separator('=')) 182 else: # data 183 lines.append('|'.join([''] + [ 184 j == 0 and ( 185 ' %s%*s ' % (col, len(col) - widths[j], '') 186 ) or ( 187 ['%*s ', ' %*s '][idx == 1] % (widths[j], col) 188 ) 189 for j, col in enumerate(row) 190 ] + [''])) 191 lines.append(separator()) 192 193 fplines = [] 194 fp = open(filename) 195 try: 196 fpiter = iter(fp) 197 for line in fpiter: 198 line = line.rstrip() 199 if line == '.. begin tables': 200 buf = [] 201 for line in fpiter: 202 line = line.rstrip() 203 if line == '.. end tables': 204 fplines.append('.. begin tables') 205 fplines.append('') 206 fplines.extend(lines) 207 fplines.append('') 208 fplines.append('.. end tables') 209 buf = [] 210 break 211 else: 212 buf.append(line) 213 else: 214 fplines.extend(buf) 215 _sys.stderr.write("Placeholder container not found!\n") 216 else: 217 fplines.append(line) 218 finally: 219 fp.close() 220 221 fp = open(filename, 'w') 222 try: 223 fp.write('\n'.join(fplines) + '\n') 224 finally: 225 fp.close() 226 227 228def write_plain(filename, results): 229 """ 230 Output plain benchmark results 231 232 :Parameters: 233 `filename` : ``str`` 234 Filename to write to 235 236 `results` : ``list`` 237 Results 238 """ 239 lines = [] 240 results = sorted(results, reverse=True) 241 for idx, (version, import_notes, result) in enumerate(results): 242 if idx: 243 lines.append('') 244 lines.append('') 245 246 lines.append('$ python%s -OO bench/main.py bench/*.js' % ( 247 '.'.join(version.split('.')[:2]) 248 )) 249 lines.append('~' * 72) 250 for note in import_notes: 251 lines.append(uni(note)) 252 lines.append('Python Release: %s' % (version,)) 253 254 for single in result: 255 lines.append('') 256 lines.append('Benchmarking %r... (%.1f KiB)' % ( 257 uni(single['filename']), single['size'] / 1024.0 258 )) 259 for msg in single['messages']: 260 lines.append(msg) 261 times = [] 262 space = max([len(uni(port)) for port, _ in single['times']]) 263 for idx, (port, time) in enumerate(single['times']): 264 port = uni(port) 265 if time is None: 266 lines.append(" FAILED %s" % (port,)) 267 else: 268 times.append(time) 269 lines.append( 270 " Timing %s%s ... (%5.1f KiB %s) %8.2f ms" % ( 271 port, 272 " " * (space - len(port)), 273 single['sizes'][idx] / 1024.0, 274 idx == 0 and '*' or ['=', '>', '<'][ 275 cmp(single['sizes'][idx], single['sizes'][0]) 276 ], 277 time 278 ) 279 ) 280 if len(times) > 1: 281 lines[-1] += " (factor: %s)" % (', '.join([ 282 '%.2f' % (timed / time) for timed in times[:-1] 283 ])) 284 285 lines.append('') 286 lines.append('') 287 lines.append('# vim: nowrap') 288 fp = open(filename, 'w') 289 try: 290 fp.write('\n'.join(lines) + '\n') 291 finally: 292 fp.close() 293 294 295def main(argv=None): 296 """ Main """ 297 import getopt as _getopt 298 import pickle as _pickle 299 300 if argv is None: 301 argv = _sys.argv[1:] 302 try: 303 opts, args = _getopt.getopt(argv, "hp:t:", ["help"]) 304 except getopt.GetoptError: 305 e = _sys.exc_info()[0](_sys.exc_info()[1]) 306 print >> _sys.stderr, "%s\nTry %s -mbench.write --help" % ( 307 e, 308 _os.path.basename(_sys.executable), 309 ) 310 _sys.exit(2) 311 312 plain, table = None, None 313 for key, value in opts: 314 if key in ("-h", "--help"): 315 print >> _sys.stderr, ( 316 "%s -mbench.write [-p plain] [-t table] <pickled" % ( 317 _os.path.basename(_sys.executable), 318 ) 319 ) 320 _sys.exit(0) 321 elif key == '-p': 322 plain = str(value) 323 elif key == '-t': 324 table = str(value) 325 326 struct = [] 327 _sys.stdin = getattr(_sys.stdin, 'detach', lambda: _sys.stdin)() 328 try: 329 while True: 330 version, import_notes, result = _pickle.load(_sys.stdin) 331 if hasattr(version, 'decode'): 332 version = version.decode('latin-1') 333 struct.append((version, import_notes, result)) 334 except EOFError: 335 pass 336 337 if plain: 338 write_plain(plain, struct) 339 340 if table: 341 write_table(table, struct) 342 343 344if __name__ == '__main__': 345 main() 346