普通文本  |  145行  |  4.59 KB

#!/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 sane 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()