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