~paparazzi-uav/paparazzi/v5.0-manual

« back to all changes in this revision

Viewing changes to sw/ext/opencv_bebop/opencv/modules/ts/misc/xls-report.py

  • Committer: Paparazzi buildbot
  • Date: 2016-05-18 15:00:29 UTC
  • Revision ID: felix.ruess+docbot@gmail.com-20160518150029-e8lgzi5kvb4p7un9
Manual import commit 4b8bbb730080dac23cf816b98908dacfabe2a8ec from v5.0 branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
 
 
3
"""
 
4
    This script can generate XLS reports from OpenCV tests' XML output files.
 
5
 
 
6
    To use it, first, create a directory for each machine you ran tests on.
 
7
    Each such directory will become a sheet in the report. Put each XML file
 
8
    into the corresponding directory.
 
9
 
 
10
    Then, create your configuration file(s). You can have a global configuration
 
11
    file (specified with the -c option), and per-sheet configuration files, which
 
12
    must be called sheet.conf and placed in the directory corresponding to the sheet.
 
13
    The settings in the per-sheet configuration file will override those in the
 
14
    global configuration file, if both are present.
 
15
 
 
16
    A configuration file must consist of a Python dictionary. The following keys
 
17
    will be recognized:
 
18
 
 
19
    * 'comparisons': [{'from': string, 'to': string}]
 
20
        List of configurations to compare performance between. For each item,
 
21
        the sheet will have a column showing speedup from configuration named
 
22
        'from' to configuration named "to".
 
23
 
 
24
    * 'configuration_matchers': [{'properties': {string: object}, 'name': string}]
 
25
        Instructions for matching test run property sets to configuration names.
 
26
 
 
27
        For each found XML file:
 
28
 
 
29
        1) All attributes of the root element starting with the prefix 'cv_' are
 
30
           placed in a dictionary, with the cv_ prefix stripped and the cv_module_name
 
31
           element deleted.
 
32
 
 
33
        2) The first matcher for which the XML's file property set contains the same
 
34
           keys with equal values as its 'properties' dictionary is searched for.
 
35
           A missing property can be matched by using None as the value.
 
36
 
 
37
           Corollary 1: you should place more specific matchers before less specific
 
38
           ones.
 
39
 
 
40
           Corollary 2: an empty 'properties' dictionary matches every property set.
 
41
 
 
42
        3) If a matching matcher is found, its 'name' string is presumed to be the name
 
43
           of the configuration the XML file corresponds to. A warning is printed if
 
44
           two different property sets match to the same configuration name.
 
45
 
 
46
        4) If a such a matcher isn't found, if --include-unmatched was specified, the
 
47
           configuration name is assumed to be the relative path from the sheet's
 
48
           directory to the XML file's containing directory. If the XML file isinstance
 
49
           directly inside the sheet's directory, the configuration name is instead
 
50
           a dump of all its properties. If --include-unmatched wasn't specified,
 
51
           the XML file is ignored and a warning is printed.
 
52
 
 
53
    * 'configurations': [string]
 
54
        List of names for compile-time and runtime configurations of OpenCV.
 
55
        Each item will correspond to a column of the sheet.
 
56
 
 
57
    * 'module_colors': {string: string}
 
58
        Mapping from module name to color name. In the sheet, cells containing module
 
59
        names from this mapping will be colored with the corresponding color. You can
 
60
        find the list of available colors here:
 
61
        <http://www.simplistix.co.uk/presentations/python-excel.pdf>.
 
62
 
 
63
    * 'sheet_name': string
 
64
        Name for the sheet. If this parameter is missing, the name of sheet's directory
 
65
        will be used.
 
66
 
 
67
    * 'sheet_properties': [(string, string)]
 
68
        List of arbitrary (key, value) pairs that somehow describe the sheet. Will be
 
69
        dumped into the first row of the sheet in string form.
 
70
 
 
71
    Note that all keys are optional, although to get useful results, you'll want to
 
72
    specify at least 'configurations' and 'configuration_matchers'.
 
73
 
 
74
    Finally, run the script. Use the --help option for usage information.
 
75
"""
 
76
 
 
77
from __future__ import division
 
78
 
 
79
import ast
 
80
import errno
 
81
import fnmatch
 
82
import logging
 
83
import numbers
 
84
import os, os.path
 
85
import re
 
86
 
 
87
from argparse import ArgumentParser
 
88
from glob import glob
 
89
from itertools import ifilter
 
90
 
 
91
import xlwt
 
92
 
 
93
from testlog_parser import parseLogFile
 
