Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2013 The Chromium 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 """Performance Test Bisect Tool | 6 """Performance Test Bisect Tool |
| 7 | 7 |
| 8 This script bisects a series of changelists using binary search. It starts at | 8 This script bisects a series of changelists using binary search. It starts at |
| 9 a bad revision where a performance metric has regressed, and asks for a last | 9 a bad revision where a performance metric has regressed, and asks for a last |
| 10 known-good revision. It will then binary search across this revision range by | 10 known-good revision. It will then binary search across this revision range by |
| (...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 119 True if the string can be converted to an int. | 119 True if the string can be converted to an int. |
| 120 """ | 120 """ |
| 121 try: | 121 try: |
| 122 int(string_to_check) | 122 int(string_to_check) |
| 123 | 123 |
| 124 return True | 124 return True |
| 125 except ValueError: | 125 except ValueError: |
| 126 return False | 126 return False |
| 127 | 127 |
| 128 | 128 |
| 129 def OutputAnnotationStepStart(name): | |
| 130 """Outputs appropriate annotation to signal the start of a step to | |
| 131 a trybot. | |
| 132 | |
| 133 Args: | |
| 134 name: The name of the step. | |
| 135 """ | |
| 136 print '@@@SEED_STEP %s@@@' % name | |
| 137 print '@@@STEP_CURSOR %s@@@' % name | |
| 138 print '@@@STEP_STARTED@@@' | |
| 139 | |
| 140 | |
| 141 def OutputAnnotationStepClosed(): | |
| 142 """Outputs appropriate annotation to signal the closing of a step to | |
| 143 a trybot.""" | |
| 144 print '@@@STEP_CLOSED@@@' | |
| 145 | |
| 146 | |
| 129 def RunProcess(command): | 147 def RunProcess(command): |
| 130 """Run an arbitrary command, returning its output and return code. | 148 """Run an arbitrary command, returning its output and return code. |
| 131 | 149 |
| 132 Args: | 150 Args: |
| 133 command: A list containing the command and args to execute. | 151 command: A list containing the command and args to execute. |
| 134 | 152 |
| 135 Returns: | 153 Returns: |
| 136 A tuple of the output and return code. | 154 A tuple of the output and return code. |
| 137 """ | 155 """ |
| 138 # On Windows, use shell=True to get PATH interpretation. | 156 # On Windows, use shell=True to get PATH interpretation. |
| (...skipping 633 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 772 for i in xrange(num_depot_revisions): | 790 for i in xrange(num_depot_revisions): |
| 773 r = revisions[i] | 791 r = revisions[i] |
| 774 | 792 |
| 775 revision_data[r] = {'revision' : r, | 793 revision_data[r] = {'revision' : r, |
| 776 'depot' : depot, | 794 'depot' : depot, |
| 777 'value' : None, | 795 'value' : None, |
| 778 'passed' : '?', | 796 'passed' : '?', |
| 779 'sort' : i + sort + 1} | 797 'sort' : i + sort + 1} |
| 780 | 798 |
| 781 def PrintRevisionsToBisectMessage(self, revision_list, depot): | 799 def PrintRevisionsToBisectMessage(self, revision_list, depot): |
| 800 if self.opts.output_annotations: | |
| 801 OutputAnnotationStepStart('Bisection Range [%s]' % depot) | |
|
tonyg
2013/02/14 00:17:47
depot seems like the wrong thing to print as the b
shatch
2013/02/14 01:16:01
Done.
| |
| 802 | |
| 782 print | 803 print |
| 783 print 'Revisions to bisect on [%s]:' % depot | 804 print 'Revisions to bisect on [%s]:' % depot |
| 784 for revision_id in revision_list: | 805 for revision_id in revision_list: |
| 785 print ' -> %s' % (revision_id, ) | 806 print ' -> %s' % (revision_id, ) |
| 786 print | 807 print |
| 787 | 808 |
| 809 if self.opts.output_annotations: | |
| 810 OutputAnnotationStepClosed() | |
| 811 | |
| 788 def Run(self, command_to_run, bad_revision_in, good_revision_in, metric): | 812 def Run(self, command_to_run, bad_revision_in, good_revision_in, metric): |
| 789 """Given known good and bad revisions, run a binary search on all | 813 """Given known good and bad revisions, run a binary search on all |
| 790 intermediate revisions to determine the CL where the performance regression | 814 intermediate revisions to determine the CL where the performance regression |
| 791 occurred. | 815 occurred. |
| 792 | 816 |
| 793 Args: | 817 Args: |
| 794 command_to_run: Specify the command to execute the performance test. | 818 command_to_run: Specify the command to execute the performance test. |
| 795 good_revision: Number/tag of the known good revision. | 819 good_revision: Number/tag of the known good revision. |
| 796 bad_revision: Number/tag of the known bad revision. | 820 bad_revision: Number/tag of the known bad revision. |
| 797 metric: The performance metric to monitor. | 821 metric: The performance metric to monitor. |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 839 'src') | 863 'src') |
| 840 | 864 |
| 841 if bad_revision is None: | 865 if bad_revision is None: |
| 842 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (bad_revision_in,) | 866 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (bad_revision_in,) |
| 843 return results | 867 return results |
| 844 | 868 |
| 845 if good_revision is None: | 869 if good_revision is None: |
| 846 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (good_revision_in,) | 870 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (good_revision_in,) |
| 847 return results | 871 return results |
| 848 | 872 |
| 873 if self.opts.output_annotations: | |
| 874 OutputAnnotationStepStart('Gathering Revisions') | |
| 875 | |
| 849 print 'Gathering revision range for bisection.' | 876 print 'Gathering revision range for bisection.' |
| 850 | 877 |
| 851 # Retrieve a list of revisions to do bisection on. | 878 # Retrieve a list of revisions to do bisection on. |
| 852 src_revision_list = self.GetRevisionList(bad_revision, good_revision) | 879 src_revision_list = self.GetRevisionList(bad_revision, good_revision) |
| 853 | 880 |
| 881 if self.opts.output_annotations: | |
| 882 OutputAnnotationStepClosed() | |
| 883 | |
| 854 if src_revision_list: | 884 if src_revision_list: |
| 855 # revision_data will store information about a revision such as the | 885 # revision_data will store information about a revision such as the |
| 856 # depot it came from, the webkit/V8 revision at that time, | 886 # depot it came from, the webkit/V8 revision at that time, |
| 857 # performance timing, build state, etc... | 887 # performance timing, build state, etc... |
| 858 revision_data = results['revision_data'] | 888 revision_data = results['revision_data'] |
| 859 | 889 |
| 860 # revision_list is the list we're binary searching through at the moment. | 890 # revision_list is the list we're binary searching through at the moment. |
| 861 revision_list = [] | 891 revision_list = [] |
| 862 | 892 |
| 863 sort_key_ids = 0 | 893 sort_key_ids = 0 |
| 864 | 894 |
| 865 for current_revision_id in src_revision_list: | 895 for current_revision_id in src_revision_list: |
| 866 sort_key_ids += 1 | 896 sort_key_ids += 1 |
| 867 | 897 |
| 868 revision_data[current_revision_id] = {'value' : None, | 898 revision_data[current_revision_id] = {'value' : None, |
| 869 'passed' : '?', | 899 'passed' : '?', |
| 870 'depot' : 'chromium', | 900 'depot' : 'chromium', |
| 871 'external' : None, | 901 'external' : None, |
| 872 'sort' : sort_key_ids} | 902 'sort' : sort_key_ids} |
| 873 revision_list.append(current_revision_id) | 903 revision_list.append(current_revision_id) |
| 874 | 904 |
| 875 min_revision = 0 | 905 min_revision = 0 |
| 876 max_revision = len(revision_list) - 1 | 906 max_revision = len(revision_list) - 1 |
| 877 | 907 |
| 878 self.PrintRevisionsToBisectMessage(revision_list, 'src') | 908 self.PrintRevisionsToBisectMessage(revision_list, 'src') |
| 879 | 909 |
| 910 if self.opts.output_annotations: | |
| 911 OutputAnnotationStepStart('Gathering Reference Values') | |
| 912 | |
| 880 print 'Gathering reference values for bisection.' | 913 print 'Gathering reference values for bisection.' |
| 881 | 914 |
| 882 # Perform the performance tests on the good and bad revisions, to get | 915 # Perform the performance tests on the good and bad revisions, to get |
| 883 # reference values. | 916 # reference values. |
| 884 (bad_results, good_results) = self.GatherReferenceValues(good_revision, | 917 (bad_results, good_results) = self.GatherReferenceValues(good_revision, |
| 885 bad_revision, | 918 bad_revision, |
| 886 command_to_run, | 919 command_to_run, |
| 887 metric) | 920 metric) |
| 888 | 921 |
| 922 if self.opts.output_annotations: | |
| 923 OutputAnnotationStepClosed() | |
| 924 | |
| 889 if bad_results[1]: | 925 if bad_results[1]: |
| 890 results['error'] = bad_results[0] | 926 results['error'] = bad_results[0] |
| 891 return results | 927 return results |
| 892 | 928 |
| 893 if good_results[1]: | 929 if good_results[1]: |
| 894 results['error'] = good_results[0] | 930 results['error'] = good_results[0] |
| 895 return results | 931 return results |
| 896 | 932 |
| 897 | 933 |
| 898 # We need these reference values to determine if later runs should be | 934 # We need these reference values to determine if later runs should be |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 978 else: | 1014 else: |
| 979 next_revision_index = int((max_revision - min_revision) / 2) +\ | 1015 next_revision_index = int((max_revision - min_revision) / 2) +\ |
| 980 min_revision | 1016 min_revision |
| 981 | 1017 |
| 982 next_revision_id = revision_list[next_revision_index] | 1018 next_revision_id = revision_list[next_revision_index] |
| 983 next_revision_data = revision_data[next_revision_id] | 1019 next_revision_data = revision_data[next_revision_id] |
| 984 next_revision_depot = next_revision_data['depot'] | 1020 next_revision_depot = next_revision_data['depot'] |
| 985 | 1021 |
| 986 self.ChangeToDepotWorkingDirectory(next_revision_depot) | 1022 self.ChangeToDepotWorkingDirectory(next_revision_depot) |
| 987 | 1023 |
| 1024 if self.opts.output_annotations: | |
| 1025 step_name = 'Working on [%s]' % next_revision_id | |
| 1026 OutputAnnotationStepStart(step_name) | |
| 1027 | |
| 988 print 'Working on revision: [%s]' % next_revision_id | 1028 print 'Working on revision: [%s]' % next_revision_id |
| 989 | 1029 |
| 990 run_results = self.SyncBuildAndRunRevision(next_revision_id, | 1030 run_results = self.SyncBuildAndRunRevision(next_revision_id, |
| 991 next_revision_depot, | 1031 next_revision_depot, |
| 992 command_to_run, | 1032 command_to_run, |
| 993 metric) | 1033 metric) |
| 994 | 1034 |
| 1035 if self.opts.output_annotations: | |
| 1036 OutputAnnotationStepClosed() | |
| 1037 | |
| 995 # If the build is successful, check whether or not the metric | 1038 # If the build is successful, check whether or not the metric |
| 996 # had regressed. | 1039 # had regressed. |
| 997 if not run_results[1]: | 1040 if not run_results[1]: |
| 998 if next_revision_depot == 'chromium': | 1041 if next_revision_depot == 'chromium': |
| 999 next_revision_data['external'] = run_results[2] | 1042 next_revision_data['external'] = run_results[2] |
| 1000 | 1043 |
| 1001 passed_regression = self.CheckIfRunPassed(run_results[0], | 1044 passed_regression = self.CheckIfRunPassed(run_results[0], |
| 1002 known_good_value, | 1045 known_good_value, |
| 1003 known_bad_value) | 1046 known_bad_value) |
| 1004 | 1047 |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 1026 def FormatAndPrintResults(self, bisect_results): | 1069 def FormatAndPrintResults(self, bisect_results): |
| 1027 """Prints the results from a bisection run in a readable format. | 1070 """Prints the results from a bisection run in a readable format. |
| 1028 | 1071 |
| 1029 Args | 1072 Args |
| 1030 bisect_results: The results from a bisection test run. | 1073 bisect_results: The results from a bisection test run. |
| 1031 """ | 1074 """ |
| 1032 revision_data = bisect_results['revision_data'] | 1075 revision_data = bisect_results['revision_data'] |
| 1033 revision_data_sorted = sorted(revision_data.iteritems(), | 1076 revision_data_sorted = sorted(revision_data.iteritems(), |
| 1034 key = lambda x: x[1]['sort']) | 1077 key = lambda x: x[1]['sort']) |
| 1035 | 1078 |
| 1079 if self.opts.output_annotations: | |
| 1080 OutputAnnotationStepStart('Results') | |
| 1081 | |
| 1036 print | 1082 print |
| 1037 print 'Full results of bisection:' | 1083 print 'Full results of bisection:' |
| 1038 for current_id, current_data in revision_data_sorted: | 1084 for current_id, current_data in revision_data_sorted: |
| 1039 build_status = current_data['passed'] | 1085 build_status = current_data['passed'] |
| 1040 | 1086 |
| 1041 if type(build_status) is bool: | 1087 if type(build_status) is bool: |
| 1042 build_status = int(build_status) | 1088 build_status = int(build_status) |
| 1043 | 1089 |
| 1044 print ' %8s %s %s' % (current_data['depot'], current_id, build_status) | 1090 print ' %8s %s %s' % (current_data['depot'], current_id, build_status) |
| 1045 print | 1091 print |
| 1046 | 1092 |
| 1047 # Find range where it possibly broke. | 1093 # Find range where it possibly broke. |
| 1048 first_working_revision = None | 1094 first_working_revision = None |
| 1049 last_broken_revision = None | 1095 last_broken_revision = None |
| 1050 | 1096 |
| 1051 for k, v in revision_data_sorted: | 1097 for k, v in revision_data_sorted: |
| 1052 if v['passed']: | 1098 if v['passed']: |
| 1053 if first_working_revision is None: | 1099 if first_working_revision is None: |
| 1054 first_working_revision = k | 1100 first_working_revision = k |
| 1055 | 1101 |
| 1056 if not v['passed']: | 1102 if not v['passed']: |
| 1057 last_broken_revision = k | 1103 last_broken_revision = k |
| 1058 | 1104 |
| 1059 if last_broken_revision != None and first_working_revision != None: | 1105 if last_broken_revision != None and first_working_revision != None: |
| 1060 print 'Results: Regression was detected as a result of changes on:' | 1106 print 'Results: Regression may have occurred in range:' |
| 1061 print ' -> First Bad Revision: [%s] [%s]' %\ | 1107 print ' -> First Bad Revision: [%s] [%s]' %\ |
| 1062 (last_broken_revision, | 1108 (last_broken_revision, |
| 1063 revision_data[last_broken_revision]['depot']) | 1109 revision_data[last_broken_revision]['depot']) |
| 1064 print ' -> Last Good Revision: [%s] [%s]' %\ | 1110 print ' -> Last Good Revision: [%s] [%s]' %\ |
| 1065 (first_working_revision, | 1111 (first_working_revision, |
| 1066 revision_data[first_working_revision]['depot']) | 1112 revision_data[first_working_revision]['depot']) |
| 1067 | 1113 |
| 1114 if self.opts.output_annotations: | |
| 1115 OutputAnnotationStepClosed() | |
| 1116 | |
| 1068 | 1117 |
| 1069 def DetermineAndCreateSourceControl(): | 1118 def DetermineAndCreateSourceControl(): |
| 1070 """Attempts to determine the underlying source control workflow and returns | 1119 """Attempts to determine the underlying source control workflow and returns |
| 1071 a SourceControl object. | 1120 a SourceControl object. |
| 1072 | 1121 |
| 1073 Returns: | 1122 Returns: |
| 1074 An instance of a SourceControl object, or None if the current workflow | 1123 An instance of a SourceControl object, or None if the current workflow |
| 1075 is unsupported. | 1124 is unsupported. |
| 1076 """ | 1125 """ |
| 1077 | 1126 |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 1105 help='A revision to start bisection where performance' + | 1154 help='A revision to start bisection where performance' + |
| 1106 ' test is known to pass. Must be earlier than the ' + | 1155 ' test is known to pass. Must be earlier than the ' + |
| 1107 'bad revision. May be either a git or svn revision.') | 1156 'bad revision. May be either a git or svn revision.') |
| 1108 parser.add_option('-m', '--metric', | 1157 parser.add_option('-m', '--metric', |
| 1109 type='str', | 1158 type='str', |
| 1110 help='The desired metric to bisect on. For example ' + | 1159 help='The desired metric to bisect on. For example ' + |
| 1111 '"vm_rss_final_b/vm_rss_f_b"') | 1160 '"vm_rss_final_b/vm_rss_f_b"') |
| 1112 parser.add_option('--use_goma', | 1161 parser.add_option('--use_goma', |
| 1113 action="store_true", | 1162 action="store_true", |
| 1114 help='Add a bunch of extra threads for goma.') | 1163 help='Add a bunch of extra threads for goma.') |
| 1164 parser.add_option('--output_annotations', | |
|
tonyg
2013/02/14 00:17:47
I'd name this --buildbot_annotations or --output_b
shatch
2013/02/14 01:16:01
Done.
| |
| 1165 action="store_true", | |
| 1166 help='Add extra annotation output for trybot.') | |
| 1115 parser.add_option('--debug_ignore_build', | 1167 parser.add_option('--debug_ignore_build', |
| 1116 action="store_true", | 1168 action="store_true", |
| 1117 help='DEBUG: Don\'t perform builds.') | 1169 help='DEBUG: Don\'t perform builds.') |
| 1118 parser.add_option('--debug_ignore_sync', | 1170 parser.add_option('--debug_ignore_sync', |
| 1119 action="store_true", | 1171 action="store_true", |
| 1120 help='DEBUG: Don\'t perform syncs.') | 1172 help='DEBUG: Don\'t perform syncs.') |
| 1121 parser.add_option('--debug_ignore_perf_test', | 1173 parser.add_option('--debug_ignore_perf_test', |
| 1122 action="store_true", | 1174 action="store_true", |
| 1123 help='DEBUG: Don\'t perform performance tests.') | 1175 help='DEBUG: Don\'t perform performance tests.') |
| 1124 (opts, args) = parser.parse_args() | 1176 (opts, args) = parser.parse_args() |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1185 if not(bisect_results['error']): | 1237 if not(bisect_results['error']): |
| 1186 bisect_test.FormatAndPrintResults(bisect_results) | 1238 bisect_test.FormatAndPrintResults(bisect_results) |
| 1187 return 0 | 1239 return 0 |
| 1188 else: | 1240 else: |
| 1189 print 'Error: ' + bisect_results['error'] | 1241 print 'Error: ' + bisect_results['error'] |
| 1190 print | 1242 print |
| 1191 return 1 | 1243 return 1 |
| 1192 | 1244 |
| 1193 if __name__ == '__main__': | 1245 if __name__ == '__main__': |
| 1194 sys.exit(main()) | 1246 sys.exit(main()) |
| OLD | NEW |