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

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: cleanup and addressing comments 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 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
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
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
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
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
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:]))
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