Chromium Code Reviews| 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 172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 183 self.units = units | 183 self.units = units |
| 184 self.results_regexp = results_regexp | 184 self.results_regexp = results_regexp |
| 185 self.stddev_regexp = stddev_regexp | 185 self.stddev_regexp = stddev_regexp |
| 186 self.results = [] | 186 self.results = [] |
| 187 self.errors = [] | 187 self.errors = [] |
| 188 self.stddev = "" | 188 self.stddev = "" |
| 189 | 189 |
| 190 def ConsumeOutput(self, stdout): | 190 def ConsumeOutput(self, stdout): |
| 191 try: | 191 try: |
| 192 result = re.search(self.results_regexp, stdout, re.M).group(1) | 192 result = re.search(self.results_regexp, stdout, re.M).group(1) |
| 193 self.results.append(str(float(result))) | 193 self.results.append(float(result)) |
| 194 except ValueError: | 194 except ValueError: |
| 195 self.errors.append("Regexp \"%s\" returned a non-numeric for test %s." | 195 self.errors.append("Regexp \"%s\" returned a non-numeric for test %s." |
| 196 % (self.results_regexp, self.name)) | 196 % (self.results_regexp, self.name)) |
| 197 except: | 197 except: |
| 198 self.errors.append("Regexp \"%s\" didn't match for test %s." | 198 self.errors.append("Regexp \"%s\" didn't match for test %s." |
| 199 % (self.results_regexp, self.name)) | 199 % (self.results_regexp, self.name)) |
| 200 | 200 |
| 201 try: | 201 try: |
| 202 if self.stddev_regexp and self.stddev: | 202 if self.stddev_regexp and self.stddev: |
| 203 self.errors.append("Test %s should only run once since a stddev " | 203 self.errors.append("Test %s should only run once since a stddev " |
| 204 "is provided by the test." % self.name) | 204 "is provided by the test." % self.name) |
| 205 if self.stddev_regexp: | 205 if self.stddev_regexp: |
| 206 self.stddev = re.search(self.stddev_regexp, stdout, re.M).group(1) | 206 self.stddev = re.search(self.stddev_regexp, stdout, re.M).group(1) |
| 207 except: | 207 except: |
| 208 self.errors.append("Regexp \"%s\" didn't match for test %s." | 208 self.errors.append("Regexp \"%s\" didn't match for test %s." |
| 209 % (self.stddev_regexp, self.name)) | 209 % (self.stddev_regexp, self.name)) |
| 210 | 210 |
| 211 def GetDeviation(self): | |
| 212 if self.stddev: | |
| 213 return self.stddev | |
| 214 if len(self.results) == 0: | |
| 215 return 0 | |
| 216 mean = self.GetMean() | |
| 217 square_deviation = sum((x-mean)**2 for x in self.results) | |
| 218 return (square_deviation / len(self.results)) ** 0.5 | |
| 219 | |
| 220 def GetMean(self): | |
| 221 if len(self.results) == 0: | |
| 222 return 0 | |
| 223 if self.stddev: | |
|
Michael Achenbach
2016/02/11 13:01:11
This might deserve a small comment (sorry for not
| |
| 224 return self.results[0] | |
| 225 return sum(x for x in self.results) / len(self.results) | |
|
Michael Achenbach
2016/02/11 13:01:11
nit: Now that you removed the float() conversion,
Camillo Bruni
2016/02/11 13:20:29
getting there... incrementally :D
| |
| 226 | |
| 211 def GetResults(self): | 227 def GetResults(self): |
| 212 return Results([{ | 228 return Results([{ |
| 213 "graphs": self.graphs, | 229 "graphs": self.graphs, |
| 214 "units": self.units, | 230 "units": self.units, |
| 215 "results": self.results, | 231 "results": [str(x) for x in self.results], |
| 216 "stddev": self.stddev, | 232 "average": self.GetMean(), |
| 233 "stddev": self.GetDeviation(), | |
| 217 }], self.errors) | 234 }], self.errors) |
| 218 | 235 |
| 219 | 236 |
| 220 class NullMeasurement(object): | 237 class NullMeasurement(object): |
| 221 """Null object to avoid having extra logic for configurations that didn't | 238 """Null object to avoid having extra logic for configurations that didn't |
| 222 run like running without patch on trybots. | 239 run like running without patch on trybots. |
| 223 """ | 240 """ |
| 224 def ConsumeOutput(self, stdout): | 241 def ConsumeOutput(self, stdout): |
| 225 pass | 242 pass |
| 226 | 243 |
| (...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 398 self.timeout = suite.get("timeout_%s" % arch, self.timeout) | 415 self.timeout = suite.get("timeout_%s" % arch, self.timeout) |
| 399 self.units = suite.get("units", parent.units) | 416 self.units = suite.get("units", parent.units) |
| 400 self.total = suite.get("total", parent.total) | 417 self.total = suite.get("total", parent.total) |
| 401 | 418 |
| 402 # A regular expression for results. If the parent graph provides a | 419 # 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 | 420 # regexp and the current suite has none, a string place holder for the |
| 404 # suite name is expected. | 421 # suite name is expected. |
| 405 # TODO(machenbach): Currently that makes only sense for the leaf level. | 422 # TODO(machenbach): Currently that makes only sense for the leaf level. |
| 406 # Multiple place holders for multiple levels are not supported. | 423 # Multiple place holders for multiple levels are not supported. |
| 407 if parent.results_regexp: | 424 if parent.results_regexp: |
| 408 regexp_default = parent.results_regexp % re.escape(suite["name"]) | 425 try: |
| 426 regexp_default = parent.results_regexp % re.escape(suite["name"]) | |
| 427 except TypeError: | |
| 428 regexp_default = parent.results_regexp | |
| 409 else: | 429 else: |
| 410 regexp_default = None | 430 regexp_default = None |
| 411 self.results_regexp = suite.get("results_regexp", regexp_default) | 431 self.results_regexp = suite.get("results_regexp", regexp_default) |
| 412 | 432 |
| 413 # A similar regular expression for the standard deviation (optional). | 433 # A similar regular expression for the standard deviation (optional). |
| 414 if parent.stddev_regexp: | 434 if parent.stddev_regexp: |
| 415 stddev_default = parent.stddev_regexp % re.escape(suite["name"]) | 435 stddev_default = parent.stddev_regexp % re.escape(suite["name"]) |
| 416 else: | 436 else: |
| 417 stddev_default = None | 437 stddev_default = None |
| 418 self.stddev_regexp = suite.get("stddev_regexp", stddev_default) | 438 self.stddev_regexp = suite.get("stddev_regexp", stddev_default) |
| (...skipping 161 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 580 self.shell_dir_no_patch = options.shell_dir_no_patch | 600 self.shell_dir_no_patch = options.shell_dir_no_patch |
| 581 self.extra_flags = options.extra_flags.split() | 601 self.extra_flags = options.extra_flags.split() |
| 582 | 602 |
| 583 @staticmethod | 603 @staticmethod |
| 584 def GetPlatform(options): | 604 def GetPlatform(options): |
| 585 if options.android_build_tools: | 605 if options.android_build_tools: |
| 586 return AndroidPlatform(options) | 606 return AndroidPlatform(options) |
| 587 else: | 607 else: |
| 588 return DesktopPlatform(options) | 608 return DesktopPlatform(options) |
| 589 | 609 |
| 610 def GetPrettyFormatted(self, options): | |
| 611 return self | |
| 612 | |
| 590 def _Run(self, runnable, count, no_patch=False): | 613 def _Run(self, runnable, count, no_patch=False): |
| 591 raise NotImplementedError() # pragma: no cover | 614 raise NotImplementedError() # pragma: no cover |
| 592 | 615 |
| 593 def Run(self, runnable, count): | 616 def Run(self, runnable, count): |
| 594 """Execute the benchmark's main file. | 617 """Execute the benchmark's main file. |
| 595 | 618 |
| 596 If options.shell_dir_no_patch is specified, the benchmark is run once with | 619 If options.shell_dir_no_patch is specified, the benchmark is run once with |
| 597 and once without patch. | 620 and once without patch. |
| 598 Args: | 621 Args: |
| 599 runnable: A Runnable benchmark instance. | 622 runnable: A Runnable benchmark instance. |
| 600 count: The number of this (repeated) run. | 623 count: The number of this (repeated) run. |
| 601 Returns: A tuple with the benchmark outputs with and without patch. The | 624 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 | 625 latter will be None if options.shell_dir_no_patch was not |
| 603 specified. | 626 specified. |
| 604 """ | 627 """ |
| 605 stdout = self._Run(runnable, count, no_patch=False) | 628 stdout = self._Run(runnable, count, no_patch=False) |
| 606 if self.shell_dir_no_patch: | 629 if self.shell_dir_no_patch: |
| 607 return stdout, self._Run(runnable, count, no_patch=True) | 630 return stdout, self._Run(runnable, count, no_patch=True) |
| 608 else: | 631 else: |
| 609 return stdout, None | 632 return stdout, None |
| 610 | 633 |
| 611 | 634 |
| 612 class DesktopPlatform(Platform): | 635 class DesktopPlatform(Platform): |
| 613 def __init__(self, options): | 636 def __init__(self, options): |
| 614 super(DesktopPlatform, self).__init__(options) | 637 super(DesktopPlatform, self).__init__(options) |
| 615 | 638 |
| 639 def GetPrettyFormatted(self, options): | |
| 640 return PrettyFormattedDesktopPlatform(options) | |
| 641 | |
| 616 def PreExecution(self): | 642 def PreExecution(self): |
| 617 pass | 643 pass |
| 618 | 644 |
| 619 def PostExecution(self): | 645 def PostExecution(self): |
| 620 pass | 646 pass |
| 621 | 647 |
| 622 def PreTests(self, node, path): | 648 def PreTests(self, node, path): |
| 623 if isinstance(node, RunnableConfig): | 649 if isinstance(node, RunnableConfig): |
| 624 node.ChangeCWD(path) | 650 node.ChangeCWD(path) |
| 625 | 651 |
| 652 def PrintResult(self): | |
|
Michael Achenbach
2016/02/11 13:01:11
This needs a second argument, just like the functi
| |
| 653 pass | |
| 654 | |
| 655 def _PrintStdout(self, title, output): | |
| 656 print title % "Stdout" | |
| 657 print output.stdout | |
| 658 | |
| 626 def _Run(self, runnable, count, no_patch=False): | 659 def _Run(self, runnable, count, no_patch=False): |
| 627 suffix = ' - without patch' if no_patch else '' | 660 suffix = ' - without patch' if no_patch else '' |
| 628 shell_dir = self.shell_dir_no_patch if no_patch else self.shell_dir | 661 shell_dir = self.shell_dir_no_patch if no_patch else self.shell_dir |
| 629 title = ">>> %%s (#%d)%s:" % ((count + 1), suffix) | 662 title = ">>> %%s (#%d)%s:" % ((count + 1), suffix) |
| 630 try: | 663 try: |
| 631 output = commands.Execute( | 664 output = commands.Execute( |
| 632 runnable.GetCommand(shell_dir, self.extra_flags), | 665 runnable.GetCommand(shell_dir, self.extra_flags), |
| 633 timeout=runnable.timeout, | 666 timeout=runnable.timeout, |
| 634 ) | 667 ) |
| 635 except OSError as e: # pragma: no cover | 668 except OSError as e: # pragma: no cover |
| 636 print title % "OSError" | 669 print title % "OSError" |
| 637 print e | 670 print e |
| 638 return "" | 671 return "" |
| 639 print title % "Stdout" | 672 self._PrintStdout(title, output) |
| 640 print output.stdout | |
| 641 if output.stderr: # pragma: no cover | 673 if output.stderr: # pragma: no cover |
| 642 # Print stderr for debugging. | 674 # Print stderr for debugging. |
| 643 print title % "Stderr" | 675 print title % "Stderr" |
| 644 print output.stderr | 676 print output.stderr |
| 645 if output.timed_out: | 677 if output.timed_out: |
| 646 print ">>> Test timed out after %ss." % runnable.timeout | 678 print ">>> Test timed out after %ss." % runnable.timeout |
| 647 if '--prof' in self.extra_flags: | 679 if '--prof' in self.extra_flags: |
| 648 os_prefix = {"linux": "linux", "macos": "mac"}.get(utils.GuessOS()) | 680 os_prefix = {"linux": "linux", "macos": "mac"}.get(utils.GuessOS()) |
| 649 if os_prefix: | 681 if os_prefix: |
| 650 tick_tools = os.path.join(TOOLS_BASE, "%s-tick-processor" % os_prefix) | 682 tick_tools = os.path.join(TOOLS_BASE, "%s-tick-processor" % os_prefix) |
| 651 subprocess.check_call(tick_tools + " --only-summary", shell=True) | 683 subprocess.check_call(tick_tools + " --only-summary", shell=True) |
| 652 else: # pragma: no cover | 684 else: # pragma: no cover |
| 653 print "Profiler option currently supported on Linux and Mac OS." | 685 print "Profiler option currently supported on Linux and Mac OS." |
| 654 return output.stdout | 686 return output.stdout |
| 655 | 687 |
| 656 | 688 |
| 689 class PrettyFormattedDesktopPlatform(DesktopPlatform): | |
| 690 | |
|
Michael Achenbach
2016/02/11 13:01:10
Could you add a todo to make this available to the
Camillo Bruni
2016/02/11 13:20:29
done
| |
| 691 def PrintResult(self, result): | |
| 692 if result.errors: | |
| 693 print "\r:Errors:" | |
| 694 print "\n".join(set(result.errors)) | |
| 695 else: | |
| 696 trace = result.traces[0] | |
| 697 average = trace['average'] | |
| 698 stdev = trace['stddev'] | |
| 699 stdev_percentage = 100 * stdev / average if average != 0 else 0 | |
| 700 result_string = "\r %s +/- %3.2f%% %s" % ( | |
| 701 average, stdev_percentage, trace['units']) | |
| 702 sys.stdout.write(result_string) | |
| 703 # Fill with spaces up to 80 characters. | |
| 704 sys.stdout.write(' '*max(0, 80-len(result_string))) | |
| 705 sys.stdout.write("\n") | |
| 706 sys.stdout.flush() | |
| 707 | |
| 708 def _PrintStdout(self, title, output): | |
| 709 sys.stdout.write("\r") | |
| 710 if output.exit_code != 0: | |
| 711 print output.stdout | |
| 712 return | |
| 713 # Assume the time is on the last line | |
| 714 result_line = output.stdout.splitlines()[-1].strip() | |
| 715 sys.stdout.write(result_line) | |
| 716 # Fill with spaces up to 80 characters. | |
| 717 sys.stdout.write(' '*max(0, 80-len(result_line))) | |
| 718 sys.stdout.flush() | |
| 719 | |
| 720 | |
| 657 class AndroidPlatform(Platform): # pragma: no cover | 721 class AndroidPlatform(Platform): # pragma: no cover |
| 658 DEVICE_DIR = "/data/local/tmp/v8/" | 722 DEVICE_DIR = "/data/local/tmp/v8/" |
| 659 | 723 |
| 660 def __init__(self, options): | 724 def __init__(self, options): |
| 661 super(AndroidPlatform, self).__init__(options) | 725 super(AndroidPlatform, self).__init__(options) |
| 662 LoadAndroidBuildTools(options.android_build_tools) | 726 LoadAndroidBuildTools(options.android_build_tools) |
| 663 | 727 |
| 664 if not options.device: | 728 if not options.device: |
| 665 # Detect attached device if not specified. | 729 # Detect attached device if not specified. |
| 666 devices = adb_wrapper.AdbWrapper.Devices() | 730 devices = adb_wrapper.AdbWrapper.Devices() |
| (...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 811 default="") | 875 default="") |
| 812 parser.add_option("--json-test-results", | 876 parser.add_option("--json-test-results", |
| 813 help="Path to a file for storing json results.") | 877 help="Path to a file for storing json results.") |
| 814 parser.add_option("--json-test-results-no-patch", | 878 parser.add_option("--json-test-results-no-patch", |
| 815 help="Path to a file for storing json results from run " | 879 help="Path to a file for storing json results from run " |
| 816 "without patch.") | 880 "without patch.") |
| 817 parser.add_option("--outdir", help="Base directory with compile output", | 881 parser.add_option("--outdir", help="Base directory with compile output", |
| 818 default="out") | 882 default="out") |
| 819 parser.add_option("--outdir-no-patch", | 883 parser.add_option("--outdir-no-patch", |
| 820 help="Base directory with compile output without patch") | 884 help="Base directory with compile output without patch") |
| 885 parser.add_option("--pretty", | |
| 886 help="Print human readable output", | |
| 887 default=False, action="store_true") | |
| 821 parser.add_option("--binary-override-path", | 888 parser.add_option("--binary-override-path", |
| 822 help="JavaScript engine binary. By default, d8 under " | 889 help="JavaScript engine binary. By default, d8 under " |
| 823 "architecture-specific build dir. " | 890 "architecture-specific build dir. " |
| 824 "Not supported in conjunction with outdir-no-patch.") | 891 "Not supported in conjunction with outdir-no-patch.") |
| 825 | 892 |
| 826 (options, args) = parser.parse_args(args) | 893 (options, args) = parser.parse_args(args) |
| 827 | 894 |
| 828 if len(args) == 0: # pragma: no cover | 895 if len(args) == 0: # pragma: no cover |
| 829 parser.print_help() | 896 parser.print_help() |
| 830 return 1 | 897 return 1 |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 866 options.shell_dir = os.path.dirname(options.binary_override_path) | 933 options.shell_dir = os.path.dirname(options.binary_override_path) |
| 867 default_binary_name = os.path.basename(options.binary_override_path) | 934 default_binary_name = os.path.basename(options.binary_override_path) |
| 868 | 935 |
| 869 if options.outdir_no_patch: | 936 if options.outdir_no_patch: |
| 870 options.shell_dir_no_patch = os.path.join( | 937 options.shell_dir_no_patch = os.path.join( |
| 871 workspace, options.outdir_no_patch, build_config) | 938 workspace, options.outdir_no_patch, build_config) |
| 872 else: | 939 else: |
| 873 options.shell_dir_no_patch = None | 940 options.shell_dir_no_patch = None |
| 874 | 941 |
| 875 platform = Platform.GetPlatform(options) | 942 platform = Platform.GetPlatform(options) |
| 943 if options.pretty: | |
| 944 platform = platform.GetPrettyFormatted(options) | |
| 876 | 945 |
| 877 results = Results() | 946 results = Results() |
| 878 results_no_patch = Results() | 947 results_no_patch = Results() |
| 879 for path in args: | 948 for path in args: |
| 880 path = os.path.abspath(path) | 949 path = os.path.abspath(path) |
| 881 | 950 |
| 882 if not os.path.exists(path): # pragma: no cover | 951 if not os.path.exists(path): # pragma: no cover |
| 883 results.errors.append("Configuration file %s does not exist." % path) | 952 results.errors.append("Configuration file %s does not exist." % path) |
| 884 continue | 953 continue |
| 885 | 954 |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 907 def Runner(): | 976 def Runner(): |
| 908 """Output generator that reruns several times.""" | 977 """Output generator that reruns several times.""" |
| 909 for i in xrange(0, max(1, runnable.run_count)): | 978 for i in xrange(0, max(1, runnable.run_count)): |
| 910 # TODO(machenbach): Allow timeout per arch like with run_count per | 979 # TODO(machenbach): Allow timeout per arch like with run_count per |
| 911 # arch. | 980 # arch. |
| 912 yield platform.Run(runnable, i) | 981 yield platform.Run(runnable, i) |
| 913 | 982 |
| 914 # Let runnable iterate over all runs and handle output. | 983 # Let runnable iterate over all runs and handle output. |
| 915 result, result_no_patch = runnable.Run( | 984 result, result_no_patch = runnable.Run( |
| 916 Runner, trybot=options.shell_dir_no_patch) | 985 Runner, trybot=options.shell_dir_no_patch) |
| 986 platform.PrintResult(result) | |
| 917 results += result | 987 results += result |
| 918 results_no_patch += result_no_patch | 988 results_no_patch += result_no_patch |
| 919 platform.PostExecution() | 989 platform.PostExecution() |
| 920 | 990 |
| 921 if options.json_test_results: | 991 if options.json_test_results: |
| 922 results.WriteToFile(options.json_test_results) | 992 results.WriteToFile(options.json_test_results) |
| 923 else: # pragma: no cover | 993 else: # pragma: no cover |
| 924 print results | 994 if not options.pretty: |
| 995 print results | |
| 925 | 996 |
| 926 if options.json_test_results_no_patch: | 997 if options.json_test_results_no_patch: |
| 927 results_no_patch.WriteToFile(options.json_test_results_no_patch) | 998 results_no_patch.WriteToFile(options.json_test_results_no_patch) |
| 928 else: # pragma: no cover | 999 else: # pragma: no cover |
| 929 print results_no_patch | 1000 if not options.pretty: |
| 1001 print results_no_patch | |
| 930 | 1002 |
| 931 return min(1, len(results.errors)) | 1003 return min(1, len(results.errors)) |
| 932 | 1004 |
| 933 if __name__ == "__main__": # pragma: no cover | 1005 if __name__ == "__main__": # pragma: no cover |
| 934 sys.exit(Main(sys.argv[1:])) | 1006 sys.exit(Main(sys.argv[1:])) |
| OLD | NEW |