2013-06-14 18:53:02 +08:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
from __future__ import division
|
|
|
|
|
|
|
|
import ast
|
2013-06-20 23:34:32 +08:00
|
|
|
import fnmatch
|
2013-06-14 18:53:02 +08:00
|
|
|
import logging
|
2013-06-20 18:27:51 +08:00
|
|
|
import numbers
|
2013-06-14 18:53:02 +08:00
|
|
|
import os, os.path
|
|
|
|
import re
|
|
|
|
|
|
|
|
from argparse import ArgumentParser
|
2013-06-18 17:36:20 +08:00
|
|
|
from collections import OrderedDict
|
2013-06-14 18:53:02 +08:00
|
|
|
from glob import glob
|
|
|
|
from itertools import ifilter
|
|
|
|
|
|
|
|
import xlwt
|
|
|
|
|
|
|
|
from testlog_parser import parseLogFile
|
|
|
|
|
|
|
|
# To build XLS report you neet to put your xmls (OpenCV tests output) in the
|
|
|
|
# following way:
|
|
|
|
#
|
|
|
|
# "root" --- folder, representing the whole XLS document. It contains several
|
|
|
|
# subfolders --- sheet-paths of the XLS document. Each sheet-path contains it's
|
|
|
|
# subfolders --- config-paths. Config-paths are columns of the sheet and
|
|
|
|
# they contains xmls files --- output of OpenCV modules testing.
|
|
|
|
# Config-path means OpenCV build configuration, including different
|
|
|
|
# options such as NEON, TBB, GPU enabling/disabling.
|
|
|
|
#
|
|
|
|
# root
|
|
|
|
# root\sheet_path
|
|
|
|
# root\sheet_path\configuration1 (column 1)
|
|
|
|
# root\sheet_path\configuration2 (column 2)
|
|
|
|
|
|
|
|
re_image_size = re.compile(r'^ \d+ x \d+$', re.VERBOSE)
|
|
|
|
re_data_type = re.compile(r'^ (?: 8 | 16 | 32 | 64 ) [USF] C [1234] $', re.VERBOSE)
|
|
|
|
|
|
|
|
time_style = xlwt.easyxf(num_format_str='#0.00')
|
|
|
|
no_time_style = xlwt.easyxf('pattern: pattern solid, fore_color gray25')
|
|
|
|
|
|
|
|
speedup_style = time_style
|
|
|
|
good_speedup_style = xlwt.easyxf('font: color green', num_format_str='#0.00')
|
|
|
|
bad_speedup_style = xlwt.easyxf('font: color red', num_format_str='#0.00')
|
|
|
|
no_speedup_style = no_time_style
|
|
|
|
error_speedup_style = xlwt.easyxf('pattern: pattern solid, fore_color orange')
|
2013-06-18 01:06:02 +08:00
|
|
|
header_style = xlwt.easyxf('font: bold true; alignment: horizontal centre, vertical top, wrap True')
|
2013-06-14 18:53:02 +08:00
|
|
|
|
2013-06-20 23:34:32 +08:00
|
|
|
class Collector(object):
|
|
|
|
def __init__(self, config_match_func):
|
|
|
|
self.__config_cache = {}
|
|
|
|
self.config_match_func = config_match_func
|
|
|
|
self.tests = {}
|
2013-06-14 18:53:02 +08:00
|
|
|
|
2013-06-21 17:43:16 +08:00
|
|
|
# Format a sorted sequence of pairs as if it was a dictionary.
|
|
|
|
# We can't just use a dictionary instead, since we want to preserve the sorted order of the keys.
|
|
|
|
@staticmethod
|
|
|
|
def __format_config_cache_key(pairs):
|
|
|
|
return '{' + ', '.join(repr(k) + ': ' + repr(v) for (k, v) in pairs) + '}'
|
|
|
|
|
2013-06-20 23:34:32 +08:00
|
|
|
def collect_from(self, xml_path):
|
|
|
|
run = parseLogFile(xml_path)
|
2013-06-14 18:53:02 +08:00
|
|
|
|
2013-06-20 23:34:32 +08:00
|
|
|
module = run.properties['module_name']
|
|
|
|
|
|
|
|
properties = run.properties.copy()
|
|
|
|
del properties['module_name']
|
|
|
|
|
|
|
|
props_key = tuple(sorted(properties.iteritems())) # dicts can't be keys
|
|
|
|
|
|
|
|
if props_key in self.__config_cache:
|
|
|
|
configuration = self.__config_cache[props_key]
|
|
|
|
else:
|
|
|
|
configuration = self.config_match_func(properties)
|
|
|
|
|
|
|
|
if configuration is None:
|
2013-06-21 17:43:16 +08:00
|
|
|
logging.warning('failed to match properties to a configuration: %s',
|
|
|
|
Collector.__format_config_cache_key(props_key))
|
2013-06-20 23:34:32 +08:00
|
|
|
else:
|
|
|
|
same_config_props = [it[0] for it in self.__config_cache.iteritems() if it[1] == configuration]
|
|
|
|
if len(same_config_props) > 0:
|
2013-06-21 17:43:16 +08:00
|
|
|
logging.warning('property set %s matches the same configuration %r as property set %s',
|
|
|
|
Collector.__format_config_cache_key(props_key),
|
|
|
|
configuration,
|
|
|
|
Collector.__format_config_cache_key(same_config_props[0]))
|
2013-06-20 23:34:32 +08:00
|
|
|
|
|
|
|
self.__config_cache[props_key] = configuration
|
|
|
|
|
|
|
|
if configuration is None: return
|
|
|
|
|
|
|
|
module_tests = self.tests.setdefault(module, OrderedDict())
|
|
|
|
|
|
|
|
for test in run.tests:
|
|
|
|
test_results = module_tests.setdefault((test.shortName(), test.param()), {})
|
|
|
|
test_results[configuration] = test.get("gmean") if test.status == 'run' else test.status
|
|
|
|
|
|
|
|
def make_match_func(matchers):
|
|
|
|
def match_func(properties):
|
|
|
|
for matcher in matchers:
|
|
|
|
if all(properties.get(name) == value
|
|
|
|
for (name, value) in matcher['properties'].iteritems()):
|
|
|
|
return matcher['name']
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
return match_func
|
2013-06-14 18:53:02 +08:00
|
|
|
|
|
|
|
def main():
|
|
|
|
arg_parser = ArgumentParser(description='Build an XLS performance report.')
|
|
|
|
arg_parser.add_argument('sheet_dirs', nargs='+', metavar='DIR', help='directory containing perf test logs')
|
|
|
|
arg_parser.add_argument('-o', '--output', metavar='XLS', default='report.xls', help='name of output file')
|
|
|
|
arg_parser.add_argument('-c', '--config', metavar='CONF', help='global configuration file')
|
|
|
|
|
|
|
|
args = arg_parser.parse_args()
|
|
|
|
|
|
|
|
logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)
|
|
|
|
|
|
|
|
if args.config is not None:
|
|
|
|
with open(args.config) as global_conf_file:
|
|
|
|
global_conf = ast.literal_eval(global_conf_file.read())
|
|
|
|
else:
|
|
|
|
global_conf = {}
|
|
|
|
|
|
|
|
wb = xlwt.Workbook()
|
|
|
|
|
|
|
|
for sheet_path in args.sheet_dirs:
|
|
|
|
try:
|
|
|
|
with open(os.path.join(sheet_path, 'sheet.conf')) as sheet_conf_file:
|
|
|
|
sheet_conf = ast.literal_eval(sheet_conf_file.read())
|
|
|
|
except Exception:
|
|
|
|
sheet_conf = {}
|
2013-06-20 23:39:02 +08:00
|
|
|
logging.debug('no sheet.conf for %s', sheet_path)
|
2013-06-14 18:53:02 +08:00
|
|
|
|
|
|
|
sheet_conf = dict(global_conf.items() + sheet_conf.items())
|
|
|
|
|
2013-06-20 23:34:32 +08:00
|
|
|
config_names = sheet_conf.get('configurations', [])
|
|
|
|
config_matchers = sheet_conf.get('configuration_matchers', [])
|
2013-06-14 18:53:02 +08:00
|
|
|
|
2013-06-20 23:34:32 +08:00
|
|
|
collector = Collector(make_match_func(config_matchers))
|
2013-06-14 18:53:02 +08:00
|
|
|
|
2013-06-20 23:34:32 +08:00
|
|
|
for root, _, filenames in os.walk(sheet_path):
|
|
|
|
logging.info('looking in %s', root)
|
|
|
|
for filename in fnmatch.filter(filenames, '*.xml'):
|
|
|
|
collector.collect_from(os.path.join(root, filename))
|
2013-06-14 18:53:02 +08:00
|
|
|
|
|
|
|
sheet = wb.add_sheet(sheet_conf.get('sheet_name', os.path.basename(os.path.abspath(sheet_path))))
|
|
|
|
|
|
|
|
sheet.row(0).height = 800
|
|
|
|
sheet.panes_frozen = True
|
|
|
|
sheet.remove_splits = True
|
|
|
|
sheet.horz_split_pos = 1
|
|
|
|
sheet.horz_split_first_visible = 1
|
|
|
|
|
|
|
|
sheet_comparisons = sheet_conf.get('comparisons', [])
|
|
|
|
|
|
|
|
for i, w in enumerate([2000, 15000, 2500, 2000, 15000]
|
|
|
|
+ (len(config_names) + 1 + len(sheet_comparisons)) * [3000]):
|
|
|
|
sheet.col(i).width = w
|
|
|
|
|
|
|
|
for i, caption in enumerate(['Module', 'Test', 'Image\nsize', 'Data\ntype', 'Parameters']
|
|
|
|
+ config_names + [None]
|
2013-06-20 18:27:51 +08:00
|
|
|
+ [comp['to'] + '\nvs\n' + comp['from'] for comp in sheet_comparisons]):
|
2013-06-14 18:53:02 +08:00
|
|
|
sheet.row(0).write(i, caption, header_style)
|
|
|
|
|
|
|
|
row = 1
|
|
|
|
|
|
|
|
module_colors = sheet_conf.get('module_colors', {})
|
|
|
|
module_styles = {module: xlwt.easyxf('pattern: pattern solid, fore_color {}'.format(color))
|
|
|
|
for module, color in module_colors.iteritems()}
|
|
|
|
|
2013-06-20 23:34:32 +08:00
|
|
|
for module, tests in sorted(collector.tests.iteritems()):
|
2013-06-18 17:36:20 +08:00
|
|
|
for ((test, param), configs) in tests.iteritems():
|
|
|
|
sheet.write(row, 0, module, module_styles.get(module, xlwt.Style.default_style))
|
|
|
|
sheet.write(row, 1, test)
|
|
|
|
|
|
|
|
param_list = param[1:-1].split(", ")
|
|
|
|
sheet.write(row, 2, next(ifilter(re_image_size.match, param_list), None))
|
|
|
|
sheet.write(row, 3, next(ifilter(re_data_type.match, param_list), None))
|
|
|
|
|
|
|
|
sheet.row(row).write(4, param)
|
|
|
|
for i, c in enumerate(config_names):
|
|
|
|
if c in configs:
|
|
|
|
sheet.write(row, 5 + i, configs[c], time_style)
|
|
|
|
else:
|
|
|
|
sheet.write(row, 5 + i, None, no_time_style)
|
|
|
|
|
|
|
|
for i, comp in enumerate(sheet_comparisons):
|
2013-06-20 18:27:51 +08:00
|
|
|
cmp_from = configs.get(comp["from"])
|
|
|
|
cmp_to = configs.get(comp["to"])
|
2013-06-18 17:36:20 +08:00
|
|
|
col = 5 + len(config_names) + 1 + i
|
|
|
|
|
2013-06-20 18:27:51 +08:00
|
|
|
if isinstance(cmp_from, numbers.Number) and isinstance(cmp_to, numbers.Number):
|
2013-06-18 17:36:20 +08:00
|
|
|
try:
|
2013-06-20 18:27:51 +08:00
|
|
|
speedup = cmp_from / cmp_to
|
2013-06-18 17:36:20 +08:00
|
|
|
sheet.write(row, col, speedup, good_speedup_style if speedup > 1.1 else
|
|
|
|
bad_speedup_style if speedup < 0.9 else
|
|
|
|
speedup_style)
|
|
|
|
except ArithmeticError as e:
|
|
|
|
sheet.write(row, col, None, error_speedup_style)
|
|
|
|
else:
|
|
|
|
sheet.write(row, col, None, no_speedup_style)
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
if row % 1000 == 0: sheet.flush_row_data()
|
2013-06-14 18:53:02 +08:00
|
|
|
|
|
|
|
wb.save(args.output)
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|