Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(152)

Side by Side Diff: tools/run_perf.py

Issue 1681283004: [tools] add --pretty switch to run_perf.py Base URL: https://chromium.googlesource.com/v8/v8.git@master
Patch Set: more clean, less nit Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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 the benchmark provided a stddev use that directly.
213 if self.stddev:
214 return self.stddev
215 # If no stddev was provided calculate it from the results.
216 if len(self.results) == 0:
217 return 0
218 mean = self.GetMean()
219 square_deviation = sum((x-mean)**2 for x in self.results)
220 return (square_deviation / len(self.results)) ** 0.5
221
222 def GetMean(self):
223 if len(self.results) == 0:
224 return 0
225 # If the tests provided a stddev the results consists of one single average
226 # value, so return that instead.
227 if self.stddev:
228 return self.results[0]
229 # For a non-zero length results list calculate the average here.
230 return sum(self.results) / len(self.results)
231
211 def GetResults(self): 232 def GetResults(self):
212 return Results([{ 233 return Results([{
213 "graphs": self.graphs, 234 "graphs": self.graphs,
214 "units": self.units, 235 "units": self.units,
215 "results": self.results, 236 "results": [str(x) for x in self.results],
216 "stddev": self.stddev, 237 "average": self.GetMean(),
238 "stddev": self.GetDeviation(),
217 }], self.errors) 239 }], self.errors)
218 240
219 241
220 class NullMeasurement(object): 242 class NullMeasurement(object):
221 """Null object to avoid having extra logic for configurations that didn't 243 """Null object to avoid having extra logic for configurations that didn't
222 run like running without patch on trybots. 244 run like running without patch on trybots.
223 """ 245 """
224 def ConsumeOutput(self, stdout): 246 def ConsumeOutput(self, stdout):
225 pass 247 pass
226 248
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after
398 self.timeout = suite.get("timeout_%s" % arch, self.timeout) 420 self.timeout = suite.get("timeout_%s" % arch, self.timeout)
399 self.units = suite.get("units", parent.units) 421 self.units = suite.get("units", parent.units)
400 self.total = suite.get("total", parent.total) 422 self.total = suite.get("total", parent.total)
401 423
402 # A regular expression for results. If the parent graph provides a 424 # 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 425 # regexp and the current suite has none, a string place holder for the
404 # suite name is expected. 426 # suite name is expected.
405 # TODO(machenbach): Currently that makes only sense for the leaf level. 427 # TODO(machenbach): Currently that makes only sense for the leaf level.
406 # Multiple place holders for multiple levels are not supported. 428 # Multiple place holders for multiple levels are not supported.
407 if parent.results_regexp: 429 if parent.results_regexp:
408 regexp_default = parent.results_regexp % re.escape(suite["name"]) 430 try:
431 regexp_default = parent.results_regexp % re.escape(suite["name"])
432 except TypeError:
433 regexp_default = parent.results_regexp
409 else: 434 else:
410 regexp_default = None 435 regexp_default = None
411 self.results_regexp = suite.get("results_regexp", regexp_default) 436 self.results_regexp = suite.get("results_regexp", regexp_default)
412 437
413 # A similar regular expression for the standard deviation (optional). 438 # A similar regular expression for the standard deviation (optional).
414 if parent.stddev_regexp: 439 if parent.stddev_regexp:
415 stddev_default = parent.stddev_regexp % re.escape(suite["name"]) 440 stddev_default = parent.stddev_regexp % re.escape(suite["name"])
416 else: 441 else:
417 stddev_default = None 442 stddev_default = None
418 self.stddev_regexp = suite.get("stddev_regexp", stddev_default) 443 self.stddev_regexp = suite.get("stddev_regexp", stddev_default)
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after
579 self.shell_dir = options.shell_dir 604 self.shell_dir = options.shell_dir
580 self.shell_dir_no_patch = options.shell_dir_no_patch 605 self.shell_dir_no_patch = options.shell_dir_no_patch
581 self.extra_flags = options.extra_flags.split() 606 self.extra_flags = options.extra_flags.split()
582 607
583 @staticmethod 608 @staticmethod
584 def GetPlatform(options): 609 def GetPlatform(options):
585 if options.android_build_tools: 610 if options.android_build_tools:
586 return AndroidPlatform(options) 611 return AndroidPlatform(options)
587 else: 612 else:
588 return DesktopPlatform(options) 613 return DesktopPlatform(options)
589 614
Camillo Bruni 2016/02/11 13:20:29 Moved all basic "public" methods up here.
615 def GetPrettyFormatted(self, options):
616 return self
617
618 def PreExecution(self):
619 pass
620
621 def PostExecution(self):
622 pass
623
624 def PreTests(self, node, path):
625 pass
626
627 def PrintResult(self, result):
628 pass
629
590 def _Run(self, runnable, count, no_patch=False): 630 def _Run(self, runnable, count, no_patch=False):
591 raise NotImplementedError() # pragma: no cover 631 raise NotImplementedError() # pragma: no cover
592 632
593 def Run(self, runnable, count): 633 def Run(self, runnable, count):
594 """Execute the benchmark's main file. 634 """Execute the benchmark's main file.
595 635
596 If options.shell_dir_no_patch is specified, the benchmark is run once with 636 If options.shell_dir_no_patch is specified, the benchmark is run once with
597 and once without patch. 637 and once without patch.
598 Args: 638 Args:
599 runnable: A Runnable benchmark instance. 639 runnable: A Runnable benchmark instance.
600 count: The number of this (repeated) run. 640 count: The number of this (repeated) run.
601 Returns: A tuple with the benchmark outputs with and without patch. The 641 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 642 latter will be None if options.shell_dir_no_patch was not
603 specified. 643 specified.
604 """ 644 """
605 stdout = self._Run(runnable, count, no_patch=False) 645 stdout = self._Run(runnable, count, no_patch=False)
606 if self.shell_dir_no_patch: 646 if self.shell_dir_no_patch:
607 return stdout, self._Run(runnable, count, no_patch=True) 647 return stdout, self._Run(runnable, count, no_patch=True)
608 else: 648 else:
609 return stdout, None 649 return stdout, None
610 650
611 651
652 class PlatformFormattedMixin(object):
653 """
654 Helper mixin that adds formatted output used when running benchmarks
655 with the --pretty flag.
656 """
657 def PrintResult(self, result):
658 if result.errors:
659 print "\r:Errors:"
660 print "\n".join(set(result.errors))
661 else:
662 trace = result.traces[0]
663 average = trace['average']
664 stdev = trace['stddev']
665 stdev_percentage = 100 * stdev / average if average != 0 else 0
666 result_string = "\r %s +/- %3.2f%% %s" % (
667 average, stdev_percentage, trace['units'])
668 sys.stdout.write(result_string)
669 # Fill with spaces up to 80 characters.
670 sys.stdout.write(' '*max(0, 80-len(result_string)))
671 sys.stdout.write("\n")
672 sys.stdout.flush()
673
674 def _PrintStdout(self, title, output):
675 sys.stdout.write("\r")
676 if output.exit_code != 0:
677 print output.stdout
678 return
679 # Assume the time is on the last line
680 result_line = output.stdout.splitlines()[-1].strip()
681 sys.stdout.write(result_line)
682 # Fill with spaces up to 80 characters.
683 sys.stdout.write(' '*max(0, 80-len(result_line)))
684 sys.stdout.flush()
685
686
612 class DesktopPlatform(Platform): 687 class DesktopPlatform(Platform):
613 def __init__(self, options): 688 def __init__(self, options):
614 super(DesktopPlatform, self).__init__(options) 689 super(DesktopPlatform, self).__init__(options)
615 690
616 def PreExecution(self): 691 def GetPrettyFormatted(self, options):
617 pass 692 return PrettyFormattedDesktopPlatform(options)
618
619 def PostExecution(self):
620 pass
621 693
622 def PreTests(self, node, path): 694 def PreTests(self, node, path):
623 if isinstance(node, RunnableConfig): 695 if isinstance(node, RunnableConfig):
624 node.ChangeCWD(path) 696 node.ChangeCWD(path)
625 697
698 def _PrintStdout(self, title, output):
699 print title % "Stdout"
700 print output.stdout
701
626 def _Run(self, runnable, count, no_patch=False): 702 def _Run(self, runnable, count, no_patch=False):
627 suffix = ' - without patch' if no_patch else '' 703 suffix = ' - without patch' if no_patch else ''
628 shell_dir = self.shell_dir_no_patch if no_patch else self.shell_dir 704 shell_dir = self.shell_dir_no_patch if no_patch else self.shell_dir
629 title = ">>> %%s (#%d)%s:" % ((count + 1), suffix) 705 title = ">>> %%s (#%d)%s:" % ((count + 1), suffix)
630 try: 706 try:
631 output = commands.Execute( 707 output = commands.Execute(
632 runnable.GetCommand(shell_dir, self.extra_flags), 708 runnable.GetCommand(shell_dir, self.extra_flags),
633 timeout=runnable.timeout, 709 timeout=runnable.timeout,
634 ) 710 )
635 except OSError as e: # pragma: no cover 711 except OSError as e: # pragma: no cover
636 print title % "OSError" 712 print title % "OSError"
637 print e 713 print e
638 return "" 714 return ""
639 print title % "Stdout" 715 self._PrintStdout(title, output)
640 print output.stdout
641 if output.stderr: # pragma: no cover 716 if output.stderr: # pragma: no cover
642 # Print stderr for debugging. 717 # Print stderr for debugging.
643 print title % "Stderr" 718 print title % "Stderr"
644 print output.stderr 719 print output.stderr
645 if output.timed_out: 720 if output.timed_out:
646 print ">>> Test timed out after %ss." % runnable.timeout 721 print ">>> Test timed out after %ss." % runnable.timeout
647 if '--prof' in self.extra_flags: 722 if '--prof' in self.extra_flags:
648 os_prefix = {"linux": "linux", "macos": "mac"}.get(utils.GuessOS()) 723 os_prefix = {"linux": "linux", "macos": "mac"}.get(utils.GuessOS())
649 if os_prefix: 724 if os_prefix:
650 tick_tools = os.path.join(TOOLS_BASE, "%s-tick-processor" % os_prefix) 725 tick_tools = os.path.join(TOOLS_BASE, "%s-tick-processor" % os_prefix)
651 subprocess.check_call(tick_tools + " --only-summary", shell=True) 726 subprocess.check_call(tick_tools + " --only-summary", shell=True)
652 else: # pragma: no cover 727 else: # pragma: no cover
653 print "Profiler option currently supported on Linux and Mac OS." 728 print "Profiler option currently supported on Linux and Mac OS."
654 return output.stdout 729 return output.stdout
655 730
656 731
732 class PrettyFormattedDesktopPlatform(PlatformFormattedMixin, DesktopPlatform):
733 pass
734
735
657 class AndroidPlatform(Platform): # pragma: no cover 736 class AndroidPlatform(Platform): # pragma: no cover
658 DEVICE_DIR = "/data/local/tmp/v8/" 737 DEVICE_DIR = "/data/local/tmp/v8/"
659 738
660 def __init__(self, options): 739 def __init__(self, options):
661 super(AndroidPlatform, self).__init__(options) 740 super(AndroidPlatform, self).__init__(options)
662 LoadAndroidBuildTools(options.android_build_tools) 741 LoadAndroidBuildTools(options.android_build_tools)
663 742
664 if not options.device: 743 if not options.device:
665 # Detect attached device if not specified. 744 # Detect attached device if not specified.
666 devices = adb_wrapper.AdbWrapper.Devices() 745 devices = adb_wrapper.AdbWrapper.Devices()
667 assert devices and len(devices) == 1, ( 746 assert devices and len(devices) == 1, (
668 "None or multiple devices detected. Please specify the device on " 747 "None or multiple devices detected. Please specify the device on "
669 "the command-line with --device") 748 "the command-line with --device")
670 options.device = str(devices[0]) 749 options.device = str(devices[0])
671 self.adb_wrapper = adb_wrapper.AdbWrapper(options.device) 750 self.adb_wrapper = adb_wrapper.AdbWrapper(options.device)
672 self.device = device_utils.DeviceUtils(self.adb_wrapper) 751 self.device = device_utils.DeviceUtils(self.adb_wrapper)
673 752
753 def GetPrettyFormatted(self, options):
754 return PrettyFormattedAndroidPlatform(options)
755
674 def PreExecution(self): 756 def PreExecution(self):
675 perf = perf_control.PerfControl(self.device) 757 perf = perf_control.PerfControl(self.device)
676 perf.SetHighPerfMode() 758 perf.SetHighPerfMode()
677 759
678 # Remember what we have already pushed to the device. 760 # Remember what we have already pushed to the device.
679 self.pushed = set() 761 self.pushed = set()
680 762
681 def PostExecution(self): 763 def PostExecution(self):
682 perf = perf_control.PerfControl(self.device) 764 perf = perf_control.PerfControl(self.device)
683 perf.SetDefaultPerfMode() 765 perf.SetDefaultPerfMode()
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after
774 bench_rel = "." 856 bench_rel = "."
775 857
776 try: 858 try:
777 output = self.device.RunShellCommand( 859 output = self.device.RunShellCommand(
778 cmd, 860 cmd,
779 cwd=os.path.join(AndroidPlatform.DEVICE_DIR, bench_rel), 861 cwd=os.path.join(AndroidPlatform.DEVICE_DIR, bench_rel),
780 timeout=runnable.timeout, 862 timeout=runnable.timeout,
781 retries=0, 863 retries=0,
782 ) 864 )
783 stdout = "\n".join(output) 865 stdout = "\n".join(output)
784 print title % "Stdout" 866 print title % "Stdout"
Michael Achenbach 2016/02/11 13:48:30 You could add the self._PrintStdout(title, output)
785 print stdout 867 print stdout
786 except device_errors.CommandTimeoutError: 868 except device_errors.CommandTimeoutError:
787 print ">>> Test timed out after %ss." % runnable.timeout 869 print ">>> Test timed out after %ss." % runnable.timeout
788 stdout = "" 870 stdout = ""
789 return stdout 871 return stdout
790 872
791 873
874 class PrettyFormattedAndroidPlatform(PlatformFormattedMixin, AndroidPlatform):
875 pass
876
877
792 # TODO: Implement results_processor. 878 # TODO: Implement results_processor.
793 def Main(args): 879 def Main(args):
794 logging.getLogger().setLevel(logging.INFO) 880 logging.getLogger().setLevel(logging.INFO)
795 parser = optparse.OptionParser() 881 parser = optparse.OptionParser()
796 parser.add_option("--android-build-tools", 882 parser.add_option("--android-build-tools",
797 help="Path to chromium's build/android. Specifying this " 883 help="Path to chromium's build/android. Specifying this "
798 "option will run tests using android platform.") 884 "option will run tests using android platform.")
799 parser.add_option("--arch", 885 parser.add_option("--arch",
800 help=("The architecture to run tests for, " 886 help=("The architecture to run tests for, "
801 "'auto' or 'native' for auto-detect"), 887 "'auto' or 'native' for auto-detect"),
802 default="x64") 888 default="x64")
803 parser.add_option("--buildbot", 889 parser.add_option("--buildbot",
804 help="Adapt to path structure used on buildbots", 890 help="Adapt to path structure used on buildbots",
805 default=False, action="store_true") 891 default=False, action="store_true")
806 parser.add_option("--device", 892 parser.add_option("--device",
807 help="The device ID to run Android tests on. If not given " 893 help="The device ID to run Android tests on. If not given "
808 "it will be autodetected.") 894 "it will be autodetected.")
809 parser.add_option("--extra-flags", 895 parser.add_option("--extra-flags",
810 help="Additional flags to pass to the test executable", 896 help="Additional flags to pass to the test executable",
811 default="") 897 default="")
812 parser.add_option("--json-test-results", 898 parser.add_option("--json-test-results",
813 help="Path to a file for storing json results.") 899 help="Path to a file for storing json results.")
814 parser.add_option("--json-test-results-no-patch", 900 parser.add_option("--json-test-results-no-patch",
815 help="Path to a file for storing json results from run " 901 help="Path to a file for storing json results from run "
816 "without patch.") 902 "without patch.")
817 parser.add_option("--outdir", help="Base directory with compile output", 903 parser.add_option("--outdir", help="Base directory with compile output",
818 default="out") 904 default="out")
819 parser.add_option("--outdir-no-patch", 905 parser.add_option("--outdir-no-patch",
820 help="Base directory with compile output without patch") 906 help="Base directory with compile output without patch")
907 parser.add_option("--pretty",
908 help="Print human readable output",
909 default=False, action="store_true")
821 parser.add_option("--binary-override-path", 910 parser.add_option("--binary-override-path",
822 help="JavaScript engine binary. By default, d8 under " 911 help="JavaScript engine binary. By default, d8 under "
823 "architecture-specific build dir. " 912 "architecture-specific build dir. "
824 "Not supported in conjunction with outdir-no-patch.") 913 "Not supported in conjunction with outdir-no-patch.")
825 914
826 (options, args) = parser.parse_args(args) 915 (options, args) = parser.parse_args(args)
827 916
828 if len(args) == 0: # pragma: no cover 917 if len(args) == 0: # pragma: no cover
829 parser.print_help() 918 parser.print_help()
830 return 1 919 return 1
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
866 options.shell_dir = os.path.dirname(options.binary_override_path) 955 options.shell_dir = os.path.dirname(options.binary_override_path)
867 default_binary_name = os.path.basename(options.binary_override_path) 956 default_binary_name = os.path.basename(options.binary_override_path)
868 957
869 if options.outdir_no_patch: 958 if options.outdir_no_patch:
870 options.shell_dir_no_patch = os.path.join( 959 options.shell_dir_no_patch = os.path.join(
871 workspace, options.outdir_no_patch, build_config) 960 workspace, options.outdir_no_patch, build_config)
872 else: 961 else:
873 options.shell_dir_no_patch = None 962 options.shell_dir_no_patch = None
874 963
875 platform = Platform.GetPlatform(options) 964 platform = Platform.GetPlatform(options)
965 if options.pretty:
966 platform = platform.GetPrettyFormatted(options)
876 967
877 results = Results() 968 results = Results()
878 results_no_patch = Results() 969 results_no_patch = Results()
879 for path in args: 970 for path in args:
880 path = os.path.abspath(path) 971 path = os.path.abspath(path)
881 972
882 if not os.path.exists(path): # pragma: no cover 973 if not os.path.exists(path): # pragma: no cover
883 results.errors.append("Configuration file %s does not exist." % path) 974 results.errors.append("Configuration file %s does not exist." % path)
884 continue 975 continue
885 976
(...skipping 21 matching lines...) Expand all
907 def Runner(): 998 def Runner():
908 """Output generator that reruns several times.""" 999 """Output generator that reruns several times."""
909 for i in xrange(0, max(1, runnable.run_count)): 1000 for i in xrange(0, max(1, runnable.run_count)):
910 # TODO(machenbach): Allow timeout per arch like with run_count per 1001 # TODO(machenbach): Allow timeout per arch like with run_count per
911 # arch. 1002 # arch.
912 yield platform.Run(runnable, i) 1003 yield platform.Run(runnable, i)
913 1004
914 # Let runnable iterate over all runs and handle output. 1005 # Let runnable iterate over all runs and handle output.
915 result, result_no_patch = runnable.Run( 1006 result, result_no_patch = runnable.Run(
916 Runner, trybot=options.shell_dir_no_patch) 1007 Runner, trybot=options.shell_dir_no_patch)
1008 platform.PrintResult(result)
917 results += result 1009 results += result
918 results_no_patch += result_no_patch 1010 results_no_patch += result_no_patch
919 platform.PostExecution() 1011 platform.PostExecution()
920 1012
921 if options.json_test_results: 1013 if options.json_test_results:
922 results.WriteToFile(options.json_test_results) 1014 results.WriteToFile(options.json_test_results)
923 else: # pragma: no cover 1015 else: # pragma: no cover
924 print results 1016 if not options.pretty:
1017 print results
925 1018
926 if options.json_test_results_no_patch: 1019 if options.json_test_results_no_patch:
927 results_no_patch.WriteToFile(options.json_test_results_no_patch) 1020 results_no_patch.WriteToFile(options.json_test_results_no_patch)
928 else: # pragma: no cover 1021 else: # pragma: no cover
929 print results_no_patch 1022 if not options.pretty:
1023 print results_no_patch
930 1024
931 return min(1, len(results.errors)) 1025 return min(1, len(results.errors))
932 1026
933 if __name__ == "__main__": # pragma: no cover 1027 if __name__ == "__main__": # pragma: no cover
934 sys.exit(Main(sys.argv[1:])) 1028 sys.exit(Main(sys.argv[1:]))
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698