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 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 ('cssmin', 'YUI Port'), 85 ('rcssmin', '|rcssmin|'), 86 ('_rcssmin', r'_\ |rcssmin|'), 87 ] 88 benched_per_table = 2 89 90 results = sorted(results, reverse=True) 91 92 # First we transform our data into a table (list of lists) 93 pythons, widths = [], [0] * (benched_per_table + 1) 94 last_version = None 95 for version, _, result in results: 96 version = uni(version) 97 if not(last_version is None or version.startswith('2.')): 98 continue 99 last_version = version 100 101 namesub = _re.compile(r'(?:-\d+(?:\.\d+)*)?\.css$').sub 102 result = iter(result) 103 tables = [] 104 105 # given our data it's easier to create the table transposed... 106 for benched in result: 107 rows = [['Name'] + [desc for _, desc in names]] 108 for _ in range(benched_per_table): 109 if _: 110 try: 111 benched = next(result) 112 except StopIteration: 113 rows.append([''] + ['' for _ in names]) 114 continue 115 116 times = dict(( 117 uni(port), (time, benched['sizes'][idx]) 118 ) for idx, (port, time) in enumerate(benched['times'])) 119 columns = ['%s (%.1f)' % ( 120 namesub('', _os.path.basename(uni(benched['filename']))), 121 benched['size'] / 1024.0, 122 )] 123 for idx, (port, _) in enumerate(names): 124 if port not in times: 125 columns.append('n/a') 126 continue 127 time, size = times[port] 128 if time is None: 129 columns.append('(failed)') 130 continue 131 columns.append('%s%.2f ms (%.1f %s)' % ( 132 idx == 0 and ' ' or '', 133 time, 134 size / 1024.0, 135 idx == 0 and '\\*' or ['=', '>', '<'][ 136 cmp(size, benched['sizes'][0]) 137 ], 138 )) 139 rows.append(columns) 140 141 # calculate column widths (global for all tables) 142 for idx, row in enumerate(rows): 143 widths[idx] = max(widths[idx], max(map(len, row))) 144 145 # ... and transpose it back. 146 tables.append(zip(*rows)) 147 pythons.append((version, tables)) 148 149 if last_version.startswith('2.'): 150 break 151 152 # Second we create a rest table from it 153 lines = [] 154 separator = lambda c='-': '+'.join([''] + [ 155 c * (width + 2) for width in widths 156 ] + ['']) 157 158 for idx, (version, tables) in enumerate(pythons): 159 if idx: 160 lines.append('') 161 lines.append('') 162 163 line = 'Python %s' % (version,) 164 lines.append(line) 165 lines.append('~' * len(line)) 166 167 for table in tables: 168 lines.append('') 169 lines.append('.. rst-class:: benchmark') 170 lines.append('') 171 172 for idx, row in enumerate(table): 173 if idx == 0: 174 # header 175 lines.append(separator()) 176 lines.append('|'.join([''] + [ 177 ' %s%*s ' % (col, len(col) - width, '') 178 for width, col in zip(widths, row) 179 ] + [''])) 180 lines.append(separator('=')) 181 else: # data 182 lines.append('|'.join([''] + [ 183 j == 0 and ( 184 ' %s%*s ' % (col, len(col) - widths[j], '') 185 ) or ( 186 ['%*s ', ' %*s '][idx == 1] % (widths[j], col) 187 ) 188 for j, col in enumerate(row) 189 ] + [''])) 190 lines.append(separator()) 191 192 fplines = [] 193 fp = open(filename) 194 try: 195 fpiter = iter(fp) 196 for line in fpiter: 197 line = line.rstrip() 198 if line == '.. begin tables': 199 buf = [] 200 for line in fpiter: 201 line = line.rstrip() 202 if line == '.. end tables': 203 fplines.append('.. begin tables') 204 fplines.append('') 205 fplines.extend(lines) 206 fplines.append('') 207 fplines.append('.. end tables') 208 buf = [] 209 break 210 else: 211 buf.append(line) 212 else: 213 fplines.extend(buf) 214 _sys.stderr.write("Placeholder container not found!\n") 215 else: 216 fplines.append(line) 217 finally: 218 fp.close() 219 220 fp = open(filename, 'w') 221 try: 222 fp.write('\n'.join(fplines) + '\n') 223 finally: 224 fp.close() 225 226 227def write_plain(filename, results): 228 """ 229 Output plain benchmark results 230 231 :Parameters: 232 `filename` : ``str`` 233 Filename to write to 234 235 `results` : ``list`` 236 Results 237 """ 238 lines = [] 239 results = sorted(results, reverse=True) 240 for idx, (version, import_notes, result) in enumerate(results): 241 if idx: 242 lines.append('') 243 lines.append('') 244 245 lines.append('$ python%s -OO bench/main.py bench/*.css' % ( 246 '.'.join(version.split('.')[:2]) 247 )) 248 lines.append('~' * 72) 249 for note in import_notes: 250 lines.append(uni(note)) 251 lines.append('Python Release: %s' % (version,)) 252 253 for single in result: 254 lines.append('') 255 lines.append('Benchmarking %r... (%.1f KiB)' % ( 256 uni(single['filename']), single['size'] / 1024.0 257 )) 258 for msg in single['messages']: 259 lines.append(msg) 260 times = [] 261 space = max([len(uni(port)) for port, _ in single['times']]) 262 for idx, (port, time) in enumerate(single['times']): 263 port = uni(port) 264 if time is None: 265 lines.append(" FAILED %s" % (port,)) 266 else: 267 times.append(time) 268 lines.append( 269 " Timing %s%s ... (%5.1f KiB %s) %8.2f ms" % ( 270 port, 271 " " * (space - len(port)), 272 single['sizes'][idx] / 1024.0, 273 idx == 0 and '*' or ['=', '>', '<'][ 274 cmp(single['sizes'][idx], single['sizes'][0]) 275 ], 276 time 277 ) 278 ) 279 if len(times) > 1: 280 lines[-1] += " (factor: %s)" % (', '.join([ 281 '%.2f' % (timed / time) for timed in times[:-1] 282 ])) 283 284 lines.append('') 285 lines.append('') 286 lines.append('# vim: nowrap') 287 fp = open(filename, 'w') 288 try: 289 fp.write('\n'.join(lines) + '\n') 290 finally: 291 fp.close() 292 293 294def main(argv=None): 295 """ Main """ 296 import getopt as _getopt 297 import pickle as _pickle 298 299 if argv is None: 300 argv = _sys.argv[1:] 301 try: 302 opts, args = _getopt.getopt(argv, "hp:t:", ["help"]) 303 except getopt.GetoptError: 304 e = _sys.exc_info()[0](_sys.exc_info()[1]) 305 print >> _sys.stderr, "%s\nTry %s -mbench.write --help" % ( 306 e, 307 _os.path.basename(_sys.executable), 308 ) 309 _sys.exit(2) 310 311 plain, table = None, None 312 for key, value in opts: 313 if key in ("-h", "--help"): 314 print >> _sys.stderr, ( 315 "%s -mbench.write [-p plain] [-t table] <pickled" % ( 316 _os.path.basename(_sys.executable), 317 ) 318 ) 319 _sys.exit(0) 320 elif key == '-p': 321 plain = str(value) 322 elif key == '-t': 323 table = str(value) 324 325 struct = [] 326 _sys.stdin = getattr(_sys.stdin, 'detach', lambda: _sys.stdin)() 327 try: 328 while True: 329 version, import_notes, result = _pickle.load(_sys.stdin) 330 if hasattr(version, 'decode'): 331 version = version.decode('latin-1') 332 struct.append((version, import_notes, result)) 333 except EOFError: 334 pass 335 336 if plain: 337 write_plain(plain, struct) 338 339 if table: 340 write_table(table, struct) 341 342 343if __name__ == '__main__': 344 main() 345