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 17 matching lines...) Expand all Loading... | |
28 An example usage (using git hashes): | 28 An example usage (using git hashes): |
29 | 29 |
30 ./tools/bisect-perf-regression.py -c\ | 30 ./tools/bisect-perf-regression.py -c\ |
31 "out/Release/performance_ui_tests --gtest_filter=ShutdownTest.SimpleUserQuit"\ | 31 "out/Release/performance_ui_tests --gtest_filter=ShutdownTest.SimpleUserQuit"\ |
32 -g 1f6e67861535121c5c819c16a666f2436c207e7b\ | 32 -g 1f6e67861535121c5c819c16a666f2436c207e7b\ |
33 -b b732f23b4f81c382db0b23b9035f3dadc7d925bb\ | 33 -b b732f23b4f81c382db0b23b9035f3dadc7d925bb\ |
34 -m shutdown/simple-user-quit | 34 -m shutdown/simple-user-quit |
35 | 35 |
36 """ | 36 """ |
37 | 37 |
38 | 38 import errno |
39 import imp | |
40 import optparse | |
41 import os | |
39 import re | 42 import re |
40 import os | 43 import shlex |
41 import imp | 44 import subprocess |
42 import sys | 45 import sys |
43 import shlex | |
44 import optparse | |
45 import subprocess | |
46 | 46 |
47 | 47 |
48 # The additional repositories that might need to be bisected. | 48 # The additional repositories that might need to be bisected. |
49 # If the repository has any dependant repositories (such as skia/src needs | 49 # If the repository has any dependant repositories (such as skia/src needs |
50 # skia/include and skia/gyp to be updated), specify them in the 'depends' | 50 # skia/include and skia/gyp to be updated), specify them in the 'depends' |
51 # so that they're synced appropriately. | 51 # so that they're synced appropriately. |
52 # Format is: | 52 # Format is: |
53 # src: path to the working directory. | 53 # src: path to the working directory. |
54 # recurse: True if this repositry will get bisected. | 54 # recurse: True if this repositry will get bisected. |
55 # depends: A list of other repositories that are actually part of the same | 55 # depends: A list of other repositories that are actually part of the same |
(...skipping 26 matching lines...) Expand all Loading... | |
82 "recurse" : False, | 82 "recurse" : False, |
83 "svn" : "http://skia.googlecode.com/svn/trunk/gyp", | 83 "svn" : "http://skia.googlecode.com/svn/trunk/gyp", |
84 "depends" : None | 84 "depends" : None |
85 } | 85 } |
86 } | 86 } |
87 | 87 |
88 DEPOT_NAMES = DEPOT_DEPS_NAME.keys() | 88 DEPOT_NAMES = DEPOT_DEPS_NAME.keys() |
89 | 89 |
90 FILE_DEPS_GIT = '.DEPS.git' | 90 FILE_DEPS_GIT = '.DEPS.git' |
91 | 91 |
92 GCLIENT_SPEC = """ | |
93 solutions = [ | |
94 { "name" : "src", | |
95 "url" : "https://chromium.googlesource.com/chromium/src.git", | |
96 "deps_file" : ".DEPS.git", | |
97 "managed" : True, | |
98 "custom_deps" : { | |
99 }, | |
100 "safesync_url": "", | |
101 }, | |
102 { "name" : "src-internal", | |
103 "url" : "ssh://gerrit-int.chromium.org:29419/" + | |
104 "chrome/src-internal.git", | |
105 "deps_file" : ".DEPS.git", | |
106 }, | |
107 ] | |
108 """ | |
109 GCLIENT_SPEC = ''.join([l for l in GCLIENT_SPEC.splitlines()]) | |
110 | |
92 | 111 |
93 | 112 |
94 def IsStringFloat(string_to_check): | 113 def IsStringFloat(string_to_check): |
95 """Checks whether or not the given string can be converted to a floating | 114 """Checks whether or not the given string can be converted to a floating |
96 point number. | 115 point number. |
97 | 116 |
98 Args: | 117 Args: |
99 string_to_check: Input string to check if it can be converted to a float. | 118 string_to_check: Input string to check if it can be converted to a float. |
100 | 119 |
101 Returns: | 120 Returns: |
(...skipping 26 matching lines...) Expand all Loading... | |
128 | 147 |
129 def OutputAnnotationStepStart(name): | 148 def OutputAnnotationStepStart(name): |
130 """Outputs appropriate annotation to signal the start of a step to | 149 """Outputs appropriate annotation to signal the start of a step to |
131 a trybot. | 150 a trybot. |
132 | 151 |
133 Args: | 152 Args: |
134 name: The name of the step. | 153 name: The name of the step. |
135 """ | 154 """ |
136 print '@@@SEED_STEP %s@@@' % name | 155 print '@@@SEED_STEP %s@@@' % name |
137 print '@@@STEP_CURSOR %s@@@' % name | 156 print '@@@STEP_CURSOR %s@@@' % name |
138 print '@@@STEP_STARTED@@@' | 157 print '@@@STEP_STARTED@@@' |
Mike Stip (use stip instead)
2013/02/21 02:15:32
check if this shows up OK on the waterfall. if it'
shatch
2013/02/21 16:58:08
Seems to show up ok on my local buildbot.
| |
139 | 158 |
140 | 159 |
141 def OutputAnnotationStepClosed(): | 160 def OutputAnnotationStepClosed(): |
142 """Outputs appropriate annotation to signal the closing of a step to | 161 """Outputs appropriate annotation to signal the closing of a step to |
143 a trybot.""" | 162 a trybot.""" |
144 print '@@@STEP_CLOSED@@@' | 163 print '@@@STEP_CLOSED@@@' |
145 | 164 |
146 | 165 |
147 def RunProcess(command): | 166 def RunProcess(command): |
148 """Run an arbitrary command, returning its output and return code. | 167 """Run an arbitrary command, returning its output and return code. |
(...skipping 978 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1127 """ | 1146 """ |
1128 | 1147 |
1129 (output, return_code) = RunGit(['rev-parse', '--is-inside-work-tree']) | 1148 (output, return_code) = RunGit(['rev-parse', '--is-inside-work-tree']) |
1130 | 1149 |
1131 if output.strip() == 'true': | 1150 if output.strip() == 'true': |
1132 return GitSourceControl() | 1151 return GitSourceControl() |
1133 | 1152 |
1134 return None | 1153 return None |
1135 | 1154 |
1136 | 1155 |
1156 def CreateAndChangeToSourceDirectory(working_directory): | |
1157 """Creates a directory 'bisect' as a subdirectory of 'working_directory'. If | |
1158 the function is successful, the current working directory will change to that | |
1159 of the new 'bisect' directory. | |
1160 | |
1161 Returns: | |
1162 True if the directory was successfully created (or already existed). | |
1163 """ | |
1164 cwd = os.getcwd() | |
1165 os.chdir(working_directory) | |
1166 try: | |
1167 os.mkdir('bisect') | |
1168 except OSError, e: | |
1169 if e.errno != errno.EEXIST: | |
1170 return False | |
1171 os.chdir('bisect') | |
1172 return True | |
1173 | |
1174 | |
1175 def RunGClient(params): | |
1176 """Runs gclient with the specified parameters. | |
1177 | |
1178 Args: | |
1179 params: A list of parameters to pass to gclient. | |
1180 | |
1181 Returns: | |
1182 The return code of the call. | |
1183 """ | |
1184 cmd = ['gclient'] + params | |
1185 return subprocess.call(cmd) | |
1186 | |
1187 | |
1188 def RunGClientAndCreateConfig(): | |
1189 """Runs gclient and creates a config containing both src and src-internal. | |
1190 | |
1191 Returns: | |
1192 The return code of the call. | |
1193 """ | |
1194 return_code = RunGClient( | |
1195 ['config', '--spec=%s' % GCLIENT_SPEC, '--git-deps']) | |
1196 return return_code | |
1197 | |
1198 | |
1199 def RunGClientAndSync(): | |
1200 """Runs gclient and does a normal sync. | |
1201 | |
1202 Returns: | |
1203 The return code of the call. | |
1204 """ | |
1205 return RunGClient(['sync']) | |
1206 | |
1207 | |
1208 def SetupGitDepot(output_buildbot_annotations): | |
1209 """Sets up the depot for the bisection. The depot will be located in a | |
1210 subdirectory called 'bisect'. | |
1211 | |
1212 Returns: | |
1213 True if gclient successfully created the config file and did a sync, False | |
1214 otherwise. | |
1215 """ | |
1216 name = 'Setting up Bisection Depot' | |
1217 | |
1218 if output_buildbot_annotations: | |
Mike Stip (use stip instead)
2013/02/21 02:15:32
probably want to use OutputAnnotationStepStart() h
shatch
2013/02/21 16:58:08
Done.
| |
1219 print '@@@SEED_STEP %s@@@' % name | |
1220 print '@@@STEP_CURSOR %s@@@' % name | |
1221 print '@@@STEP_STARTED@@@' | |
1222 | |
1223 passed = False | |
1224 | |
1225 if not RunGClientAndCreateConfig(): | |
1226 if not RunGClientAndSync(): | |
1227 passed = True | |
1228 | |
1229 if output_buildbot_annotations: | |
1230 print | |
Mike Stip (use stip instead)
2013/02/21 02:15:32
OutputAnnotationStepClosed()
shatch
2013/02/21 16:58:08
Done.
| |
1231 print '@@@STEP_CLOSED@@@' | |
1232 | |
1233 return passed | |
1234 | |
1235 | |
1137 def main(): | 1236 def main(): |
1138 | 1237 |
1139 usage = ('%prog [options] [-- chromium-options]\n' | 1238 usage = ('%prog [options] [-- chromium-options]\n' |
1140 'Perform binary search on revision history to find a minimal ' | 1239 'Perform binary search on revision history to find a minimal ' |
1141 'range of revisions where a peformance metric regressed.\n') | 1240 'range of revisions where a peformance metric regressed.\n') |
1142 | 1241 |
1143 parser = optparse.OptionParser(usage=usage) | 1242 parser = optparse.OptionParser(usage=usage) |
1144 | 1243 |
1145 parser.add_option('-c', '--command', | 1244 parser.add_option('-c', '--command', |
1146 type='str', | 1245 type='str', |
1147 help='A command to execute your performance test at' + | 1246 help='A command to execute your performance test at' + |
1148 ' each point in the bisection.') | 1247 ' each point in the bisection.') |
1149 parser.add_option('-b', '--bad_revision', | 1248 parser.add_option('-b', '--bad_revision', |
1150 type='str', | 1249 type='str', |
1151 help='A bad revision to start bisection. ' + | 1250 help='A bad revision to start bisection. ' + |
1152 'Must be later than good revision. May be either a git' + | 1251 'Must be later than good revision. May be either a git' + |
1153 ' or svn revision.') | 1252 ' or svn revision.') |
1154 parser.add_option('-g', '--good_revision', | 1253 parser.add_option('-g', '--good_revision', |
1155 type='str', | 1254 type='str', |
1156 help='A revision to start bisection where performance' + | 1255 help='A revision to start bisection where performance' + |
1157 ' test is known to pass. Must be earlier than the ' + | 1256 ' test is known to pass. Must be earlier than the ' + |
1158 'bad revision. May be either a git or svn revision.') | 1257 'bad revision. May be either a git or svn revision.') |
1159 parser.add_option('-m', '--metric', | 1258 parser.add_option('-m', '--metric', |
1160 type='str', | 1259 type='str', |
1161 help='The desired metric to bisect on. For example ' + | 1260 help='The desired metric to bisect on. For example ' + |
1162 '"vm_rss_final_b/vm_rss_f_b"') | 1261 '"vm_rss_final_b/vm_rss_f_b"') |
1262 parser.add_option('-w', '--working_directory', | |
1263 type='str', | |
1264 help='Path to the working directory where the script will ' | |
1265 'do an initial checkout of the chromium depot. The ' | |
1266 'files will be placed in a subdirectory "bisect" under ' | |
1267 'working_directory and that will be used to perform the ' | |
1268 'bisection. This parameter is optional, if it is not ' | |
1269 'supplied, the script will work from the current depot.') | |
1163 parser.add_option('--use_goma', | 1270 parser.add_option('--use_goma', |
1164 action="store_true", | 1271 action="store_true", |
1165 help='Add a bunch of extra threads for goma.') | 1272 help='Add a bunch of extra threads for goma.') |
1166 parser.add_option('--output_buildbot_annotations', | 1273 parser.add_option('--output_buildbot_annotations', |
1167 action="store_true", | 1274 action="store_true", |
1168 help='Add extra annotation output for buildbot.') | 1275 help='Add extra annotation output for buildbot.') |
1169 parser.add_option('--debug_ignore_build', | 1276 parser.add_option('--debug_ignore_build', |
1170 action="store_true", | 1277 action="store_true", |
1171 help='DEBUG: Don\'t perform builds.') | 1278 help='DEBUG: Don\'t perform builds.') |
1172 parser.add_option('--debug_ignore_sync', | 1279 parser.add_option('--debug_ignore_sync', |
(...skipping 27 matching lines...) Expand all Loading... | |
1200 print | 1307 print |
1201 parser.print_help() | 1308 parser.print_help() |
1202 return 1 | 1309 return 1 |
1203 | 1310 |
1204 # Haven't tested the script out on any other platforms yet. | 1311 # Haven't tested the script out on any other platforms yet. |
1205 if not os.name in ['posix']: | 1312 if not os.name in ['posix']: |
1206 print "Sorry, this platform isn't supported yet." | 1313 print "Sorry, this platform isn't supported yet." |
1207 print | 1314 print |
1208 return 1 | 1315 return 1 |
1209 | 1316 |
1317 metric_values = opts.metric.split('/') | |
1318 if len(metric_values) != 2: | |
1319 print "Invalid metric specified: [%s]" % (opts.metric,) | |
1320 print | |
1321 return 1 | |
1322 | |
1323 if opts.working_directory: | |
1324 if not CreateAndChangeToSourceDirectory(opts.working_directory): | |
1325 print 'Error: Could not create bisect directory.' | |
1326 print | |
1327 return 1 | |
1328 | |
1329 if not SetupGitDepot(opts.output_buildbot_annotations): | |
1330 print 'Error: Failed to grab source.' | |
1331 print | |
1332 return 1 | |
1333 | |
1334 os.chdir(os.path.join(os.getcwd(), 'src')) | |
1210 | 1335 |
1211 # Check what source control method they're using. Only support git workflow | 1336 # Check what source control method they're using. Only support git workflow |
1212 # at the moment. | 1337 # at the moment. |
1213 source_control = DetermineAndCreateSourceControl() | 1338 source_control = DetermineAndCreateSourceControl() |
1214 | 1339 |
1215 if not source_control: | 1340 if not source_control: |
1216 print "Sorry, only the git workflow is supported at the moment." | 1341 print "Sorry, only the git workflow is supported at the moment." |
1217 print | 1342 print |
1218 return 1 | 1343 return 1 |
1219 | 1344 |
1220 # gClient sync seems to fail if you're not in master branch. | 1345 # gClient sync seems to fail if you're not in master branch. |
1221 if not source_control.IsInProperBranch() and not opts.debug_ignore_sync: | 1346 if not source_control.IsInProperBranch() and not opts.debug_ignore_sync: |
1222 print "You must switch to master branch to run bisection." | 1347 print "You must switch to master branch to run bisection." |
1223 print | 1348 print |
1224 return 1 | 1349 return 1 |
1225 | 1350 |
1226 metric_values = opts.metric.split('/') | |
1227 if len(metric_values) < 2: | |
1228 print "Invalid metric specified: [%s]" % (opts.metric,) | |
1229 print | |
1230 return 1 | |
1231 | |
1232 | |
1233 bisect_test = BisectPerformanceMetrics(source_control, opts) | 1351 bisect_test = BisectPerformanceMetrics(source_control, opts) |
1234 bisect_results = bisect_test.Run(opts.command, | 1352 bisect_results = bisect_test.Run(opts.command, |
1235 opts.bad_revision, | 1353 opts.bad_revision, |
1236 opts.good_revision, | 1354 opts.good_revision, |
1237 metric_values) | 1355 metric_values) |
1238 | 1356 |
1239 if not(bisect_results['error']): | 1357 if not(bisect_results['error']): |
1240 bisect_test.FormatAndPrintResults(bisect_results) | 1358 bisect_test.FormatAndPrintResults(bisect_results) |
1241 return 0 | 1359 return 0 |
1242 else: | 1360 else: |
1243 print 'Error: ' + bisect_results['error'] | 1361 print 'Error: ' + bisect_results['error'] |
1244 print | 1362 print |
1245 return 1 | 1363 return 1 |
1246 | 1364 |
1247 if __name__ == '__main__': | 1365 if __name__ == '__main__': |
1248 sys.exit(main()) | 1366 sys.exit(main()) |
OLD | NEW |