OLD | NEW |
---|---|
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 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 """Snapshot Build Bisect Tool | 6 """Snapshot Build Bisect Tool |
7 | 7 |
8 This script bisects a snapshot archive using binary search. It starts at | 8 This script bisects a snapshot archive using binary search. It starts at |
9 a bad revision (it will try to guess HEAD) and asks for a last known-good | 9 a bad revision (it will try to guess HEAD) and asks for a last known-good |
10 revision. It will then binary search across this revision range by downloading, | 10 revision. It will then binary search across this revision range by downloading, |
(...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
192 out.write(zf.read(name)) | 192 out.write(zf.read(name)) |
193 out.close() | 193 out.close() |
194 # Set permissions. Permission info in external_attr is shifted 16 bits. | 194 # Set permissions. Permission info in external_attr is shifted 16 bits. |
195 os.chmod(name, info.external_attr >> 16L) | 195 os.chmod(name, info.external_attr >> 16L) |
196 os.chdir(cwd) | 196 os.chdir(cwd) |
197 except Exception, e: | 197 except Exception, e: |
198 print >>sys.stderr, e | 198 print >>sys.stderr, e |
199 sys.exit(1) | 199 sys.exit(1) |
200 | 200 |
201 | 201 |
202 def FetchRevision(context, rev, filename, quit_event=None): | 202 def FetchRevision(context, rev, filename, quit_event=None, progress_event=None): |
203 """Downloads and unzips revision |rev|. | 203 """Downloads and unzips revision |rev|. |
204 @param context A PathContext instance. | 204 @param context A PathContext instance. |
205 @param rev The Chromium revision number/tag to download. | 205 @param rev The Chromium revision number/tag to download. |
206 @param filename The destination for the downloaded file. | 206 @param filename The destination for the downloaded file. |
207 @param quit_event A threading.Event which will be set by the master thread to | 207 @param quit_event A threading.Event which will be set by the master thread to |
208 indicate that the download should be aborted. | 208 indicate that the download should be aborted. |
209 @param progress_event A threading.Event which will be set by the master thread | |
210 to indicate that the progress of the download should be | |
211 displayed. | |
209 """ | 212 """ |
210 def ReportHook(blocknum, blocksize, totalsize): | 213 def ReportHook(blocknum, blocksize, totalsize): |
211 if quit_event and quit_event.is_set(): | 214 if quit_event and quit_event.is_set(): |
212 raise RuntimeError("Aborting download of revision %d" % rev) | 215 raise RuntimeError("Aborting download of revision %d" % rev) |
216 if progress_event and progress_event.is_set(): | |
217 size = blocknum * blocksize | |
218 if totalsize == -1: # Total size not known. | |
219 progress = "Received %d bytes" % size | |
220 else: | |
221 size = min(totalsize, size) | |
222 progress = "Received %d of %d bytes, %.2f%%" % ( | |
223 size, totalsize, 100.0 * size / totalsize) | |
224 # Send a \r to let all progress messages use just one line of output. | |
225 sys.stdout.write("\r" + progress) | |
226 sys.stdout.flush() | |
213 | 227 |
214 download_url = context.GetDownloadURL(rev) | 228 download_url = context.GetDownloadURL(rev) |
215 try: | 229 try: |
216 urllib.urlretrieve(download_url, filename, ReportHook) | 230 urllib.urlretrieve(download_url, filename, ReportHook) |
231 if progress_event and progress_event.is_set(): | |
232 print() | |
217 except RuntimeError, e: | 233 except RuntimeError, e: |
218 pass | 234 pass |
219 | 235 |
220 | 236 |
221 def RunRevision(context, revision, zipfile, profile, args): | 237 def RunRevision(context, revision, zipfile, profile, args): |
222 """Given a zipped revision, unzip it and run the test.""" | 238 """Given a zipped revision, unzip it and run the test.""" |
223 print "Trying revision %d..." % revision | 239 print "Trying revision %d..." % revision |
224 | 240 |
225 # Create a temp directory and unzip the revision into it. | 241 # Create a temp directory and unzip the revision into it. |
226 cwd = os.getcwd() | 242 cwd = os.getcwd() |
(...skipping 14 matching lines...) Expand all Loading... | |
241 shutil.rmtree(tempdir, True) | 257 shutil.rmtree(tempdir, True) |
242 except Exception, e: | 258 except Exception, e: |
243 pass | 259 pass |
244 | 260 |
245 return (subproc.returncode, stdout, stderr) | 261 return (subproc.returncode, stdout, stderr) |
246 | 262 |
247 def AskIsGoodBuild(rev, status, stdout, stderr): | 263 def AskIsGoodBuild(rev, status, stdout, stderr): |
248 """Ask the user whether build |rev| is good or bad.""" | 264 """Ask the user whether build |rev| is good or bad.""" |
249 # Loop until we get a response that we can parse. | 265 # Loop until we get a response that we can parse. |
250 while True: | 266 while True: |
251 response = raw_input('\nRevision %d is [(g)ood/(b)ad/(q)uit]: ' % int(rev)) | 267 response = raw_input('Revision %d is [(g)ood/(b)ad/(q)uit]: ' % int(rev)) |
252 if response and response in ('g', 'b'): | 268 if response and response in ('g', 'b'): |
253 return response == 'g' | 269 return response == 'g' |
254 if response and response == 'q': | 270 if response and response == 'q': |
255 raise SystemExit() | 271 raise SystemExit() |
256 | 272 |
257 def Bisect(platform, | 273 def Bisect(platform, |
258 good_rev=0, | 274 good_rev=0, |
259 bad_rev=0, | 275 bad_rev=0, |
260 try_args=(), | 276 try_args=(), |
261 profile=None, | 277 profile=None, |
(...skipping 26 matching lines...) Expand all Loading... | |
288 | 304 |
289 if not profile: | 305 if not profile: |
290 profile = 'profile' | 306 profile = 'profile' |
291 | 307 |
292 context = PathContext(platform, good_rev, bad_rev) | 308 context = PathContext(platform, good_rev, bad_rev) |
293 cwd = os.getcwd() | 309 cwd = os.getcwd() |
294 | 310 |
295 _GetDownloadPath = lambda rev: os.path.join(cwd, | 311 _GetDownloadPath = lambda rev: os.path.join(cwd, |
296 '%d-%s' % (rev, context.archive_name)) | 312 '%d-%s' % (rev, context.archive_name)) |
297 | 313 |
314 print "Downloading list of known revisions..." | |
315 | |
298 revlist = context.GetRevList() | 316 revlist = context.GetRevList() |
299 | 317 |
300 # Get a list of revisions to bisect across. | 318 # Get a list of revisions to bisect across. |
301 if len(revlist) < 2: # Don't have enough builds to bisect. | 319 if len(revlist) < 2: # Don't have enough builds to bisect. |
302 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist | 320 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist |
303 raise RuntimeError(msg) | 321 raise RuntimeError(msg) |
304 | 322 |
305 # Figure out our bookends and first pivot point; fetch the pivot revision. | 323 # Figure out our bookends and first pivot point; fetch the pivot revision. |
306 good = 0 | 324 good = 0 |
307 bad = len(revlist) - 1 | 325 bad = len(revlist) - 1 |
308 pivot = bad / 2 | 326 pivot = bad / 2 |
309 rev = revlist[pivot] | 327 rev = revlist[pivot] |
310 zipfile = _GetDownloadPath(rev) | 328 zipfile = _GetDownloadPath(rev) |
329 progress_event = threading.Event() | |
330 progress_event.set() | |
311 print "Downloading revision %d..." % rev | 331 print "Downloading revision %d..." % rev |
312 FetchRevision(context, rev, zipfile) | 332 FetchRevision(context, rev, zipfile, None, progress_event) |
Nico
2011/08/09 18:34:08
I'd do
FetchRevision(..., quit_event=None, progr
szager1
2011/08/09 18:39:06
Done.
| |
313 | 333 |
314 # Binary search time! | 334 # Binary search time! |
315 while zipfile and bad - good > 1: | 335 while zipfile and bad - good > 1: |
316 # Pre-fetch next two possible pivots | 336 # Pre-fetch next two possible pivots |
317 # - down_pivot is the next revision to check if the current revision turns | 337 # - down_pivot is the next revision to check if the current revision turns |
318 # out to be bad. | 338 # out to be bad. |
319 # - up_pivot is the next revision to check if the current revision turns | 339 # - up_pivot is the next revision to check if the current revision turns |
320 # out to be good. | 340 # out to be good. |
321 down_pivot = int((pivot - good) / 2) + good | 341 down_pivot = int((pivot - good) / 2) + good |
322 down_thread = None | 342 down_thread = None |
323 if down_pivot != pivot and down_pivot != good: | 343 if down_pivot != pivot and down_pivot != good: |
324 down_rev = revlist[down_pivot] | 344 down_rev = revlist[down_pivot] |
325 down_zipfile = _GetDownloadPath(down_rev) | 345 down_zipfile = _GetDownloadPath(down_rev) |
326 down_event = threading.Event() | 346 down_quit_event = threading.Event() |
327 fetchargs = (context, down_rev, down_zipfile, down_event) | 347 down_progress_event = threading.Event() |
348 fetchargs = (context, | |
349 down_rev, | |
350 down_zipfile, | |
351 down_quit_event, | |
352 down_progress_event) | |
328 down_thread = threading.Thread(target=FetchRevision, | 353 down_thread = threading.Thread(target=FetchRevision, |
329 name='down_fetch', | 354 name='down_fetch', |
330 args=fetchargs) | 355 args=fetchargs) |
331 down_thread.start() | 356 down_thread.start() |
332 | 357 |
333 up_pivot = int((bad - pivot) / 2) + pivot | 358 up_pivot = int((bad - pivot) / 2) + pivot |
334 up_thread = None | 359 up_thread = None |
335 if up_pivot != pivot and up_pivot != bad: | 360 if up_pivot != pivot and up_pivot != bad: |
336 up_rev = revlist[up_pivot] | 361 up_rev = revlist[up_pivot] |
337 up_zipfile = _GetDownloadPath(up_rev) | 362 up_zipfile = _GetDownloadPath(up_rev) |
338 up_event = threading.Event() | 363 up_quit_event = threading.Event() |
339 fetchargs = (context, up_rev, up_zipfile, up_event) | 364 up_progress_event = threading.Event() |
365 fetchargs = (context, | |
366 up_rev, | |
367 up_zipfile, | |
368 up_quit_event, | |
369 up_progress_event) | |
340 up_thread = threading.Thread(target=FetchRevision, | 370 up_thread = threading.Thread(target=FetchRevision, |
341 name='up_fetch', | 371 name='up_fetch', |
342 args=fetchargs) | 372 args=fetchargs) |
343 up_thread.start() | 373 up_thread.start() |
344 | 374 |
345 # Run test on the pivot revision. | 375 # Run test on the pivot revision. |
346 (status, stdout, stderr) = RunRevision(context, | 376 (status, stdout, stderr) = RunRevision(context, |
347 rev, | 377 rev, |
348 zipfile, | 378 zipfile, |
349 profile, | 379 profile, |
350 try_args) | 380 try_args) |
351 os.unlink(zipfile) | 381 os.unlink(zipfile) |
352 zipfile = None | 382 zipfile = None |
353 | 383 |
354 # Call the predicate function to see if the current revision is good or bad. | 384 # Call the predicate function to see if the current revision is good or bad. |
355 # On that basis, kill one of the background downloads and complete the | 385 # On that basis, kill one of the background downloads and complete the |
356 # other, as described in the comments above. | 386 # other, as described in the comments above. |
357 try: | 387 try: |
358 if predicate(rev, status, stdout, stderr): | 388 if predicate(rev, status, stdout, stderr): |
359 good = pivot | 389 good = pivot |
360 if down_thread: | 390 if down_thread: |
361 down_event.set() # Kill the download of older revision. | 391 down_quit_event.set() # Kill the download of older revision. |
362 down_thread.join() | 392 down_thread.join() |
363 os.unlink(down_zipfile) | 393 os.unlink(down_zipfile) |
364 if up_thread: | 394 if up_thread: |
365 print "Downloading revision %d..." % up_rev | 395 print "Downloading revision %d..." % up_rev |
396 up_progress_event.set() # Display progress of download. | |
366 up_thread.join() # Wait for newer revision to finish downloading. | 397 up_thread.join() # Wait for newer revision to finish downloading. |
367 pivot = up_pivot | 398 pivot = up_pivot |
368 zipfile = up_zipfile | 399 zipfile = up_zipfile |
369 else: | 400 else: |
370 bad = pivot | 401 bad = pivot |
371 if up_thread: | 402 if up_thread: |
372 up_event.set() # Kill download of newer revision. | 403 up_quit_event.set() # Kill download of newer revision. |
373 up_thread.join() | 404 up_thread.join() |
374 os.unlink(up_zipfile) | 405 os.unlink(up_zipfile) |
375 if down_thread: | 406 if down_thread: |
376 print "Downloading revision %d..." % down_rev | 407 print "Downloading revision %d..." % down_rev |
408 down_progress_event.set() # Display progress of download. | |
377 down_thread.join() # Wait for older revision to finish downloading. | 409 down_thread.join() # Wait for older revision to finish downloading. |
378 pivot = down_pivot | 410 pivot = down_pivot |
379 zipfile = down_zipfile | 411 zipfile = down_zipfile |
380 except SystemExit: | 412 except SystemExit: |
413 print "Cleaning up..." | |
381 for f in [down_zipfile, up_zipfile]: | 414 for f in [down_zipfile, up_zipfile]: |
382 try: | 415 try: |
383 os.unlink(f) | 416 os.unlink(f) |
384 except OSError: | 417 except OSError: |
385 pass | 418 pass |
386 sys.exit(0) | 419 sys.exit(0) |
387 | 420 |
388 rev = revlist[pivot] | 421 rev = revlist[pivot] |
389 | 422 |
390 return (revlist[good], revlist[bad]) | 423 return (revlist[good], revlist[bad]) |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
460 | 493 |
461 # We're done. Let the user know the results in an official manner. | 494 # We're done. Let the user know the results in an official manner. |
462 print('You are probably looking for build %d.' % first_known_bad_rev) | 495 print('You are probably looking for build %d.' % first_known_bad_rev) |
463 print('CHANGELOG URL:') | 496 print('CHANGELOG URL:') |
464 print(CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)) | 497 print(CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)) |
465 print('Built at revision:') | 498 print('Built at revision:') |
466 print(BUILD_VIEWVC_URL % first_known_bad_rev) | 499 print(BUILD_VIEWVC_URL % first_known_bad_rev) |
467 | 500 |
468 if __name__ == '__main__': | 501 if __name__ == '__main__': |
469 sys.exit(main()) | 502 sys.exit(main()) |
OLD | NEW |