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 |