| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python | 
|  | 2 | # Copyright 2014 the V8 project authors. All rights reserved. | 
|  | 3 | # Use of this source code is governed by a BSD-style license that can be | 
|  | 4 | # found in the LICENSE file. | 
|  | 5 |  | 
|  | 6 | """ | 
|  | 7 | Performance runner for d8. | 
|  | 8 |  | 
|  | 9 | Call e.g. with tools/run-perf.py --arch ia32 some_suite.json | 
|  | 10 |  | 
|  | 11 | The suite json format is expected to be: | 
|  | 12 | { | 
|  | 13 | "path": <relative path chunks to perf resources and main file>, | 
|  | 14 | "name": <optional suite name, file name is default>, | 
|  | 15 | "archs": [<architecture name for which this suite is run>, ...], | 
|  | 16 | "binary": <name of binary to run, default "d8">, | 
|  | 17 | "flags": [<flag to d8>, ...], | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 18 | "test_flags": [<flag to the test file>, ...], | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 19 | "run_count": <how often will this suite run (optional)>, | 
|  | 20 | "run_count_XXX": <how often will this suite run for arch XXX (optional)>, | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 21 | "resources": [<js file to be moved to android device>, ...] | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 22 | "main": <main js perf runner file>, | 
|  | 23 | "results_regexp": <optional regexp>, | 
|  | 24 | "results_processor": <optional python results processor script>, | 
|  | 25 | "units": <the unit specification for the performance dashboard>, | 
|  | 26 | "tests": [ | 
|  | 27 | { | 
|  | 28 | "name": <name of the trace>, | 
|  | 29 | "results_regexp": <optional more specific regexp>, | 
|  | 30 | "results_processor": <optional python results processor script>, | 
|  | 31 | "units": <the unit specification for the performance dashboard>, | 
|  | 32 | }, ... | 
|  | 33 | ] | 
|  | 34 | } | 
|  | 35 |  | 
|  | 36 | The tests field can also nest other suites in arbitrary depth. A suite | 
|  | 37 | with a "main" file is a leaf suite that can contain one more level of | 
|  | 38 | tests. | 
|  | 39 |  | 
|  | 40 | A suite's results_regexp is expected to have one string place holder | 
|  | 41 | "%s" for the trace name. A trace's results_regexp overwrites suite | 
|  | 42 | defaults. | 
|  | 43 |  | 
|  | 44 | A suite's results_processor may point to an optional python script. If | 
|  | 45 | specified, it is called after running the tests like this (with a path | 
|  | 46 | relatve to the suite level's path): | 
|  | 47 | <results_processor file> <same flags as for d8> <suite level name> <output> | 
|  | 48 |  | 
|  | 49 | The <output> is a temporary file containing d8 output. The results_regexp will | 
|  | 50 | be applied to the output of this script. | 
|  | 51 |  | 
|  | 52 | A suite without "tests" is considered a performance test itself. | 
|  | 53 |  | 
|  | 54 | Full example (suite with one runner): | 
|  | 55 | { | 
|  | 56 | "path": ["."], | 
|  | 57 | "flags": ["--expose-gc"], | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 58 | "test_flags": ["5"], | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 59 | "archs": ["ia32", "x64"], | 
|  | 60 | "run_count": 5, | 
|  | 61 | "run_count_ia32": 3, | 
|  | 62 | "main": "run.js", | 
|  | 63 | "results_regexp": "^%s: (.+)$", | 
|  | 64 | "units": "score", | 
|  | 65 | "tests": [ | 
|  | 66 | {"name": "Richards"}, | 
|  | 67 | {"name": "DeltaBlue"}, | 
|  | 68 | {"name": "NavierStokes", | 
|  | 69 | "results_regexp": "^NavierStokes: (.+)$"} | 
|  | 70 | ] | 
|  | 71 | } | 
|  | 72 |  | 
|  | 73 | Full example (suite with several runners): | 
|  | 74 | { | 
|  | 75 | "path": ["."], | 
|  | 76 | "flags": ["--expose-gc"], | 
|  | 77 | "archs": ["ia32", "x64"], | 
|  | 78 | "run_count": 5, | 
|  | 79 | "units": "score", | 
|  | 80 | "tests": [ | 
|  | 81 | {"name": "Richards", | 
|  | 82 | "path": ["richards"], | 
|  | 83 | "main": "run.js", | 
|  | 84 | "run_count": 3, | 
|  | 85 | "results_regexp": "^Richards: (.+)$"}, | 
|  | 86 | {"name": "NavierStokes", | 
|  | 87 | "path": ["navier_stokes"], | 
|  | 88 | "main": "run.js", | 
|  | 89 | "results_regexp": "^NavierStokes: (.+)$"} | 
|  | 90 | ] | 
|  | 91 | } | 
|  | 92 |  | 
|  | 93 | Path pieces are concatenated. D8 is always run with the suite's path as cwd. | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 94 |  | 
|  | 95 | The test flags are passed to the js test file after '--'. | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 96 | """ | 
|  | 97 |  | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 98 | from collections import OrderedDict | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 99 | import json | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 100 | import logging | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 101 | import math | 
|  | 102 | import optparse | 
|  | 103 | import os | 
|  | 104 | import re | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 105 | import subprocess | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 106 | import sys | 
|  | 107 |  | 
|  | 108 | from testrunner.local import commands | 
|  | 109 | from testrunner.local import utils | 
|  | 110 |  | 
|  | 111 | ARCH_GUESS = utils.DefaultArch() | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 112 | SUPPORTED_ARCHS = ["arm", | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 113 | "ia32", | 
|  | 114 | "mips", | 
|  | 115 | "mipsel", | 
|  | 116 | "nacl_ia32", | 
|  | 117 | "nacl_x64", | 
|  | 118 | "x64", | 
|  | 119 | "arm64"] | 
|  | 120 |  | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 121 | GENERIC_RESULTS_RE = re.compile(r"^RESULT ([^:]+): ([^=]+)= ([^ ]+) ([^ ]*)$") | 
|  | 122 | RESULT_STDDEV_RE = re.compile(r"^\{([^\}]+)\}$") | 
|  | 123 | RESULT_LIST_RE = re.compile(r"^\[([^\]]+)\]$") | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 124 | TOOLS_BASE = os.path.abspath(os.path.dirname(__file__)) | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 125 |  | 
|  | 126 |  | 
|  | 127 | def LoadAndroidBuildTools(path):  # pragma: no cover | 
|  | 128 | assert os.path.exists(path) | 
|  | 129 | sys.path.insert(0, path) | 
|  | 130 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 131 | from pylib.device import adb_wrapper  # pylint: disable=F0401 | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 132 | from pylib.device import device_errors  # pylint: disable=F0401 | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 133 | from pylib.device import device_utils  # pylint: disable=F0401 | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 134 | from pylib.perf import cache_control  # pylint: disable=F0401 | 
|  | 135 | from pylib.perf import perf_control  # pylint: disable=F0401 | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 136 | global adb_wrapper | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 137 | global cache_control | 
|  | 138 | global device_errors | 
|  | 139 | global device_utils | 
|  | 140 | global perf_control | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 141 |  | 
|  | 142 |  | 
|  | 143 | def GeometricMean(values): | 
|  | 144 | """Returns the geometric mean of a list of values. | 
|  | 145 |  | 
|  | 146 | The mean is calculated using log to avoid overflow. | 
|  | 147 | """ | 
|  | 148 | values = map(float, values) | 
|  | 149 | return str(math.exp(sum(map(math.log, values)) / len(values))) | 
|  | 150 |  | 
|  | 151 |  | 
|  | 152 | class Results(object): | 
|  | 153 | """Place holder for result traces.""" | 
|  | 154 | def __init__(self, traces=None, errors=None): | 
|  | 155 | self.traces = traces or [] | 
|  | 156 | self.errors = errors or [] | 
|  | 157 |  | 
|  | 158 | def ToDict(self): | 
|  | 159 | return {"traces": self.traces, "errors": self.errors} | 
|  | 160 |  | 
|  | 161 | def WriteToFile(self, file_name): | 
|  | 162 | with open(file_name, "w") as f: | 
|  | 163 | f.write(json.dumps(self.ToDict())) | 
|  | 164 |  | 
|  | 165 | def __add__(self, other): | 
|  | 166 | self.traces += other.traces | 
|  | 167 | self.errors += other.errors | 
|  | 168 | return self | 
|  | 169 |  | 
|  | 170 | def __str__(self):  # pragma: no cover | 
|  | 171 | return str(self.ToDict()) | 
|  | 172 |  | 
|  | 173 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 174 | class Measurement(object): | 
|  | 175 | """Represents a series of results of one trace. | 
|  | 176 |  | 
|  | 177 | The results are from repetitive runs of the same executable. They are | 
|  | 178 | gathered by repeated calls to ConsumeOutput. | 
|  | 179 | """ | 
|  | 180 | def __init__(self, graphs, units, results_regexp, stddev_regexp): | 
|  | 181 | self.name = graphs[-1] | 
|  | 182 | self.graphs = graphs | 
|  | 183 | self.units = units | 
|  | 184 | self.results_regexp = results_regexp | 
|  | 185 | self.stddev_regexp = stddev_regexp | 
|  | 186 | self.results = [] | 
|  | 187 | self.errors = [] | 
|  | 188 | self.stddev = "" | 
|  | 189 |  | 
|  | 190 | def ConsumeOutput(self, stdout): | 
|  | 191 | try: | 
|  | 192 | result = re.search(self.results_regexp, stdout, re.M).group(1) | 
|  | 193 | self.results.append(str(float(result))) | 
|  | 194 | except ValueError: | 
|  | 195 | self.errors.append("Regexp \"%s\" returned a non-numeric for test %s." | 
|  | 196 | % (self.results_regexp, self.name)) | 
|  | 197 | except: | 
|  | 198 | self.errors.append("Regexp \"%s\" didn't match for test %s." | 
|  | 199 | % (self.results_regexp, self.name)) | 
|  | 200 |  | 
|  | 201 | try: | 
|  | 202 | if self.stddev_regexp and self.stddev: | 
|  | 203 | self.errors.append("Test %s should only run once since a stddev " | 
|  | 204 | "is provided by the test." % self.name) | 
|  | 205 | if self.stddev_regexp: | 
|  | 206 | self.stddev = re.search(self.stddev_regexp, stdout, re.M).group(1) | 
|  | 207 | except: | 
|  | 208 | self.errors.append("Regexp \"%s\" didn't match for test %s." | 
|  | 209 | % (self.stddev_regexp, self.name)) | 
|  | 210 |  | 
|  | 211 | def GetResults(self): | 
|  | 212 | return Results([{ | 
|  | 213 | "graphs": self.graphs, | 
|  | 214 | "units": self.units, | 
|  | 215 | "results": self.results, | 
|  | 216 | "stddev": self.stddev, | 
|  | 217 | }], self.errors) | 
|  | 218 |  | 
|  | 219 |  | 
|  | 220 | class NullMeasurement(object): | 
|  | 221 | """Null object to avoid having extra logic for configurations that didn't | 
|  | 222 | run like running without patch on trybots. | 
|  | 223 | """ | 
|  | 224 | def ConsumeOutput(self, stdout): | 
|  | 225 | pass | 
|  | 226 |  | 
|  | 227 | def GetResults(self): | 
|  | 228 | return Results() | 
|  | 229 |  | 
|  | 230 |  | 
|  | 231 | def Unzip(iterable): | 
|  | 232 | left = [] | 
|  | 233 | right = [] | 
|  | 234 | for l, r in iterable: | 
|  | 235 | left.append(l) | 
|  | 236 | right.append(r) | 
|  | 237 | return lambda: iter(left), lambda: iter(right) | 
|  | 238 |  | 
|  | 239 |  | 
|  | 240 | def AccumulateResults( | 
|  | 241 | graph_names, trace_configs, iter_output, trybot, no_patch, calc_total): | 
|  | 242 | """Iterates over the output of multiple benchmark reruns and accumulates | 
|  | 243 | results for a configured list of traces. | 
|  | 244 |  | 
|  | 245 | Args: | 
|  | 246 | graph_names: List of names that configure the base path of the traces. E.g. | 
|  | 247 | ['v8', 'Octane']. | 
|  | 248 | trace_configs: List of "TraceConfig" instances. Each trace config defines | 
|  | 249 | how to perform a measurement. | 
|  | 250 | iter_output: Iterator over the standard output of each test run. | 
|  | 251 | trybot: Indicates that this is run in trybot mode, i.e. run twice, once | 
|  | 252 | with once without patch. | 
|  | 253 | no_patch: Indicates weather this is a trybot run without patch. | 
|  | 254 | calc_total: Boolean flag to speficy the calculation of a summary trace. | 
|  | 255 | Returns: A "Results" object. | 
|  | 256 | """ | 
|  | 257 | measurements = [ | 
|  | 258 | trace.CreateMeasurement(trybot, no_patch) for trace in trace_configs] | 
|  | 259 | for stdout in iter_output(): | 
|  | 260 | for measurement in measurements: | 
|  | 261 | measurement.ConsumeOutput(stdout) | 
|  | 262 |  | 
|  | 263 | res = reduce(lambda r, m: r + m.GetResults(), measurements, Results()) | 
|  | 264 |  | 
|  | 265 | if not res.traces or not calc_total: | 
|  | 266 | return res | 
|  | 267 |  | 
|  | 268 | # Assume all traces have the same structure. | 
|  | 269 | if len(set(map(lambda t: len(t["results"]), res.traces))) != 1: | 
|  | 270 | res.errors.append("Not all traces have the same number of results.") | 
|  | 271 | return res | 
|  | 272 |  | 
|  | 273 | # Calculate the geometric means for all traces. Above we made sure that | 
|  | 274 | # there is at least one trace and that the number of results is the same | 
|  | 275 | # for each trace. | 
|  | 276 | n_results = len(res.traces[0]["results"]) | 
|  | 277 | total_results = [GeometricMean(t["results"][i] for t in res.traces) | 
|  | 278 | for i in range(0, n_results)] | 
|  | 279 | res.traces.append({ | 
|  | 280 | "graphs": graph_names + ["Total"], | 
|  | 281 | "units": res.traces[0]["units"], | 
|  | 282 | "results": total_results, | 
|  | 283 | "stddev": "", | 
|  | 284 | }) | 
|  | 285 | return res | 
|  | 286 |  | 
|  | 287 |  | 
|  | 288 | def AccumulateGenericResults(graph_names, suite_units, iter_output): | 
|  | 289 | """Iterates over the output of multiple benchmark reruns and accumulates | 
|  | 290 | generic results. | 
|  | 291 |  | 
|  | 292 | Args: | 
|  | 293 | graph_names: List of names that configure the base path of the traces. E.g. | 
|  | 294 | ['v8', 'Octane']. | 
|  | 295 | suite_units: Measurement default units as defined by the benchmark suite. | 
|  | 296 | iter_output: Iterator over the standard output of each test run. | 
|  | 297 | Returns: A "Results" object. | 
|  | 298 | """ | 
|  | 299 | traces = OrderedDict() | 
|  | 300 | for stdout in iter_output(): | 
|  | 301 | if stdout is None: | 
|  | 302 | # The None value is used as a null object to simplify logic. | 
|  | 303 | continue | 
|  | 304 | for line in stdout.strip().splitlines(): | 
|  | 305 | match = GENERIC_RESULTS_RE.match(line) | 
|  | 306 | if match: | 
|  | 307 | stddev = "" | 
|  | 308 | graph = match.group(1) | 
|  | 309 | trace = match.group(2) | 
|  | 310 | body = match.group(3) | 
|  | 311 | units = match.group(4) | 
|  | 312 | match_stddev = RESULT_STDDEV_RE.match(body) | 
|  | 313 | match_list = RESULT_LIST_RE.match(body) | 
|  | 314 | errors = [] | 
|  | 315 | if match_stddev: | 
|  | 316 | result, stddev = map(str.strip, match_stddev.group(1).split(",")) | 
|  | 317 | results = [result] | 
|  | 318 | elif match_list: | 
|  | 319 | results = map(str.strip, match_list.group(1).split(",")) | 
|  | 320 | else: | 
|  | 321 | results = [body.strip()] | 
|  | 322 |  | 
|  | 323 | try: | 
|  | 324 | results = map(lambda r: str(float(r)), results) | 
|  | 325 | except ValueError: | 
|  | 326 | results = [] | 
|  | 327 | errors = ["Found non-numeric in %s" % | 
|  | 328 | "/".join(graph_names + [graph, trace])] | 
|  | 329 |  | 
|  | 330 | trace_result = traces.setdefault(trace, Results([{ | 
|  | 331 | "graphs": graph_names + [graph, trace], | 
|  | 332 | "units": (units or suite_units).strip(), | 
|  | 333 | "results": [], | 
|  | 334 | "stddev": "", | 
|  | 335 | }], errors)) | 
|  | 336 | trace_result.traces[0]["results"].extend(results) | 
|  | 337 | trace_result.traces[0]["stddev"] = stddev | 
|  | 338 |  | 
|  | 339 | return reduce(lambda r, t: r + t, traces.itervalues(), Results()) | 
|  | 340 |  | 
|  | 341 |  | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 342 | class Node(object): | 
|  | 343 | """Represents a node in the suite tree structure.""" | 
|  | 344 | def __init__(self, *args): | 
|  | 345 | self._children = [] | 
|  | 346 |  | 
|  | 347 | def AppendChild(self, child): | 
|  | 348 | self._children.append(child) | 
|  | 349 |  | 
|  | 350 |  | 
|  | 351 | class DefaultSentinel(Node): | 
|  | 352 | """Fake parent node with all default values.""" | 
| Ben Murdoch | 097c5b2 | 2016-05-18 11:27:45 +0100 | [diff] [blame^] | 353 | def __init__(self, binary = "d8"): | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 354 | super(DefaultSentinel, self).__init__() | 
| Ben Murdoch | 097c5b2 | 2016-05-18 11:27:45 +0100 | [diff] [blame^] | 355 | self.binary = binary | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 356 | self.run_count = 10 | 
|  | 357 | self.timeout = 60 | 
|  | 358 | self.path = [] | 
|  | 359 | self.graphs = [] | 
|  | 360 | self.flags = [] | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 361 | self.test_flags = [] | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 362 | self.resources = [] | 
|  | 363 | self.results_regexp = None | 
|  | 364 | self.stddev_regexp = None | 
|  | 365 | self.units = "score" | 
|  | 366 | self.total = False | 
|  | 367 |  | 
|  | 368 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 369 | class GraphConfig(Node): | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 370 | """Represents a suite definition. | 
|  | 371 |  | 
|  | 372 | Can either be a leaf or an inner node that provides default values. | 
|  | 373 | """ | 
|  | 374 | def __init__(self, suite, parent, arch): | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 375 | super(GraphConfig, self).__init__() | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 376 | self._suite = suite | 
|  | 377 |  | 
|  | 378 | assert isinstance(suite.get("path", []), list) | 
|  | 379 | assert isinstance(suite["name"], basestring) | 
|  | 380 | assert isinstance(suite.get("flags", []), list) | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 381 | assert isinstance(suite.get("test_flags", []), list) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 382 | assert isinstance(suite.get("resources", []), list) | 
|  | 383 |  | 
|  | 384 | # Accumulated values. | 
|  | 385 | self.path = parent.path[:] + suite.get("path", []) | 
|  | 386 | self.graphs = parent.graphs[:] + [suite["name"]] | 
|  | 387 | self.flags = parent.flags[:] + suite.get("flags", []) | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 388 | self.test_flags = parent.test_flags[:] + suite.get("test_flags", []) | 
|  | 389 |  | 
|  | 390 | # Values independent of parent node. | 
|  | 391 | self.resources = suite.get("resources", []) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 392 |  | 
|  | 393 | # Descrete values (with parent defaults). | 
|  | 394 | self.binary = suite.get("binary", parent.binary) | 
|  | 395 | self.run_count = suite.get("run_count", parent.run_count) | 
|  | 396 | self.run_count = suite.get("run_count_%s" % arch, self.run_count) | 
|  | 397 | self.timeout = suite.get("timeout", parent.timeout) | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 398 | self.timeout = suite.get("timeout_%s" % arch, self.timeout) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 399 | self.units = suite.get("units", parent.units) | 
|  | 400 | self.total = suite.get("total", parent.total) | 
|  | 401 |  | 
|  | 402 | # A regular expression for results. If the parent graph provides a | 
|  | 403 | # regexp and the current suite has none, a string place holder for the | 
|  | 404 | # suite name is expected. | 
|  | 405 | # TODO(machenbach): Currently that makes only sense for the leaf level. | 
|  | 406 | # Multiple place holders for multiple levels are not supported. | 
|  | 407 | if parent.results_regexp: | 
|  | 408 | regexp_default = parent.results_regexp % re.escape(suite["name"]) | 
|  | 409 | else: | 
|  | 410 | regexp_default = None | 
|  | 411 | self.results_regexp = suite.get("results_regexp", regexp_default) | 
|  | 412 |  | 
|  | 413 | # A similar regular expression for the standard deviation (optional). | 
|  | 414 | if parent.stddev_regexp: | 
|  | 415 | stddev_default = parent.stddev_regexp % re.escape(suite["name"]) | 
|  | 416 | else: | 
|  | 417 | stddev_default = None | 
|  | 418 | self.stddev_regexp = suite.get("stddev_regexp", stddev_default) | 
|  | 419 |  | 
|  | 420 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 421 | class TraceConfig(GraphConfig): | 
|  | 422 | """Represents a leaf in the suite tree structure.""" | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 423 | def __init__(self, suite, parent, arch): | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 424 | super(TraceConfig, self).__init__(suite, parent, arch) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 425 | assert self.results_regexp | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 426 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 427 | def CreateMeasurement(self, trybot, no_patch): | 
|  | 428 | if not trybot and no_patch: | 
|  | 429 | # Use null object for no-patch logic if this is not a trybot run. | 
|  | 430 | return NullMeasurement() | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 431 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 432 | return Measurement( | 
|  | 433 | self.graphs, | 
|  | 434 | self.units, | 
|  | 435 | self.results_regexp, | 
|  | 436 | self.stddev_regexp, | 
|  | 437 | ) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 438 |  | 
|  | 439 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 440 | class RunnableConfig(GraphConfig): | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 441 | """Represents a runnable suite definition (i.e. has a main file). | 
|  | 442 | """ | 
|  | 443 | @property | 
|  | 444 | def main(self): | 
|  | 445 | return self._suite.get("main", "") | 
|  | 446 |  | 
|  | 447 | def ChangeCWD(self, suite_path): | 
|  | 448 | """Changes the cwd to to path defined in the current graph. | 
|  | 449 |  | 
|  | 450 | The tests are supposed to be relative to the suite configuration. | 
|  | 451 | """ | 
|  | 452 | suite_dir = os.path.abspath(os.path.dirname(suite_path)) | 
|  | 453 | bench_dir = os.path.normpath(os.path.join(*self.path)) | 
|  | 454 | os.chdir(os.path.join(suite_dir, bench_dir)) | 
|  | 455 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 456 | def GetCommandFlags(self, extra_flags=None): | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 457 | suffix = ["--"] + self.test_flags if self.test_flags else [] | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 458 | return self.flags + (extra_flags or []) + [self.main] + suffix | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 459 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 460 | def GetCommand(self, shell_dir, extra_flags=None): | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 461 | # TODO(machenbach): This requires +.exe if run on windows. | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 462 | extra_flags = extra_flags or [] | 
|  | 463 | cmd = [os.path.join(shell_dir, self.binary)] | 
|  | 464 | if self.binary != 'd8' and '--prof' in extra_flags: | 
|  | 465 | print "Profiler supported only on a benchmark run with d8" | 
|  | 466 | return cmd + self.GetCommandFlags(extra_flags=extra_flags) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 467 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 468 | def Run(self, runner, trybot): | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 469 | """Iterates over several runs and handles the output for all traces.""" | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 470 | stdout_with_patch, stdout_no_patch = Unzip(runner()) | 
|  | 471 | return ( | 
|  | 472 | AccumulateResults( | 
|  | 473 | self.graphs, | 
|  | 474 | self._children, | 
|  | 475 | iter_output=stdout_with_patch, | 
|  | 476 | trybot=trybot, | 
|  | 477 | no_patch=False, | 
|  | 478 | calc_total=self.total, | 
|  | 479 | ), | 
|  | 480 | AccumulateResults( | 
|  | 481 | self.graphs, | 
|  | 482 | self._children, | 
|  | 483 | iter_output=stdout_no_patch, | 
|  | 484 | trybot=trybot, | 
|  | 485 | no_patch=True, | 
|  | 486 | calc_total=self.total, | 
|  | 487 | ), | 
|  | 488 | ) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 489 |  | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 490 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 491 | class RunnableTraceConfig(TraceConfig, RunnableConfig): | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 492 | """Represents a runnable suite definition that is a leaf.""" | 
|  | 493 | def __init__(self, suite, parent, arch): | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 494 | super(RunnableTraceConfig, self).__init__(suite, parent, arch) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 495 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 496 | def Run(self, runner, trybot): | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 497 | """Iterates over several runs and handles the output.""" | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 498 | measurement_with_patch = self.CreateMeasurement(trybot, False) | 
|  | 499 | measurement_no_patch = self.CreateMeasurement(trybot, True) | 
|  | 500 | for stdout_with_patch, stdout_no_patch in runner(): | 
|  | 501 | measurement_with_patch.ConsumeOutput(stdout_with_patch) | 
|  | 502 | measurement_no_patch.ConsumeOutput(stdout_no_patch) | 
|  | 503 | return ( | 
|  | 504 | measurement_with_patch.GetResults(), | 
|  | 505 | measurement_no_patch.GetResults(), | 
|  | 506 | ) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 507 |  | 
|  | 508 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 509 | class RunnableGenericConfig(RunnableConfig): | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 510 | """Represents a runnable suite definition with generic traces.""" | 
|  | 511 | def __init__(self, suite, parent, arch): | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 512 | super(RunnableGenericConfig, self).__init__(suite, parent, arch) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 513 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 514 | def Run(self, runner, trybot): | 
|  | 515 | stdout_with_patch, stdout_no_patch = Unzip(runner()) | 
|  | 516 | return ( | 
|  | 517 | AccumulateGenericResults(self.graphs, self.units, stdout_with_patch), | 
|  | 518 | AccumulateGenericResults(self.graphs, self.units, stdout_no_patch), | 
|  | 519 | ) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 520 |  | 
|  | 521 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 522 | def MakeGraphConfig(suite, arch, parent): | 
|  | 523 | """Factory method for making graph configuration objects.""" | 
|  | 524 | if isinstance(parent, RunnableConfig): | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 525 | # Below a runnable can only be traces. | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 526 | return TraceConfig(suite, parent, arch) | 
|  | 527 | elif suite.get("main") is not None: | 
|  | 528 | # A main file makes this graph runnable. Empty strings are accepted. | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 529 | if suite.get("tests"): | 
|  | 530 | # This graph has subgraphs (traces). | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 531 | return RunnableConfig(suite, parent, arch) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 532 | else: | 
|  | 533 | # This graph has no subgraphs, it's a leaf. | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 534 | return RunnableTraceConfig(suite, parent, arch) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 535 | elif suite.get("generic"): | 
|  | 536 | # This is a generic suite definition. It is either a runnable executable | 
|  | 537 | # or has a main js file. | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 538 | return RunnableGenericConfig(suite, parent, arch) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 539 | elif suite.get("tests"): | 
|  | 540 | # This is neither a leaf nor a runnable. | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 541 | return GraphConfig(suite, parent, arch) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 542 | else:  # pragma: no cover | 
|  | 543 | raise Exception("Invalid suite configuration.") | 
|  | 544 |  | 
|  | 545 |  | 
| Ben Murdoch | 097c5b2 | 2016-05-18 11:27:45 +0100 | [diff] [blame^] | 546 | def BuildGraphConfigs(suite, arch, parent): | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 547 | """Builds a tree structure of graph objects that corresponds to the suite | 
|  | 548 | configuration. | 
|  | 549 | """ | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 550 |  | 
|  | 551 | # TODO(machenbach): Implement notion of cpu type? | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 552 | if arch not in suite.get("archs", SUPPORTED_ARCHS): | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 553 | return None | 
|  | 554 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 555 | graph = MakeGraphConfig(suite, arch, parent) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 556 | for subsuite in suite.get("tests", []): | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 557 | BuildGraphConfigs(subsuite, arch, graph) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 558 | parent.AppendChild(graph) | 
|  | 559 | return graph | 
|  | 560 |  | 
|  | 561 |  | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 562 | def FlattenRunnables(node, node_cb): | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 563 | """Generator that traverses the tree structure and iterates over all | 
|  | 564 | runnables. | 
|  | 565 | """ | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 566 | node_cb(node) | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 567 | if isinstance(node, RunnableConfig): | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 568 | yield node | 
|  | 569 | elif isinstance(node, Node): | 
|  | 570 | for child in node._children: | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 571 | for result in FlattenRunnables(child, node_cb): | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 572 | yield result | 
|  | 573 | else:  # pragma: no cover | 
|  | 574 | raise Exception("Invalid suite configuration.") | 
|  | 575 |  | 
|  | 576 |  | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 577 | class Platform(object): | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 578 | def __init__(self, options): | 
|  | 579 | self.shell_dir = options.shell_dir | 
|  | 580 | self.shell_dir_no_patch = options.shell_dir_no_patch | 
|  | 581 | self.extra_flags = options.extra_flags.split() | 
|  | 582 |  | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 583 | @staticmethod | 
|  | 584 | def GetPlatform(options): | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 585 | if options.android_build_tools: | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 586 | return AndroidPlatform(options) | 
|  | 587 | else: | 
|  | 588 | return DesktopPlatform(options) | 
|  | 589 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 590 | def _Run(self, runnable, count, no_patch=False): | 
|  | 591 | raise NotImplementedError()  # pragma: no cover | 
|  | 592 |  | 
|  | 593 | def Run(self, runnable, count): | 
|  | 594 | """Execute the benchmark's main file. | 
|  | 595 |  | 
|  | 596 | If options.shell_dir_no_patch is specified, the benchmark is run once with | 
|  | 597 | and once without patch. | 
|  | 598 | Args: | 
|  | 599 | runnable: A Runnable benchmark instance. | 
|  | 600 | count: The number of this (repeated) run. | 
|  | 601 | Returns: A tuple with the benchmark outputs with and without patch. The | 
|  | 602 | latter will be None if options.shell_dir_no_patch was not | 
|  | 603 | specified. | 
|  | 604 | """ | 
|  | 605 | stdout = self._Run(runnable, count, no_patch=False) | 
|  | 606 | if self.shell_dir_no_patch: | 
|  | 607 | return stdout, self._Run(runnable, count, no_patch=True) | 
|  | 608 | else: | 
|  | 609 | return stdout, None | 
|  | 610 |  | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 611 |  | 
|  | 612 | class DesktopPlatform(Platform): | 
|  | 613 | def __init__(self, options): | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 614 | super(DesktopPlatform, self).__init__(options) | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 615 |  | 
|  | 616 | def PreExecution(self): | 
|  | 617 | pass | 
|  | 618 |  | 
|  | 619 | def PostExecution(self): | 
|  | 620 | pass | 
|  | 621 |  | 
|  | 622 | def PreTests(self, node, path): | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 623 | if isinstance(node, RunnableConfig): | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 624 | node.ChangeCWD(path) | 
|  | 625 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 626 | def _Run(self, runnable, count, no_patch=False): | 
|  | 627 | suffix = ' - without patch' if no_patch else '' | 
|  | 628 | shell_dir = self.shell_dir_no_patch if no_patch else self.shell_dir | 
|  | 629 | title = ">>> %%s (#%d)%s:" % ((count + 1), suffix) | 
|  | 630 | try: | 
|  | 631 | output = commands.Execute( | 
|  | 632 | runnable.GetCommand(shell_dir, self.extra_flags), | 
|  | 633 | timeout=runnable.timeout, | 
|  | 634 | ) | 
|  | 635 | except OSError as e:  # pragma: no cover | 
|  | 636 | print title % "OSError" | 
|  | 637 | print e | 
|  | 638 | return "" | 
|  | 639 | print title % "Stdout" | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 640 | print output.stdout | 
|  | 641 | if output.stderr:  # pragma: no cover | 
|  | 642 | # Print stderr for debugging. | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 643 | print title % "Stderr" | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 644 | print output.stderr | 
|  | 645 | if output.timed_out: | 
|  | 646 | print ">>> Test timed out after %ss." % runnable.timeout | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 647 | if '--prof' in self.extra_flags: | 
|  | 648 | os_prefix = {"linux": "linux", "macos": "mac"}.get(utils.GuessOS()) | 
|  | 649 | if os_prefix: | 
|  | 650 | tick_tools = os.path.join(TOOLS_BASE, "%s-tick-processor" % os_prefix) | 
|  | 651 | subprocess.check_call(tick_tools + " --only-summary", shell=True) | 
|  | 652 | else:  # pragma: no cover | 
|  | 653 | print "Profiler option currently supported on Linux and Mac OS." | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 654 | return output.stdout | 
|  | 655 |  | 
|  | 656 |  | 
|  | 657 | class AndroidPlatform(Platform):  # pragma: no cover | 
|  | 658 | DEVICE_DIR = "/data/local/tmp/v8/" | 
|  | 659 |  | 
|  | 660 | def __init__(self, options): | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 661 | super(AndroidPlatform, self).__init__(options) | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 662 | LoadAndroidBuildTools(options.android_build_tools) | 
|  | 663 |  | 
|  | 664 | if not options.device: | 
|  | 665 | # Detect attached device if not specified. | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 666 | devices = adb_wrapper.AdbWrapper.Devices() | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 667 | assert devices and len(devices) == 1, ( | 
|  | 668 | "None or multiple devices detected. Please specify the device on " | 
|  | 669 | "the command-line with --device") | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 670 | options.device = str(devices[0]) | 
|  | 671 | self.adb_wrapper = adb_wrapper.AdbWrapper(options.device) | 
|  | 672 | self.device = device_utils.DeviceUtils(self.adb_wrapper) | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 673 |  | 
|  | 674 | def PreExecution(self): | 
|  | 675 | perf = perf_control.PerfControl(self.device) | 
|  | 676 | perf.SetHighPerfMode() | 
|  | 677 |  | 
|  | 678 | # Remember what we have already pushed to the device. | 
|  | 679 | self.pushed = set() | 
|  | 680 |  | 
|  | 681 | def PostExecution(self): | 
|  | 682 | perf = perf_control.PerfControl(self.device) | 
|  | 683 | perf.SetDefaultPerfMode() | 
|  | 684 | self.device.RunShellCommand(["rm", "-rf", AndroidPlatform.DEVICE_DIR]) | 
|  | 685 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 686 | def _PushFile(self, host_dir, file_name, target_rel=".", | 
|  | 687 | skip_if_missing=False): | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 688 | file_on_host = os.path.join(host_dir, file_name) | 
|  | 689 | file_on_device_tmp = os.path.join( | 
|  | 690 | AndroidPlatform.DEVICE_DIR, "_tmp_", file_name) | 
|  | 691 | file_on_device = os.path.join( | 
|  | 692 | AndroidPlatform.DEVICE_DIR, target_rel, file_name) | 
|  | 693 | folder_on_device = os.path.dirname(file_on_device) | 
|  | 694 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 695 | # Only attempt to push files that exist. | 
|  | 696 | if not os.path.exists(file_on_host): | 
|  | 697 | if not skip_if_missing: | 
|  | 698 | logging.critical('Missing file on host: %s' % file_on_host) | 
|  | 699 | return | 
|  | 700 |  | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 701 | # Only push files not yet pushed in one execution. | 
|  | 702 | if file_on_host in self.pushed: | 
|  | 703 | return | 
|  | 704 | else: | 
|  | 705 | self.pushed.add(file_on_host) | 
|  | 706 |  | 
|  | 707 | # Work-around for "text file busy" errors. Push the files to a temporary | 
|  | 708 | # location and then copy them with a shell command. | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 709 | output = self.adb_wrapper.Push(file_on_host, file_on_device_tmp) | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 710 | # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)". | 
|  | 711 | # Errors look like this: "failed to copy  ... ". | 
|  | 712 | if output and not re.search('^[0-9]', output.splitlines()[-1]): | 
|  | 713 | logging.critical('PUSH FAILED: ' + output) | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 714 | self.adb_wrapper.Shell("mkdir -p %s" % folder_on_device) | 
|  | 715 | self.adb_wrapper.Shell("cp %s %s" % (file_on_device_tmp, file_on_device)) | 
|  | 716 |  | 
|  | 717 | def _PushExecutable(self, shell_dir, target_dir, binary): | 
|  | 718 | self._PushFile(shell_dir, binary, target_dir) | 
|  | 719 |  | 
|  | 720 | # Push external startup data. Backwards compatible for revisions where | 
|  | 721 | # these files didn't exist. | 
|  | 722 | self._PushFile( | 
|  | 723 | shell_dir, | 
|  | 724 | "natives_blob.bin", | 
|  | 725 | target_dir, | 
|  | 726 | skip_if_missing=True, | 
|  | 727 | ) | 
|  | 728 | self._PushFile( | 
|  | 729 | shell_dir, | 
|  | 730 | "snapshot_blob.bin", | 
|  | 731 | target_dir, | 
|  | 732 | skip_if_missing=True, | 
|  | 733 | ) | 
| Ben Murdoch | 097c5b2 | 2016-05-18 11:27:45 +0100 | [diff] [blame^] | 734 | self._PushFile( | 
|  | 735 | shell_dir, | 
|  | 736 | "snapshot_blob_ignition.bin", | 
|  | 737 | target_dir, | 
|  | 738 | skip_if_missing=True, | 
|  | 739 | ) | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 740 |  | 
|  | 741 | def PreTests(self, node, path): | 
|  | 742 | suite_dir = os.path.abspath(os.path.dirname(path)) | 
|  | 743 | if node.path: | 
|  | 744 | bench_rel = os.path.normpath(os.path.join(*node.path)) | 
|  | 745 | bench_abs = os.path.join(suite_dir, bench_rel) | 
|  | 746 | else: | 
|  | 747 | bench_rel = "." | 
|  | 748 | bench_abs = suite_dir | 
|  | 749 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 750 | self._PushExecutable(self.shell_dir, "bin", node.binary) | 
|  | 751 | if self.shell_dir_no_patch: | 
|  | 752 | self._PushExecutable( | 
|  | 753 | self.shell_dir_no_patch, "bin_no_patch", node.binary) | 
|  | 754 |  | 
|  | 755 | if isinstance(node, RunnableConfig): | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 756 | self._PushFile(bench_abs, node.main, bench_rel) | 
|  | 757 | for resource in node.resources: | 
|  | 758 | self._PushFile(bench_abs, resource, bench_rel) | 
|  | 759 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 760 | def _Run(self, runnable, count, no_patch=False): | 
|  | 761 | suffix = ' - without patch' if no_patch else '' | 
|  | 762 | target_dir = "bin_no_patch" if no_patch else "bin" | 
|  | 763 | title = ">>> %%s (#%d)%s:" % ((count + 1), suffix) | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 764 | cache = cache_control.CacheControl(self.device) | 
|  | 765 | cache.DropRamCaches() | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 766 | binary_on_device = os.path.join( | 
|  | 767 | AndroidPlatform.DEVICE_DIR, target_dir, runnable.binary) | 
|  | 768 | cmd = [binary_on_device] + runnable.GetCommandFlags(self.extra_flags) | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 769 |  | 
|  | 770 | # Relative path to benchmark directory. | 
|  | 771 | if runnable.path: | 
|  | 772 | bench_rel = os.path.normpath(os.path.join(*runnable.path)) | 
|  | 773 | else: | 
|  | 774 | bench_rel = "." | 
|  | 775 |  | 
|  | 776 | try: | 
|  | 777 | output = self.device.RunShellCommand( | 
|  | 778 | cmd, | 
|  | 779 | cwd=os.path.join(AndroidPlatform.DEVICE_DIR, bench_rel), | 
|  | 780 | timeout=runnable.timeout, | 
|  | 781 | retries=0, | 
|  | 782 | ) | 
|  | 783 | stdout = "\n".join(output) | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 784 | print title % "Stdout" | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 785 | print stdout | 
|  | 786 | except device_errors.CommandTimeoutError: | 
|  | 787 | print ">>> Test timed out after %ss." % runnable.timeout | 
|  | 788 | stdout = "" | 
|  | 789 | return stdout | 
|  | 790 |  | 
|  | 791 |  | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 792 | # TODO: Implement results_processor. | 
|  | 793 | def Main(args): | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 794 | logging.getLogger().setLevel(logging.INFO) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 795 | parser = optparse.OptionParser() | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 796 | parser.add_option("--android-build-tools", | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 797 | help="Path to chromium's build/android. Specifying this " | 
|  | 798 | "option will run tests using android platform.") | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 799 | parser.add_option("--arch", | 
|  | 800 | help=("The architecture to run tests for, " | 
|  | 801 | "'auto' or 'native' for auto-detect"), | 
|  | 802 | default="x64") | 
|  | 803 | parser.add_option("--buildbot", | 
|  | 804 | help="Adapt to path structure used on buildbots", | 
|  | 805 | default=False, action="store_true") | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 806 | parser.add_option("--device", | 
|  | 807 | help="The device ID to run Android tests on. If not given " | 
|  | 808 | "it will be autodetected.") | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 809 | parser.add_option("--extra-flags", | 
|  | 810 | help="Additional flags to pass to the test executable", | 
|  | 811 | default="") | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 812 | parser.add_option("--json-test-results", | 
|  | 813 | help="Path to a file for storing json results.") | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 814 | parser.add_option("--json-test-results-no-patch", | 
|  | 815 | help="Path to a file for storing json results from run " | 
|  | 816 | "without patch.") | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 817 | parser.add_option("--outdir", help="Base directory with compile output", | 
|  | 818 | default="out") | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 819 | parser.add_option("--outdir-no-patch", | 
|  | 820 | help="Base directory with compile output without patch") | 
| Ben Murdoch | 097c5b2 | 2016-05-18 11:27:45 +0100 | [diff] [blame^] | 821 | parser.add_option("--binary-override-path", | 
|  | 822 | help="JavaScript engine binary. By default, d8 under " | 
|  | 823 | "architecture-specific build dir. " | 
|  | 824 | "Not supported in conjunction with outdir-no-patch.") | 
|  | 825 |  | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 826 | (options, args) = parser.parse_args(args) | 
|  | 827 |  | 
|  | 828 | if len(args) == 0:  # pragma: no cover | 
|  | 829 | parser.print_help() | 
|  | 830 | return 1 | 
|  | 831 |  | 
|  | 832 | if options.arch in ["auto", "native"]:  # pragma: no cover | 
|  | 833 | options.arch = ARCH_GUESS | 
|  | 834 |  | 
|  | 835 | if not options.arch in SUPPORTED_ARCHS:  # pragma: no cover | 
|  | 836 | print "Unknown architecture %s" % options.arch | 
|  | 837 | return 1 | 
|  | 838 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 839 | if options.device and not options.android_build_tools:  # pragma: no cover | 
|  | 840 | print "Specifying a device requires Android build tools." | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 841 | return 1 | 
|  | 842 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 843 | if (options.json_test_results_no_patch and | 
|  | 844 | not options.outdir_no_patch):  # pragma: no cover | 
|  | 845 | print("For writing json test results without patch, an outdir without " | 
|  | 846 | "patch must be specified.") | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 847 | return 1 | 
|  | 848 |  | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 849 | workspace = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) | 
|  | 850 |  | 
|  | 851 | if options.buildbot: | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 852 | build_config = "Release" | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 853 | else: | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 854 | build_config = "%s.release" % options.arch | 
|  | 855 |  | 
| Ben Murdoch | 097c5b2 | 2016-05-18 11:27:45 +0100 | [diff] [blame^] | 856 | if options.binary_override_path == None: | 
|  | 857 | options.shell_dir = os.path.join(workspace, options.outdir, build_config) | 
|  | 858 | default_binary_name = "d8" | 
|  | 859 | else: | 
|  | 860 | if not os.path.isfile(options.binary_override_path): | 
|  | 861 | print "binary-override-path must be a file name" | 
|  | 862 | return 1 | 
|  | 863 | if options.outdir_no_patch: | 
|  | 864 | print "specify either binary-override-path or outdir-no-patch" | 
|  | 865 | return 1 | 
|  | 866 | options.shell_dir = os.path.dirname(options.binary_override_path) | 
|  | 867 | default_binary_name = os.path.basename(options.binary_override_path) | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 868 |  | 
|  | 869 | if options.outdir_no_patch: | 
|  | 870 | options.shell_dir_no_patch = os.path.join( | 
|  | 871 | workspace, options.outdir_no_patch, build_config) | 
|  | 872 | else: | 
|  | 873 | options.shell_dir_no_patch = None | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 874 |  | 
|  | 875 | platform = Platform.GetPlatform(options) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 876 |  | 
|  | 877 | results = Results() | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 878 | results_no_patch = Results() | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 879 | for path in args: | 
|  | 880 | path = os.path.abspath(path) | 
|  | 881 |  | 
|  | 882 | if not os.path.exists(path):  # pragma: no cover | 
|  | 883 | results.errors.append("Configuration file %s does not exist." % path) | 
|  | 884 | continue | 
|  | 885 |  | 
|  | 886 | with open(path) as f: | 
|  | 887 | suite = json.loads(f.read()) | 
|  | 888 |  | 
|  | 889 | # If no name is given, default to the file name without .json. | 
|  | 890 | suite.setdefault("name", os.path.splitext(os.path.basename(path))[0]) | 
|  | 891 |  | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 892 | # Setup things common to one test suite. | 
|  | 893 | platform.PreExecution() | 
|  | 894 |  | 
|  | 895 | # Build the graph/trace tree structure. | 
| Ben Murdoch | 097c5b2 | 2016-05-18 11:27:45 +0100 | [diff] [blame^] | 896 | default_parent = DefaultSentinel(default_binary_name) | 
|  | 897 | root = BuildGraphConfigs(suite, options.arch, default_parent) | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 898 |  | 
|  | 899 | # Callback to be called on each node on traversal. | 
|  | 900 | def NodeCB(node): | 
|  | 901 | platform.PreTests(node, path) | 
|  | 902 |  | 
|  | 903 | # Traverse graph/trace tree and interate over all runnables. | 
|  | 904 | for runnable in FlattenRunnables(root, NodeCB): | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 905 | print ">>> Running suite: %s" % "/".join(runnable.graphs) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 906 |  | 
|  | 907 | def Runner(): | 
|  | 908 | """Output generator that reruns several times.""" | 
|  | 909 | for i in xrange(0, max(1, runnable.run_count)): | 
|  | 910 | # TODO(machenbach): Allow timeout per arch like with run_count per | 
|  | 911 | # arch. | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 912 | yield platform.Run(runnable, i) | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 913 |  | 
|  | 914 | # Let runnable iterate over all runs and handle output. | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 915 | result, result_no_patch = runnable.Run( | 
|  | 916 | Runner, trybot=options.shell_dir_no_patch) | 
|  | 917 | results += result | 
|  | 918 | results_no_patch += result_no_patch | 
| Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 919 | platform.PostExecution() | 
|  | 920 |  | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 921 | if options.json_test_results: | 
|  | 922 | results.WriteToFile(options.json_test_results) | 
|  | 923 | else:  # pragma: no cover | 
|  | 924 | print results | 
|  | 925 |  | 
| Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 926 | if options.json_test_results_no_patch: | 
|  | 927 | results_no_patch.WriteToFile(options.json_test_results_no_patch) | 
|  | 928 | else:  # pragma: no cover | 
|  | 929 | print results_no_patch | 
|  | 930 |  | 
| Ben Murdoch | b8a8cc1 | 2014-11-26 15:28:44 +0000 | [diff] [blame] | 931 | return min(1, len(results.errors)) | 
|  | 932 |  | 
|  | 933 | if __name__ == "__main__":  # pragma: no cover | 
|  | 934 | sys.exit(Main(sys.argv[1:])) |