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 |