94
 
 
95
re_image_size = re.compile(r'^ \d+ x \d+$', re.VERBOSE)
 
96
re_data_type = re.compile(r'^ (?: 8 | 16 | 32 | 64 ) [USF] C [1234] $', re.VERBOSE)
 
97
 
 
98
time_style = xlwt.easyxf(num_format_str='#0.00')
 
99
no_time_style = xlwt.easyxf('pattern: pattern solid, fore_color gray25')
 
100
failed_style = xlwt.easyxf('pattern: pattern solid, fore_color red')
 
101
noimpl_style = xlwt.easyxf('pattern: pattern solid, fore_color orange')
 
102
style_dict = {"failed": failed_style, "noimpl":noimpl_style}
 
103
 
 
104
speedup_style = time_style
 
105
good_speedup_style = xlwt.easyxf('font: color green', num_format_str='#0.00')
 
106
bad_speedup_style = xlwt.easyxf('font: color red', num_format_str='#0.00')
 
107
no_speedup_style = no_time_style
 
108
error_speedup_style = xlwt.easyxf('pattern: pattern solid, fore_color orange')
 
109
header_style = xlwt.easyxf('font: bold true; alignment: horizontal centre, vertical top, wrap True')
 
110
subheader_style = xlwt.easyxf('alignment: horizontal centre, vertical top')
 
111
 
 
112
class Collector(object):
 
113
    def __init__(self, config_match_func, include_unmatched):
 
114
        self.__config_cache = {}
 
115
        self.config_match_func = config_match_func
 
116
        self.include_unmatched = include_unmatched
 
117
        self.tests = {}
 
118
        self.extra_configurations = set()
 
119
 
 
120
    # Format a sorted sequence of pairs as if it was a dictionary.
 
121
    # We can't just use a dictionary instead, since we want to preserve the sorted order of the keys.
 
122
    @staticmethod
 
123
    def __format_config_cache_key(pairs, multiline=False):
 
124
        return (
 
125
          ('{\n' if multiline else '{') +
 
126
          (',\n' if multiline else ', ').join(
 
127
             ('  ' if multiline else '') + repr(k) + ': ' + repr(v) for (k, v) in pairs) +
 
128
          ('\n}\n' if multiline else '}')
 
129
        )
 
130
 
 
131
    def collect_from(self, xml_path, default_configuration):
 
132
        run = parseLogFile(xml_path)
 
133
 
 
134
        module = run.properties['module_name']
 
135
 
 
136
        properties = run.properties.copy()
 
137
        del properties['module_name']
 
138
 
 
139
        props_key = tuple(sorted(properties.iteritems())) # dicts can't be keys
 
140
 
 
141
        if props_key in self.__config_cache:
 
142
            configuration = self.__config_cache[props_key]
 
143
        else:
 
144
            configuration = self.config_match_func(properties)
 
145
 
 
146
            if configuration is None:
 
147
                if self.include_unmatched:
 
148
                    if default_configuration is not None:
 
149
                        configuration = default_configuration
 
150
                    else:
 
151
                        configuration = Collector.__format_config_cache_key(props_key, multiline=True)
 
152
 
 
153
                    self.extra_configurations.add(configuration)
 
154
                else:
 
155
                    logging.warning('failed to match properties to a configuration: %s',
 
156
                        Collector.__format_config_cache_key(props_key))
 
157
 
 
158
            else:
 
159
                same_config_props = [it[0] for it in self.__config_cache.iteritems() if it[1] == configuration]
 
160
                if len(same_config_props) > 0:
 
161
                    logging.warning('property set %s matches the same configuration %r as property set %s',
 
162
                        Collector.__format_config_cache_key(props_key),
 
163
                        configuration,
 
164
                        Collector.__format_config_cache_key(same_config_props[0]))
 
165
 
 
166
            self.__config_cache[props_key] = configuration
 
167
 
 
168
        if configuration is None: return
 
169
 
 
170
        module_tests = self.tests.setdefault(module, {})
 
171
 
 
172
        for test in run.tests:
 
173
            test_results = module_tests.setdefault((test.shortName(), test.param()), {})
 
174
            new_result = test.get("gmean") if test.status == 'run' else test.status
 
175
            test_results[configuration] = min(
 
176
              test_results.get(configuration), new_result,
 
177
              key=lambda r: (1, r) if isinstance(r, numbers.Number) else
 
178
                            (2,) if r is not None else
 
179
                            (3,)
 
180
            ) # prefer lower result; prefer numbers to errors and errors to nothing
 
181
 
 
182
def make_match_func(matchers):
 
