| #!/usr/bin/env python2 |
| # |
| # Copyright 2017 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| # |
| # pylint: disable=cros-logging-import |
| """Transforms skia benchmark results to ones that crosperf can understand.""" |
| |
| from __future__ import print_function |
| |
| import itertools |
| import logging |
| import json |
| import sys |
| |
| # Turn the logging level to INFO before importing other autotest |
| # code, to avoid having failed import logging messages confuse the |
| # test_droid user. |
| logging.basicConfig(level=logging.INFO) |
| |
| # All of the results we care about, by name. |
| # Each of these *must* end in _ns, _us, _ms, or _s, since all the metrics we |
| # collect (so far) are related to time, and we alter the results based on the |
| # suffix of these strings (so we don't have 0.000421ms per sample, for example) |
| _RESULT_RENAMES = { |
| 'memset32_100000_640_480_nonrendering': 'memset_time_ms', |
| 'path_equality_50%_640_480_nonrendering': 'path_equality_ns', |
| 'sort_qsort_backward_640_480_nonrendering': 'qsort_us' |
| } |
| |
| |
| def _GetFamiliarName(name): |
| r = _RESULT_RENAMES[name] |
| return r if r else name |
| |
| |
| def _IsResultInteresting(name): |
| return name in _RESULT_RENAMES |
| |
| |
| def _GetTimeMultiplier(label_name): |
| """Given a time (in milliseconds), normalize it to what label_name expects. |
| |
| "What label_name expects" meaning "we pattern match against the last few |
| non-space chars in label_name." |
| |
| This expects the time unit to be separated from anything else by '_'. |
| """ |
| ms_mul = 1000 * 1000. |
| endings = [('_ns', 1), ('_us', 1000), |
| ('_ms', ms_mul), ('_s', ms_mul * 1000)] |
| for end, mul in endings: |
| if label_name.endswith(end): |
| return ms_mul / mul |
| raise ValueError('Unknown ending in "%s"; expecting one of %s' % |
| (label_name, [end for end, _ in endings])) |
| |
| |
| def _GetTimeDenom(ms): |
| """Given a list of times (in milliseconds), find a suitable time unit for them. |
| |
| Returns the unit name, and `ms` normalized to that time unit. |
| |
| >>> _GetTimeDenom([1, 2, 3]) |
| ('ms', [1.0, 2.0, 3.0]) |
| >>> _GetTimeDenom([.1, .2, .3]) |
| ('us', [100.0, 200.0, 300.0]) |
| """ |
| |
| ms_mul = 1000 * 1000 |
| units = [('us', 1000), ('ms', ms_mul), ('s', ms_mul * 1000)] |
| for name, mul in reversed(units): |
| normalized = [float(t) * ms_mul / mul for t in ms] |
| average = sum(normalized) / len(normalized) |
| if all(n > 0.1 for n in normalized) and average >= 1: |
| return name, normalized |
| |
| normalized = [float(t) * ms_mul for t in ms] |
| return 'ns', normalized |
| |
| |
| def _TransformBenchmarks(raw_benchmarks): |
| # We get {"results": {"bench_name": Results}} |
| # where |
| # Results = {"config_name": {"samples": [float], etc.}} |
| # |
| # We want {"data": {"skia": [[BenchmarkData]]}, |
| # "platforms": ["platform1, ..."]} |
| # where |
| # BenchmarkData = {"bench_name": bench_samples[N], ..., "retval": 0} |
| # |
| # Note that retval is awkward -- crosperf's JSON reporter reports the result |
| # as a failure if it's not there. Everything else treats it like a |
| # statistic... |
| benchmarks = raw_benchmarks['results'] |
| results = [] |
| for bench_name, bench_result in benchmarks.iteritems(): |
| try: |
| for cfg_name, keyvals in bench_result.iteritems(): |
| # Some benchmarks won't have timing data (either it won't exist |
| # at all, or it'll be empty); skip them. |
| samples = keyvals.get('samples') |
| if not samples: |
| continue |
| |
| bench_name = '%s_%s' % (bench_name, cfg_name) |
| if not _IsResultInteresting(bench_name): |
| continue |
| |
| friendly_name = _GetFamiliarName(bench_name) |
| if len(results) < len(samples): |
| results.extend({ |
| 'retval': 0 |
| } for _ in xrange(len(samples) - len(results))) |
| |
| time_mul = _GetTimeMultiplier(friendly_name) |
| for sample, app in itertools.izip(samples, results): |
| assert friendly_name not in app |
| app[friendly_name] = sample * time_mul |
| except (KeyError, ValueError) as e: |
| logging.error('While converting "%s" (key: %s): %s', |
| bench_result, bench_name, e.message) |
| raise |
| |
| # Realistically, [results] should be multiple results, where each entry in |
| # the list is the result for a different label. Because we only deal with |
| # one label at the moment, we need to wrap it in its own list. |
| return results |
| |
| |
| if __name__ == '__main__': |
| |
| def _GetUserFile(argv): |
| if not argv or argv[0] == '-': |
| return sys.stdin |
| return open(argv[0]) |
| |
| def _Main(): |
| with _GetUserFile(sys.argv[1:]) as in_file: |
| obj = json.load(in_file) |
| output = _TransformBenchmarks(obj) |
| json.dump(output, sys.stdout) |
| |
| _Main() |