Android toolchain benchmark suite initialization

Move Android toolchain benchmark suite from external/toolchain-utils to
this directory.

Test: None.
Change-Id: I5069c508b80d817d87cea87c5886488979202570
diff --git a/fix_skia_results.py b/fix_skia_results.py
new file mode 100755
index 0000000..6eec6cc
--- /dev/null
+++ b/fix_skia_results.py
@@ -0,0 +1,144 @@
+#!/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()