183
    def match_func(properties):
 
184
        for matcher in matchers:
 
185
            if all(properties.get(name) == value
 
186
                   for (name, value) in matcher['properties'].iteritems()):
 
187
                return matcher['name']
 
188
 
 
189
        return None
 
190
 
 
191
    return match_func
 
192
 
 
193
def main():
 
194
    arg_parser = ArgumentParser(description='Build an XLS performance report.')
 
195
    arg_parser.add_argument('sheet_dirs', nargs='+', metavar='DIR', help='directory containing perf test logs')
 
196
    arg_parser.add_argument('-o', '--output', metavar='XLS', default='report.xls', help='name of output file')
 
197
    arg_parser.add_argument('-c', '--config', metavar='CONF', help='global configuration file')
 
198
    arg_parser.add_argument('--include-unmatched', action='store_true',
 
199
        help='include results from XML files that were not recognized by configuration matchers')
 
200
    arg_parser.add_argument('--show-times-per-pixel', action='store_true',
 
201
        help='for tests that have an image size parameter, show per-pixel time, as well as total time')
 
202
 
 
203
    args = arg_parser.parse_args()
 
204
 
 
205
    logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)
 
206
 
 
207
    if args.config is not None:
 
208
        with open(args.config) as global_conf_file:
 
209
            global_conf = ast.literal_eval(global_conf_file.read())
 
210
    else:
 
211
        global_conf = {}
 
212
 
 
213
    wb = xlwt.Workbook()
 
214
 
 
215
    for sheet_path in args.sheet_dirs:
 
216
        try:
 
217
            with open(os.path.join(sheet_path, 'sheet.conf')) as sheet_conf_file:
 
218
                sheet_conf = ast.literal_eval(sheet_conf_file.read())
 
219
        except IOError as ioe:
 
220
            if ioe.errno != errno.ENOENT: raise
 
221
            sheet_conf = {}
 
222
            logging.debug('no sheet.conf for %s', sheet_path)
 
223
 
 
224
        sheet_conf = dict(global_conf.items() + sheet_conf.items())
 
225
 
 
226
        config_names = sheet_conf.get('configurations', [])
 
227
        config_matchers = sheet_conf.get('configuration_matchers', [])
 
228
 
 
229
        collector = Collector(make_match_func(config_matchers), args.include_unmatched)
 
230
 
 
231
        for root, _, filenames in os.walk(sheet_path):
 
232
            logging.info('looking in %s', root)
 
233
            for filename in fnmatch.filter(filenames, '*.xml'):
 
234
                if os.path.normpath(sheet_path) == os.path.normpath(root):
 
235
                  default_conf = None
 
236
                else:
 
237
                  default_conf = os.path.relpath(root, sheet_path)
 
238
                collector.collect_from(os.path.join(root, filename), default_conf)
 
239
 
 
240
        config_names.extend(sorted(collector.extra_configurations - set(config_names)))
 
241
 
 
242
        sheet = wb.add_sheet(sheet_conf.get('sheet_name', os.path.basename(os.path.abspath(sheet_path))))
 
243
 
 
244
        sheet_properties = sheet_conf.get('sheet_properties', [])
 
245
 
 
246
        sheet.write(0, 0, 'Properties:')
 
247
 
 
248
        sheet.write(0, 1,
 
249
          'N/A' if len(sheet_properties) == 0 else
 
250
          ' '.join(str(k) + '=' + repr(v) for (k, v) in sheet_properties))
 
251
 
 
252
        sheet.row(2).height = 800
 
253
        sheet.panes_frozen = True
 
254
        sheet.remove_splits = True
 
255
 
 
256
        sheet_comparisons = sheet_conf.get('comparisons', [])
 
257
 
 
258
        row = 2
 
259
 
 
260
        col = 0
 
261
 
 
262
        for (w, caption) in [
 
263
                (2500, 'Module'),
 
264
                (10000, 'Test'),
 
265
                (2000, 'Image\nwidth'),
 
266
                (2000, 'Image\nheight'),
 
267
                (2000, 'Data\ntype'),
 
268
                (7500, 'Other parameters')]:
 
269
            sheet.col(col).width = w
 
270
            if args.show_times_per_pixel:
 
271
                sheet.write_merge(row, row + 1, col, col, caption, header_style)
 
272
            else:
 
273
                sheet.write(row, col, caption, header_style)
 
274
            col += 1
 
275
 
 
276
        for config_name in config_names:
 
277
            if args.show_times_per_pixel:
 
278
                sheet.col(col).width = 3000
 
