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