| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2014 the V8 project authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """ | 6 """ |
| 7 Performance runner for d8. | 7 Performance runner for d8. |
| 8 | 8 |
| 9 Call e.g. with tools/run-perf.py --arch ia32 some_suite.json | 9 Call e.g. with tools/run-perf.py --arch ia32 some_suite.json |
| 10 | 10 |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 90 ] | 90 ] |
| 91 } | 91 } |
| 92 | 92 |
| 93 Path pieces are concatenated. D8 is always run with the suite's path as cwd. | 93 Path pieces are concatenated. D8 is always run with the suite's path as cwd. |
| 94 | 94 |
| 95 The test flags are passed to the js test file after '--'. | 95 The test flags are passed to the js test file after '--'. |
| 96 """ | 96 """ |
| 97 | 97 |
| 98 from collections import OrderedDict | 98 from collections import OrderedDict |
| 99 import json | 99 import json |
| 100 import logging |
| 100 import math | 101 import math |
| 101 import optparse | 102 import optparse |
| 102 import os | 103 import os |
| 103 import re | 104 import re |
| 104 import sys | 105 import sys |
| 105 | 106 |
| 106 from testrunner.local import commands | 107 from testrunner.local import commands |
| 107 from testrunner.local import utils | 108 from testrunner.local import utils |
| 108 | 109 |
| 109 ARCH_GUESS = utils.DefaultArch() | 110 ARCH_GUESS = utils.DefaultArch() |
| 110 SUPPORTED_ARCHS = ["android_arm", | 111 SUPPORTED_ARCHS = ["android_arm", |
| 111 "android_arm64", | 112 "android_arm64", |
| 112 "android_ia32", | 113 "android_ia32", |
| 113 "arm", | 114 "arm", |
| 114 "ia32", | 115 "ia32", |
| 115 "mips", | 116 "mips", |
| 116 "mipsel", | 117 "mipsel", |
| 117 "nacl_ia32", | 118 "nacl_ia32", |
| 118 "nacl_x64", | 119 "nacl_x64", |
| 119 "x64", | 120 "x64", |
| 120 "arm64"] | 121 "arm64"] |
| 121 | 122 |
| 122 GENERIC_RESULTS_RE = re.compile(r"^RESULT ([^:]+): ([^=]+)= ([^ ]+) ([^ ]*)$") | 123 GENERIC_RESULTS_RE = re.compile(r"^RESULT ([^:]+): ([^=]+)= ([^ ]+) ([^ ]*)$") |
| 123 RESULT_STDDEV_RE = re.compile(r"^\{([^\}]+)\}$") | 124 RESULT_STDDEV_RE = re.compile(r"^\{([^\}]+)\}$") |
| 124 RESULT_LIST_RE = re.compile(r"^\[([^\]]+)\]$") | 125 RESULT_LIST_RE = re.compile(r"^\[([^\]]+)\]$") |
| 125 | 126 |
| 126 | 127 |
| 128 def LoadAndroidBuildTools(path): # pragma: no cover |
| 129 assert os.path.exists(path) |
| 130 sys.path.insert(0, path) |
| 131 |
| 132 from pylib.device import device_utils # pylint: disable=F0401 |
| 133 from pylib.device import device_errors # pylint: disable=F0401 |
| 134 from pylib.perf import cache_control # pylint: disable=F0401 |
| 135 from pylib.perf import perf_control # pylint: disable=F0401 |
| 136 import pylib.android_commands # pylint: disable=F0401 |
| 137 global cache_control |
| 138 global device_errors |
| 139 global device_utils |
| 140 global perf_control |
| 141 global pylib |
| 142 |
| 127 | 143 |
| 128 def GeometricMean(values): | 144 def GeometricMean(values): |
| 129 """Returns the geometric mean of a list of values. | 145 """Returns the geometric mean of a list of values. |
| 130 | 146 |
| 131 The mean is calculated using log to avoid overflow. | 147 The mean is calculated using log to avoid overflow. |
| 132 """ | 148 """ |
| 133 values = map(float, values) | 149 values = map(float, values) |
| 134 return str(math.exp(sum(map(math.log, values)) / len(values))) | 150 return str(math.exp(sum(map(math.log, values)) / len(values))) |
| 135 | 151 |
| 136 | 152 |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 203 self.graphs = parent.graphs[:] + [suite["name"]] | 219 self.graphs = parent.graphs[:] + [suite["name"]] |
| 204 self.flags = parent.flags[:] + suite.get("flags", []) | 220 self.flags = parent.flags[:] + suite.get("flags", []) |
| 205 self.test_flags = parent.test_flags[:] + suite.get("test_flags", []) | 221 self.test_flags = parent.test_flags[:] + suite.get("test_flags", []) |
| 206 self.resources = parent.resources[:] + suite.get("resources", []) | 222 self.resources = parent.resources[:] + suite.get("resources", []) |
| 207 | 223 |
| 208 # Descrete values (with parent defaults). | 224 # Descrete values (with parent defaults). |
| 209 self.binary = suite.get("binary", parent.binary) | 225 self.binary = suite.get("binary", parent.binary) |
| 210 self.run_count = suite.get("run_count", parent.run_count) | 226 self.run_count = suite.get("run_count", parent.run_count) |
| 211 self.run_count = suite.get("run_count_%s" % arch, self.run_count) | 227 self.run_count = suite.get("run_count_%s" % arch, self.run_count) |
| 212 self.timeout = suite.get("timeout", parent.timeout) | 228 self.timeout = suite.get("timeout", parent.timeout) |
| 229 self.timeout = suite.get("timeout_%s" % arch, self.timeout) |
| 213 self.units = suite.get("units", parent.units) | 230 self.units = suite.get("units", parent.units) |
| 214 self.total = suite.get("total", parent.total) | 231 self.total = suite.get("total", parent.total) |
| 215 | 232 |
| 216 # A regular expression for results. If the parent graph provides a | 233 # A regular expression for results. If the parent graph provides a |
| 217 # regexp and the current suite has none, a string place holder for the | 234 # regexp and the current suite has none, a string place holder for the |
| 218 # suite name is expected. | 235 # suite name is expected. |
| 219 # TODO(machenbach): Currently that makes only sense for the leaf level. | 236 # TODO(machenbach): Currently that makes only sense for the leaf level. |
| 220 # Multiple place holders for multiple levels are not supported. | 237 # Multiple place holders for multiple levels are not supported. |
| 221 if parent.results_regexp: | 238 if parent.results_regexp: |
| 222 regexp_default = parent.results_regexp % re.escape(suite["name"]) | 239 regexp_default = parent.results_regexp % re.escape(suite["name"]) |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 280 | 297 |
| 281 def ChangeCWD(self, suite_path): | 298 def ChangeCWD(self, suite_path): |
| 282 """Changes the cwd to to path defined in the current graph. | 299 """Changes the cwd to to path defined in the current graph. |
| 283 | 300 |
| 284 The tests are supposed to be relative to the suite configuration. | 301 The tests are supposed to be relative to the suite configuration. |
| 285 """ | 302 """ |
| 286 suite_dir = os.path.abspath(os.path.dirname(suite_path)) | 303 suite_dir = os.path.abspath(os.path.dirname(suite_path)) |
| 287 bench_dir = os.path.normpath(os.path.join(*self.path)) | 304 bench_dir = os.path.normpath(os.path.join(*self.path)) |
| 288 os.chdir(os.path.join(suite_dir, bench_dir)) | 305 os.chdir(os.path.join(suite_dir, bench_dir)) |
| 289 | 306 |
| 307 def GetCommandFlags(self): |
| 308 suffix = ["--"] + self.test_flags if self.test_flags else [] |
| 309 return self.flags + [self.main] + suffix |
| 310 |
| 290 def GetCommand(self, shell_dir): | 311 def GetCommand(self, shell_dir): |
| 291 # TODO(machenbach): This requires +.exe if run on windows. | 312 # TODO(machenbach): This requires +.exe if run on windows. |
| 292 suffix = ["--"] + self.test_flags if self.test_flags else [] | 313 return [os.path.join(shell_dir, self.binary)] + self.GetCommandFlags() |
| 293 return ( | |
| 294 [os.path.join(shell_dir, self.binary)] + | |
| 295 self.flags + | |
| 296 [self.main] + | |
| 297 suffix | |
| 298 ) | |
| 299 | 314 |
| 300 def Run(self, runner): | 315 def Run(self, runner): |
| 301 """Iterates over several runs and handles the output for all traces.""" | 316 """Iterates over several runs and handles the output for all traces.""" |
| 302 for stdout in runner(): | 317 for stdout in runner(): |
| 303 for trace in self._children: | 318 for trace in self._children: |
| 304 trace.ConsumeOutput(stdout) | 319 trace.ConsumeOutput(stdout) |
| 305 res = reduce(lambda r, t: r + t.GetResults(), self._children, Results()) | 320 res = reduce(lambda r, t: r + t.GetResults(), self._children, Results()) |
| 306 | 321 |
| 307 if not res.traces or not self.total: | 322 if not res.traces or not self.total: |
| 308 return res | 323 return res |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 401 raise Exception("Invalid suite configuration.") | 416 raise Exception("Invalid suite configuration.") |
| 402 | 417 |
| 403 | 418 |
| 404 def BuildGraphs(suite, arch, parent=None): | 419 def BuildGraphs(suite, arch, parent=None): |
| 405 """Builds a tree structure of graph objects that corresponds to the suite | 420 """Builds a tree structure of graph objects that corresponds to the suite |
| 406 configuration. | 421 configuration. |
| 407 """ | 422 """ |
| 408 parent = parent or DefaultSentinel() | 423 parent = parent or DefaultSentinel() |
| 409 | 424 |
| 410 # TODO(machenbach): Implement notion of cpu type? | 425 # TODO(machenbach): Implement notion of cpu type? |
| 411 if arch not in suite.get("archs", ["ia32", "x64"]): | 426 if arch not in suite.get("archs", SUPPORTED_ARCHS): |
| 412 return None | 427 return None |
| 413 | 428 |
| 414 graph = MakeGraph(suite, arch, parent) | 429 graph = MakeGraph(suite, arch, parent) |
| 415 for subsuite in suite.get("tests", []): | 430 for subsuite in suite.get("tests", []): |
| 416 BuildGraphs(subsuite, arch, graph) | 431 BuildGraphs(subsuite, arch, graph) |
| 417 parent.AppendChild(graph) | 432 parent.AppendChild(graph) |
| 418 return graph | 433 return graph |
| 419 | 434 |
| 420 | 435 |
| 421 def FlattenRunnables(node): | 436 def FlattenRunnables(node): |
| (...skipping 14 matching lines...) Expand all Loading... |
| 436 @staticmethod | 451 @staticmethod |
| 437 def GetPlatform(options): | 452 def GetPlatform(options): |
| 438 if options.arch.startswith("android"): | 453 if options.arch.startswith("android"): |
| 439 return AndroidPlatform(options) | 454 return AndroidPlatform(options) |
| 440 else: | 455 else: |
| 441 return DesktopPlatform(options) | 456 return DesktopPlatform(options) |
| 442 | 457 |
| 443 | 458 |
| 444 class DesktopPlatform(Platform): | 459 class DesktopPlatform(Platform): |
| 445 def __init__(self, options): | 460 def __init__(self, options): |
| 446 workspace = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) | 461 self.shell_dir = options.shell_dir |
| 447 | 462 |
| 448 if options.buildbot: | 463 def PreExecution(self): |
| 449 self.shell_dir = os.path.join(workspace, options.outdir, "Release") | |
| 450 else: | |
| 451 self.shell_dir = os.path.join(workspace, options.outdir, | |
| 452 "%s.release" % options.arch) | |
| 453 | |
| 454 def PrepareExecution(self): | |
| 455 pass | 464 pass |
| 456 | 465 |
| 457 def PrepareTests(self, runnable, path): | 466 def PostExecution(self): |
| 467 pass |
| 468 |
| 469 def PreTests(self, runnable, path): |
| 458 runnable.ChangeCWD(path) | 470 runnable.ChangeCWD(path) |
| 459 | 471 |
| 460 def Run(self, runnable, count): | 472 def Run(self, runnable, count): |
| 461 output = commands.Execute(runnable.GetCommand(self.shell_dir), | 473 output = commands.Execute(runnable.GetCommand(self.shell_dir), |
| 462 timeout=runnable.timeout) | 474 timeout=runnable.timeout) |
| 463 print ">>> Stdout (#%d):" % (count + 1) | 475 print ">>> Stdout (#%d):" % (count + 1) |
| 464 print output.stdout | 476 print output.stdout |
| 465 if output.stderr: # pragma: no cover | 477 if output.stderr: # pragma: no cover |
| 466 # Print stderr for debugging. | 478 # Print stderr for debugging. |
| 467 print ">>> Stderr (#%d):" % (count + 1) | 479 print ">>> Stderr (#%d):" % (count + 1) |
| 468 print output.stderr | 480 print output.stderr |
| 469 if output.timed_out: | 481 if output.timed_out: |
| 470 print ">>> Test timed out after %ss." % runnable.timeout | 482 print ">>> Test timed out after %ss." % runnable.timeout |
| 471 return output.stdout | 483 return output.stdout |
| 472 | 484 |
| 473 | 485 |
| 474 # TODO(machenbach): Implement android platform. | 486 class AndroidPlatform(Platform): # pragma: no cover |
| 475 class AndroidPlatform(Platform): | 487 DEVICE_DIR = "/data/local/tmp/v8/" |
| 488 |
| 476 def __init__(self, options): | 489 def __init__(self, options): |
| 477 pass | 490 self.shell_dir = options.shell_dir |
| 491 LoadAndroidBuildTools(options.android_build_tools) |
| 492 |
| 493 if not options.device: |
| 494 # Detect attached device if not specified. |
| 495 devices = pylib.android_commands.GetAttachedDevices( |
| 496 hardware=True, emulator=False, offline=False) |
| 497 assert devices and len(devices) == 1, ( |
| 498 "None or multiple devices detected. Please specify the device on " |
| 499 "the command-line with --device") |
| 500 options.device = devices[0] |
| 501 adb_wrapper = pylib.android_commands.AndroidCommands(options.device) |
| 502 self.device = device_utils.DeviceUtils(adb_wrapper) |
| 503 self.adb = adb_wrapper.Adb() |
| 504 |
| 505 def PreExecution(self): |
| 506 perf = perf_control.PerfControl(self.device) |
| 507 perf.SetHighPerfMode() |
| 508 |
| 509 def PostExecution(self): |
| 510 perf = perf_control.PerfControl(self.device) |
| 511 perf.SetDefaultPerfMode() |
| 512 self.device.RunShellCommand( |
| 513 ["rm", "-rf", "*"], |
| 514 cwd=AndroidPlatform.DEVICE_DIR, |
| 515 ) |
| 516 |
| 517 def _PushFile(self, host_dir, file_name): |
| 518 file_on_host = os.path.join(host_dir, file_name) |
| 519 file_on_device = AndroidPlatform.DEVICE_DIR + file_name |
| 520 logging.info("adb push %s %s" % (file_on_host, file_on_device)) |
| 521 self.adb.Push(file_on_host, file_on_device) |
| 522 |
| 523 def PreTests(self, runnable, path): |
| 524 suite_dir = os.path.abspath(os.path.dirname(path)) |
| 525 bench_dir = os.path.join(suite_dir, |
| 526 os.path.normpath(os.path.join(*runnable.path))) |
| 527 |
| 528 self._PushFile(self.shell_dir, runnable.binary) |
| 529 self._PushFile(bench_dir, runnable.main) |
| 530 for resource in runnable.resources: |
| 531 self._PushFile(bench_dir, resource) |
| 532 |
| 533 def Run(self, runnable, count): |
| 534 cache = cache_control.CacheControl(self.device) |
| 535 cache.DropRamCaches() |
| 536 binary_on_device = AndroidPlatform.DEVICE_DIR + runnable.binary |
| 537 cmd = [binary_on_device] + runnable.GetCommandFlags() |
| 538 try: |
| 539 output = self.device.RunShellCommand( |
| 540 cmd, |
| 541 cwd=AndroidPlatform.DEVICE_DIR, |
| 542 timeout=runnable.timeout, |
| 543 retries=0, |
| 544 ) |
| 545 stdout = "\n".join(output) |
| 546 print ">>> Stdout (#%d):" % (count + 1) |
| 547 print stdout |
| 548 except device_errors.CommandTimeoutError: |
| 549 print ">>> Test timed out after %ss." % runnable.timeout |
| 550 stdout = "" |
| 551 return stdout |
| 478 | 552 |
| 479 | 553 |
| 480 # TODO: Implement results_processor. | 554 # TODO: Implement results_processor. |
| 481 def Main(args): | 555 def Main(args): |
| 556 logging.getLogger().setLevel(logging.INFO) |
| 482 parser = optparse.OptionParser() | 557 parser = optparse.OptionParser() |
| 483 parser.add_option("--android-build-tools", | 558 parser.add_option("--android-build-tools", |
| 484 help="Path to chromium's build/android.") | 559 help="Path to chromium's build/android.") |
| 485 parser.add_option("--arch", | 560 parser.add_option("--arch", |
| 486 help=("The architecture to run tests for, " | 561 help=("The architecture to run tests for, " |
| 487 "'auto' or 'native' for auto-detect"), | 562 "'auto' or 'native' for auto-detect"), |
| 488 default="x64") | 563 default="x64") |
| 489 parser.add_option("--buildbot", | 564 parser.add_option("--buildbot", |
| 490 help="Adapt to path structure used on buildbots", | 565 help="Adapt to path structure used on buildbots", |
| 491 default=False, action="store_true") | 566 default=False, action="store_true") |
| (...skipping 21 matching lines...) Expand all Loading... |
| 513 bool(options.android_build_tools)): # pragma: no cover | 588 bool(options.android_build_tools)): # pragma: no cover |
| 514 print ("Android architectures imply setting --android-build-tools and the " | 589 print ("Android architectures imply setting --android-build-tools and the " |
| 515 "other way around.") | 590 "other way around.") |
| 516 return 1 | 591 return 1 |
| 517 | 592 |
| 518 if (options.device and not | 593 if (options.device and not |
| 519 options.arch.startswith("android")): # pragma: no cover | 594 options.arch.startswith("android")): # pragma: no cover |
| 520 print "Specifying a device requires an Android architecture to be used." | 595 print "Specifying a device requires an Android architecture to be used." |
| 521 return 1 | 596 return 1 |
| 522 | 597 |
| 598 workspace = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) |
| 599 |
| 600 if options.buildbot: |
| 601 options.shell_dir = os.path.join(workspace, options.outdir, "Release") |
| 602 else: |
| 603 options.shell_dir = os.path.join(workspace, options.outdir, |
| 604 "%s.release" % options.arch) |
| 605 |
| 523 platform = Platform.GetPlatform(options) | 606 platform = Platform.GetPlatform(options) |
| 524 platform.PrepareExecution() | 607 platform.PreExecution() |
| 525 | 608 |
| 526 results = Results() | 609 results = Results() |
| 527 for path in args: | 610 for path in args: |
| 528 path = os.path.abspath(path) | 611 path = os.path.abspath(path) |
| 529 | 612 |
| 530 if not os.path.exists(path): # pragma: no cover | 613 if not os.path.exists(path): # pragma: no cover |
| 531 results.errors.append("Configuration file %s does not exist." % path) | 614 results.errors.append("Configuration file %s does not exist." % path) |
| 532 continue | 615 continue |
| 533 | 616 |
| 534 with open(path) as f: | 617 with open(path) as f: |
| 535 suite = json.loads(f.read()) | 618 suite = json.loads(f.read()) |
| 536 | 619 |
| 537 # If no name is given, default to the file name without .json. | 620 # If no name is given, default to the file name without .json. |
| 538 suite.setdefault("name", os.path.splitext(os.path.basename(path))[0]) | 621 suite.setdefault("name", os.path.splitext(os.path.basename(path))[0]) |
| 539 | 622 |
| 540 for runnable in FlattenRunnables(BuildGraphs(suite, options.arch)): | 623 for runnable in FlattenRunnables(BuildGraphs(suite, options.arch)): |
| 541 print ">>> Running suite: %s" % "/".join(runnable.graphs) | 624 print ">>> Running suite: %s" % "/".join(runnable.graphs) |
| 542 platform.PrepareTests(runnable, path) | 625 platform.PreTests(runnable, path) |
| 543 | 626 |
| 544 def Runner(): | 627 def Runner(): |
| 545 """Output generator that reruns several times.""" | 628 """Output generator that reruns several times.""" |
| 546 for i in xrange(0, max(1, runnable.run_count)): | 629 for i in xrange(0, max(1, runnable.run_count)): |
| 547 # TODO(machenbach): Allow timeout per arch like with run_count per | 630 # TODO(machenbach): Allow timeout per arch like with run_count per |
| 548 # arch. | 631 # arch. |
| 549 yield platform.Run(runnable, i) | 632 yield platform.Run(runnable, i) |
| 550 | 633 |
| 551 # Let runnable iterate over all runs and handle output. | 634 # Let runnable iterate over all runs and handle output. |
| 552 results += runnable.Run(Runner) | 635 results += runnable.Run(Runner) |
| 553 | 636 |
| 637 platform.PostExecution() |
| 638 |
| 554 if options.json_test_results: | 639 if options.json_test_results: |
| 555 results.WriteToFile(options.json_test_results) | 640 results.WriteToFile(options.json_test_results) |
| 556 else: # pragma: no cover | 641 else: # pragma: no cover |
| 557 print results | 642 print results |
| 558 | 643 |
| 559 return min(1, len(results.errors)) | 644 return min(1, len(results.errors)) |
| 560 | 645 |
| 561 if __name__ == "__main__": # pragma: no cover | 646 if __name__ == "__main__": # pragma: no cover |
| 562 sys.exit(Main(sys.argv[1:])) | 647 sys.exit(Main(sys.argv[1:])) |
| OLD | NEW |