279
                sheet.col(col + 1).width = 3000
 
280
                sheet.write_merge(row, row, col, col + 1, config_name, header_style)
 
281
                sheet.write(row + 1, col, 'total, ms', subheader_style)
 
282
                sheet.write(row + 1, col + 1, 'per pixel, ns', subheader_style)
 
283
                col += 2
 
284
            else:
 
285
                sheet.col(col).width = 4000
 
286
                sheet.write(row, col, config_name, header_style)
 
287
                col += 1
 
288
 
 
289
        col += 1 # blank column between configurations and comparisons
 
290
 
 
291
        for comp in sheet_comparisons:
 
292
            sheet.col(col).width = 4000
 
293
            caption = comp['to'] + '\nvs\n' + comp['from']
 
294
            if args.show_times_per_pixel:
 
295
                sheet.write_merge(row, row + 1, col, col, caption, header_style)
 
296
            else:
 
297
                sheet.write(row, col, caption, header_style)
 
298
            col += 1
 
299
 
 
300
        row += 2 if args.show_times_per_pixel else 1
 
301
 
 
302
        sheet.horz_split_pos = row
 
303
        sheet.horz_split_first_visible = row
 
304
 
 
305
        module_colors = sheet_conf.get('module_colors', {})
 
306
        module_styles = {module: xlwt.easyxf('pattern: pattern solid, fore_color {}'.format(color))
 
307
                         for module, color in module_colors.iteritems()}
 
308
 
 
309
        for module, tests in sorted(collector.tests.iteritems()):
 
310
            for ((test, param), configs) in sorted(tests.iteritems()):
 
311
                sheet.write(row, 0, module, module_styles.get(module, xlwt.Style.default_style))
 
312
                sheet.write(row, 1, test)
 
313
 
 
314
                param_list = param[1:-1].split(', ') if param.startswith('(') and param.endswith(')') else [param]
 
315
 
 
316
                image_size = next(ifilter(re_image_size.match, param_list), None)
 
317
                if image_size is not None:
 
318
                    (image_width, image_height) = map(int, image_size.split('x', 1))
 
319
                    sheet.write(row, 2, image_width)
 
320
                    sheet.write(row, 3, image_height)
 
321
                    del param_list[param_list.index(image_size)]
 
322
 
 
323
                data_type = next(ifilter(re_data_type.match, param_list), None)
 
324
                if data_type is not None:
 
325
                    sheet.write(row, 4, data_type)
 
326
                    del param_list[param_list.index(data_type)]
 
327
 
 
328
                sheet.row(row).write(5, ' | '.join(param_list))
 
329
 
 
330
                col = 6
 
331
 
 
332
                for c in config_names:
 
333
                    if c in configs:
 
334
                        sheet.write(row, col, configs[c], style_dict.get(configs[c], time_style))
 
335
                    else:
 
336
                        sheet.write(row, col, None, no_time_style)
 
337
                    col += 1
 
338
                    if args.show_times_per_pixel:
 
339
                        sheet.write(row, col,
 
340
                          xlwt.Formula('{0} * 1000000 / ({1} * {2})'.format(
 
341
                              xlwt.Utils.rowcol_to_cell(row, col - 1),
 
342
                              xlwt.Utils.rowcol_to_cell(row, 2),
 
343
                              xlwt.Utils.rowcol_to_cell(row, 3)
 
344
                          )),
 
345
                          time_style
 
346
                        )
 
347
                        col += 1
 
348
 
 
349
                col += 1 # blank column
 
350
 
 
351
                for comp in sheet_comparisons:
 
352
                    cmp_from = configs.get(comp["from"])
 
353
                    cmp_to = configs.get(comp["to"])
 
354
 
 
355
                    if isinstance(cmp_from, numbers.Number) and isinstance(cmp_to, numbers.Number):
 
356
                        try:
 
357
                            speedup = cmp_from / cmp_to
 
358
                            sheet.write(row, col, speedup, good_speedup_style if speedup > 1.1 else
 
359
                                                           bad_speedup_style  if speedup < 0.9 else
 
360
                                                           speedup_style)
 
361
                        except ArithmeticError as e:
 
362
                            sheet.write(row, col, None, error_speedup_style)
 
363
                    else:
 
364
                        sheet.write(row, col, None, no_speedup_style)
 
365
 
 
366
                    col += 1
 
367
 
 
368
                row += 1
 
369
                if row % 1000 == 0: sheet.flush_row_data()
 
370
 
 
371
    wb.save(args.output)
 
372
 
 
373
if __name__ == '__main__':
 
374
    main()