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_buildbot_annotations: | |
801 list_length = len(revision_list) | |
tonyg
2013/02/14 01:21:01
inline?
shatch
2013/02/14 01:37:47
Done.
| |
802 step_name = 'Bisection Range: [%s - %s]' %\ | |
803 (revision_list[list_length-1], revision_list[0]) | |
tonyg
2013/02/14 01:21:01
indent continued line by 4 spaces
shatch
2013/02/14 01:37:47
Done.
| |
804 OutputAnnotationStepStart(step_name) | |
805 | |
782 print | 806 print |
783 print 'Revisions to bisect on [%s]:' % depot | 807 print 'Revisions to bisect on [%s]:' % depot |
784 for revision_id in revision_list: | 808 for revision_id in revision_list: |
785 print ' -> %s' % (revision_id, ) | 809 print ' -> %s' % (revision_id, ) |
786 print | 810 print |
787 | 811 |
812 if self.opts.output_buildbot_annotations: | |
813 OutputAnnotationStepClosed() | |
814 | |
788 def Run(self, command_to_run, bad_revision_in, good_revision_in, metric): | 815 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 | 816 """Given known good and bad revisions, run a binary search on all |
790 intermediate revisions to determine the CL where the performance regression | 817 intermediate revisions to determine the CL where the performance regression |
791 occurred. | 818 occurred. |
792 | 819 |
793 Args: | 820 Args: |
794 command_to_run: Specify the command to execute the performance test. | 821 command_to_run: Specify the command to execute the performance test. |
795 good_revision: Number/tag of the known good revision. | 822 good_revision: Number/tag of the known good revision. |
796 bad_revision: Number/tag of the known bad revision. | 823 bad_revision: Number/tag of the known bad revision. |
797 metric: The performance metric to monitor. | 824 metric: The performance metric to monitor. |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
839 'src') | 866 'src') |
840 | 867 |
841 if bad_revision is None: | 868 if bad_revision is None: |
842 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (bad_revision_in,) | 869 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (bad_revision_in,) |
843 return results | 870 return results |
844 | 871 |
845 if good_revision is None: | 872 if good_revision is None: |
846 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (good_revision_in,) | 873 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (good_revision_in,) |
847 return results | 874 return results |
848 | 875 |
876 if self.opts.output_buildbot_annotations: | |
877 OutputAnnotationStepStart('Gathering Revisions') | |
878 | |
849 print 'Gathering revision range for bisection.' | 879 print 'Gathering revision range for bisection.' |
850 | 880 |
851 # Retrieve a list of revisions to do bisection on. | 881 # Retrieve a list of revisions to do bisection on. |
852 src_revision_list = self.GetRevisionList(bad_revision, good_revision) | 882 src_revision_list = self.GetRevisionList(bad_revision, good_revision) |
853 | 883 |
884 if self.opts.output_buildbot_annotations: | |
885 OutputAnnotationStepClosed() | |
886 | |
854 if src_revision_list: | 887 if src_revision_list: |
855 # revision_data will store information about a revision such as the | 888 # revision_data will store information about a revision such as the |
856 # depot it came from, the webkit/V8 revision at that time, | 889 # depot it came from, the webkit/V8 revision at that time, |
857 # performance timing, build state, etc... | 890 # performance timing, build state, etc... |
858 revision_data = results['revision_data'] | 891 revision_data = results['revision_data'] |
859 | 892 |
860 # revision_list is the list we're binary searching through at the moment. | 893 # revision_list is the list we're binary searching through at the moment. |
861 revision_list = [] | 894 revision_list = [] |
862 | 895 |
863 sort_key_ids = 0 | 896 sort_key_ids = 0 |
864 | 897 |
865 for current_revision_id in src_revision_list: | 898 for current_revision_id in src_revision_list: |
866 sort_key_ids += 1 | 899 sort_key_ids += 1 |
867 | 900 |
868 revision_data[current_revision_id] = {'value' : None, | 901 revision_data[current_revision_id] = {'value' : None, |
869 'passed' : '?', | 902 'passed' : '?', |
870 'depot' : 'chromium', | 903 'depot' : 'chromium', |
871 'external' : None, | 904 'external' : None, |
872 'sort' : sort_key_ids} | 905 'sort' : sort_key_ids} |
873 revision_list.append(current_revision_id) | 906 revision_list.append(current_revision_id) |
874 | 907 |
875 min_revision = 0 | 908 min_revision = 0 |
876 max_revision = len(revision_list) - 1 | 909 max_revision = len(revision_list) - 1 |
877 | 910 |
878 self.PrintRevisionsToBisectMessage(revision_list, 'src') | 911 self.PrintRevisionsToBisectMessage(revision_list, 'src') |
879 | 912 |
913 if self.opts.output_buildbot_annotations: | |
914 OutputAnnotationStepStart('Gathering Reference Values') | |
915 | |
880 print 'Gathering reference values for bisection.' | 916 print 'Gathering reference values for bisection.' |
881 | 917 |
882 # Perform the performance tests on the good and bad revisions, to get | 918 # Perform the performance tests on the good and bad revisions, to get |
883 # reference values. | 919 # reference values. |
884 (bad_results, good_results) = self.GatherReferenceValues(good_revision, | 920 (bad_results, good_results) = self.GatherReferenceValues(good_revision, |
885 bad_revision, | 921 bad_revision, |
886 command_to_run, | 922 command_to_run, |
887 metric) | 923 metric) |
888 | 924 |
925 if self.opts.output_buildbot_annotations: | |
926 OutputAnnotationStepClosed() | |
927 | |
889 if bad_results[1]: | 928 if bad_results[1]: |
890 results['error'] = bad_results[0] | 929 results['error'] = bad_results[0] |
891 return results | 930 return results |
892 | 931 |
893 if good_results[1]: | 932 if good_results[1]: |
894 results['error'] = good_results[0] | 933 results['error'] = good_results[0] |
895 return results | 934 return results |
896 | 935 |
897 | 936 |
898 # We need these reference values to determine if later runs should be | 937 # 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: | 1017 else: |
979 next_revision_index = int((max_revision - min_revision) / 2) +\ | 1018 next_revision_index = int((max_revision - min_revision) / 2) +\ |
980 min_revision | 1019 min_revision |
981 | 1020 |
982 next_revision_id = revision_list[next_revision_index] | 1021 next_revision_id = revision_list[next_revision_index] |
983 next_revision_data = revision_data[next_revision_id] | 1022 next_revision_data = revision_data[next_revision_id] |
984 next_revision_depot = next_revision_data['depot'] | 1023 next_revision_depot = next_revision_data['depot'] |
985 | 1024 |
986 self.ChangeToDepotWorkingDirectory(next_revision_depot) | 1025 self.ChangeToDepotWorkingDirectory(next_revision_depot) |
987 | 1026 |
1027 if self.opts.output_buildbot_annotations: | |
1028 step_name = 'Working on [%s]' % next_revision_id | |
1029 OutputAnnotationStepStart(step_name) | |
1030 | |
988 print 'Working on revision: [%s]' % next_revision_id | 1031 print 'Working on revision: [%s]' % next_revision_id |
989 | 1032 |
990 run_results = self.SyncBuildAndRunRevision(next_revision_id, | 1033 run_results = self.SyncBuildAndRunRevision(next_revision_id, |
991 next_revision_depot, | 1034 next_revision_depot, |
992 command_to_run, | 1035 command_to_run, |
993 metric) | 1036 metric) |
994 | 1037 |
1038 if self.opts.output_buildbot_annotations: | |
1039 OutputAnnotationStepClosed() | |
1040 | |
995 # If the build is successful, check whether or not the metric | 1041 # If the build is successful, check whether or not the metric |
996 # had regressed. | 1042 # had regressed. |
997 if not run_results[1]: | 1043 if not run_results[1]: |
998 if next_revision_depot == 'chromium': | 1044 if next_revision_depot == 'chromium': |
999 next_revision_data['external'] = run_results[2] | 1045 next_revision_data['external'] = run_results[2] |
1000 | 1046 |
1001 passed_regression = self.CheckIfRunPassed(run_results[0], | 1047 passed_regression = self.CheckIfRunPassed(run_results[0], |
1002 known_good_value, | 1048 known_good_value, |
1003 known_bad_value) | 1049 known_bad_value) |
1004 | 1050 |
(...skipping 21 matching lines...) Expand all Loading... | |
1026 def FormatAndPrintResults(self, bisect_results): | 1072 def FormatAndPrintResults(self, bisect_results): |
1027 """Prints the results from a bisection run in a readable format. | 1073 """Prints the results from a bisection run in a readable format. |
1028 | 1074 |
1029 Args | 1075 Args |
1030 bisect_results: The results from a bisection test run. | 1076 bisect_results: The results from a bisection test run. |
1031 """ | 1077 """ |
1032 revision_data = bisect_results['revision_data'] | 1078 revision_data = bisect_results['revision_data'] |
1033 revision_data_sorted = sorted(revision_data.iteritems(), | 1079 revision_data_sorted = sorted(revision_data.iteritems(), |
1034 key = lambda x: x[1]['sort']) | 1080 key = lambda x: x[1]['sort']) |
1035 | 1081 |
1082 if self.opts.output_buildbot_annotations: | |
1083 OutputAnnotationStepStart('Results') | |
1084 | |
1036 print | 1085 print |
1037 print 'Full results of bisection:' | 1086 print 'Full results of bisection:' |
1038 for current_id, current_data in revision_data_sorted: | 1087 for current_id, current_data in revision_data_sorted: |
1039 build_status = current_data['passed'] | 1088 build_status = current_data['passed'] |
1040 | 1089 |
1041 if type(build_status) is bool: | 1090 if type(build_status) is bool: |
1042 build_status = int(build_status) | 1091 build_status = int(build_status) |
1043 | 1092 |
1044 print ' %8s %s %s' % (current_data['depot'], current_id, build_status) | 1093 print ' %8s %s %s' % (current_data['depot'], current_id, build_status) |
1045 print | 1094 print |
1046 | 1095 |
1047 # Find range where it possibly broke. | 1096 # Find range where it possibly broke. |
1048 first_working_revision = None | 1097 first_working_revision = None |
1049 last_broken_revision = None | 1098 last_broken_revision = None |
1050 | 1099 |
1051 for k, v in revision_data_sorted: | 1100 for k, v in revision_data_sorted: |
1052 if v['passed'] == 1: | 1101 if v['passed'] == 1: |
1053 if not first_working_revision: | 1102 if not first_working_revision: |
1054 first_working_revision = k | 1103 first_working_revision = k |
1055 | 1104 |
1056 if not v['passed']: | 1105 if not v['passed']: |
1057 last_broken_revision = k | 1106 last_broken_revision = k |
1058 | 1107 |
1059 if last_broken_revision != None and first_working_revision != None: | 1108 if last_broken_revision != None and first_working_revision != None: |
1060 print 'Results: Regression was detected as a result of changes on:' | 1109 print 'Results: Regression may have occurred in range:' |
1061 print ' -> First Bad Revision: [%s] [%s]' %\ | 1110 print ' -> First Bad Revision: [%s] [%s]' %\ |
1062 (last_broken_revision, | 1111 (last_broken_revision, |
1063 revision_data[last_broken_revision]['depot']) | 1112 revision_data[last_broken_revision]['depot']) |
1064 print ' -> Last Good Revision: [%s] [%s]' %\ | 1113 print ' -> Last Good Revision: [%s] [%s]' %\ |
1065 (first_working_revision, | 1114 (first_working_revision, |
1066 revision_data[first_working_revision]['depot']) | 1115 revision_data[first_working_revision]['depot']) |
1067 | 1116 |
1117 if self.opts.output_buildbot_annotations: | |
1118 OutputAnnotationStepClosed() | |
1119 | |
1068 | 1120 |
1069 def DetermineAndCreateSourceControl(): | 1121 def DetermineAndCreateSourceControl(): |
1070 """Attempts to determine the underlying source control workflow and returns | 1122 """Attempts to determine the underlying source control workflow and returns |
1071 a SourceControl object. | 1123 a SourceControl object. |
1072 | 1124 |
1073 Returns: | 1125 Returns: |
1074 An instance of a SourceControl object, or None if the current workflow | 1126 An instance of a SourceControl object, or None if the current workflow |
1075 is unsupported. | 1127 is unsupported. |
1076 """ | 1128 """ |
1077 | 1129 |
(...skipping 27 matching lines...) Expand all Loading... | |
1105 help='A revision to start bisection where performance' + | 1157 help='A revision to start bisection where performance' + |
1106 ' test is known to pass. Must be earlier than the ' + | 1158 ' test is known to pass. Must be earlier than the ' + |
1107 'bad revision. May be either a git or svn revision.') | 1159 'bad revision. May be either a git or svn revision.') |
1108 parser.add_option('-m', '--metric', | 1160 parser.add_option('-m', '--metric', |
1109 type='str', | 1161 type='str', |
1110 help='The desired metric to bisect on. For example ' + | 1162 help='The desired metric to bisect on. For example ' + |
1111 '"vm_rss_final_b/vm_rss_f_b"') | 1163 '"vm_rss_final_b/vm_rss_f_b"') |
1112 parser.add_option('--use_goma', | 1164 parser.add_option('--use_goma', |
1113 action="store_true", | 1165 action="store_true", |
1114 help='Add a bunch of extra threads for goma.') | 1166 help='Add a bunch of extra threads for goma.') |
1167 parser.add_option('--output_buildbot_annotations', | |
1168 action="store_true", | |
1169 help='Add extra annotation output for buildbot.') | |
1115 parser.add_option('--debug_ignore_build', | 1170 parser.add_option('--debug_ignore_build', |
1116 action="store_true", | 1171 action="store_true", |
1117 help='DEBUG: Don\'t perform builds.') | 1172 help='DEBUG: Don\'t perform builds.') |
1118 parser.add_option('--debug_ignore_sync', | 1173 parser.add_option('--debug_ignore_sync', |
1119 action="store_true", | 1174 action="store_true", |
1120 help='DEBUG: Don\'t perform syncs.') | 1175 help='DEBUG: Don\'t perform syncs.') |
1121 parser.add_option('--debug_ignore_perf_test', | 1176 parser.add_option('--debug_ignore_perf_test', |
1122 action="store_true", | 1177 action="store_true", |
1123 help='DEBUG: Don\'t perform performance tests.') | 1178 help='DEBUG: Don\'t perform performance tests.') |
1124 (opts, args) = parser.parse_args() | 1179 (opts, args) = parser.parse_args() |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1185 if not(bisect_results['error']): | 1240 if not(bisect_results['error']): |
1186 bisect_test.FormatAndPrintResults(bisect_results) | 1241 bisect_test.FormatAndPrintResults(bisect_results) |
1187 return 0 | 1242 return 0 |
1188 else: | 1243 else: |
1189 print 'Error: ' + bisect_results['error'] | 1244 print 'Error: ' + bisect_results['error'] |
1190 print | 1245 print |
1191 return 1 | 1246 return 1 |
1192 | 1247 |
1193 if __name__ == '__main__': | 1248 if __name__ == '__main__': |
1194 sys.exit(main()) | 1249 sys.exit(main()) |
OLD | NEW |