Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(137)

Side by Side Diff: tools/bisect-builds.py

Issue 10459050: Add option (u)nknown to bisect-builds.py. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 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 323 matching lines...) Expand 10 before | Expand all | Expand 10 after
334 except Exception, e: 334 except Exception, e:
335 pass 335 pass
336 336
337 return (subproc.returncode, stdout, stderr) 337 return (subproc.returncode, stdout, stderr)
338 338
339 339
340 def AskIsGoodBuild(rev, official_builds, status, stdout, stderr): 340 def AskIsGoodBuild(rev, official_builds, status, stdout, stderr):
341 """Ask the user whether build |rev| is good or bad.""" 341 """Ask the user whether build |rev| is good or bad."""
342 # Loop until we get a response that we can parse. 342 # Loop until we get a response that we can parse.
343 while True: 343 while True:
344 response = raw_input('Revision %s is [(g)ood/(b)ad/(q)uit]: ' % str(rev)) 344 response = raw_input('Revision %s is [(g)ood/(b)ad/(u)nknown/(q)uit]: ' %
345 if response and response in ('g', 'b'): 345 str(rev))
346 return response == 'g' 346 if response and response in ('g', 'b', 'u'):
347 return response
347 if response and response == 'q': 348 if response and response == 'q':
348 raise SystemExit() 349 raise SystemExit()
349 350
350 351
352 class DownloadJob(object):
353 """DownloadJob represents a task to download a given Chromium revision."""
354 def __init__(self, context, name, rev, zipfile):
355 super(DownloadJob, self).__init__()
356 # Store off the input parameters.
357 self.context = context
358 self.name = name
359 self.rev = rev
360 self.zipfile = zipfile
361 self.quit_event = threading.Event()
362 self.progress_event = threading.Event()
363
364 def Start(self):
365 """Starts the download."""
366 fetchargs = (self.context,
367 self.rev,
368 self.zipfile,
369 self.quit_event,
370 self.progress_event)
371 self.thread = threading.Thread(target=FetchRevision,
372 name=self.name,
373 args=fetchargs)
374 self.thread.start()
375
376 def Stop(self):
377 """Stops the download which must have been started previously."""
378 self.quit_event.set()
379 self.thread.join()
380 os.unlink(self.zipfile)
381
382 def WaitFor(self):
383 """Prints a message and waits for the download to complete. The download
384 must have been started previously."""
385 print "Downloading revision %s..." % str(self.rev)
386 self.progress_event.set() # Display progress of download.
387 self.thread.join()
388
389
351 def Bisect(platform, 390 def Bisect(platform,
352 official_builds, 391 official_builds,
353 good_rev=0, 392 good_rev=0,
354 bad_rev=0, 393 bad_rev=0,
355 num_runs=1, 394 num_runs=1,
356 try_args=(), 395 try_args=(),
357 profile=None, 396 profile=None,
358 predicate=AskIsGoodBuild): 397 evaluate=AskIsGoodBuild):
359 """Given known good and known bad revisions, run a binary search on all 398 """Given known good and known bad revisions, run a binary search on all
360 archived revisions to determine the last known good revision. 399 archived revisions to determine the last known good revision.
361 400
362 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.). 401 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.).
363 @param official_builds Specify build type (Chromium or Official build). 402 @param official_builds Specify build type (Chromium or Official build).
364 @param good_rev Number/tag of the last known good revision. 403 @param good_rev Number/tag of the last known good revision.
365 @param bad_rev Number/tag of the first known bad revision. 404 @param bad_rev Number/tag of the first known bad revision.
366 @param num_runs Number of times to run each build for asking good/bad. 405 @param num_runs Number of times to run each build for asking good/bad.
367 @param try_args A tuple of arguments to pass to the test application. 406 @param try_args A tuple of arguments to pass to the test application.
368 @param profile The name of the user profile to run with. 407 @param profile The name of the user profile to run with.
369 @param predicate A predicate function which returns True iff the argument 408 @param evaluate A function which returns 'g' if the argument build is good,
370 chromium revision is good. 409 'b' if it's bad or 'u' if unknown.
371 410
372 Threading is used to fetch Chromium revisions in the background, speeding up 411 Threading is used to fetch Chromium revisions in the background, speeding up
373 the user's experience. For example, suppose the bounds of the search are 412 the user's experience. For example, suppose the bounds of the search are
374 good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on 413 good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on
375 whether revision 50 is good or bad, the next revision to check will be either 414 whether revision 50 is good or bad, the next revision to check will be either
376 25 or 75. So, while revision 50 is being checked, the script will download 415 25 or 75. So, while revision 50 is being checked, the script will download
377 revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is 416 revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is
378 known: 417 known:
379 418
380 - If rev 50 is good, the download of rev 25 is cancelled, and the next test 419 - If rev 50 is good, the download of rev 25 is cancelled, and the next test
(...skipping 23 matching lines...) Expand all
404 if len(revlist) < 2: # Don't have enough builds to bisect. 443 if len(revlist) < 2: # Don't have enough builds to bisect.
405 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist 444 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist
406 raise RuntimeError(msg) 445 raise RuntimeError(msg)
407 446
408 # Figure out our bookends and first pivot point; fetch the pivot revision. 447 # Figure out our bookends and first pivot point; fetch the pivot revision.
409 good = 0 448 good = 0
410 bad = len(revlist) - 1 449 bad = len(revlist) - 1
411 pivot = bad / 2 450 pivot = bad / 2
412 rev = revlist[pivot] 451 rev = revlist[pivot]
413 zipfile = _GetDownloadPath(rev) 452 zipfile = _GetDownloadPath(rev)
414 progress_event = threading.Event() 453 initial_fetch = DownloadJob(context, 'initial_fetch', rev, zipfile)
415 progress_event.set() 454 initial_fetch.Start()
416 print "Downloading revision %s..." % str(rev) 455 initial_fetch.WaitFor()
417 FetchRevision(context, rev, zipfile,
418 quit_event=None, progress_event=progress_event)
419 456
420 # Binary search time! 457 # Binary search time!
421 while zipfile and bad - good > 1: 458 while zipfile and bad - good > 1:
422 # Pre-fetch next two possible pivots 459 # Pre-fetch next two possible pivots
423 # - down_pivot is the next revision to check if the current revision turns 460 # - down_pivot is the next revision to check if the current revision turns
424 # out to be bad. 461 # out to be bad.
425 # - up_pivot is the next revision to check if the current revision turns 462 # - up_pivot is the next revision to check if the current revision turns
426 # out to be good. 463 # out to be good.
427 down_pivot = int((pivot - good) / 2) + good 464 down_pivot = int((pivot - good) / 2) + good
428 down_thread = None 465 down_fetch = None
429 if down_pivot != pivot and down_pivot != good: 466 if down_pivot != pivot and down_pivot != good:
430 down_rev = revlist[down_pivot] 467 down_rev = revlist[down_pivot]
431 down_zipfile = _GetDownloadPath(down_rev) 468 down_fetch = DownloadJob(context, 'down_fetch', down_rev,
432 down_quit_event = threading.Event() 469 _GetDownloadPath(down_rev))
433 down_progress_event = threading.Event() 470 down_fetch.Start()
434 fetchargs = (context,
435 down_rev,
436 down_zipfile,
437 down_quit_event,
438 down_progress_event)
439 down_thread = threading.Thread(target=FetchRevision,
440 name='down_fetch',
441 args=fetchargs)
442 down_thread.start()
443 471
444 up_pivot = int((bad - pivot) / 2) + pivot 472 up_pivot = int((bad - pivot) / 2) + pivot
445 up_thread = None 473 up_fetch = None
446 if up_pivot != pivot and up_pivot != bad: 474 if up_pivot != pivot and up_pivot != bad:
447 up_rev = revlist[up_pivot] 475 up_rev = revlist[up_pivot]
448 up_zipfile = _GetDownloadPath(up_rev) 476 up_fetch = DownloadJob(context, 'up_fetch', up_rev,
449 up_quit_event = threading.Event() 477 _GetDownloadPath(up_rev))
450 up_progress_event = threading.Event() 478 up_fetch.Start()
451 fetchargs = (context,
452 up_rev,
453 up_zipfile,
454 up_quit_event,
455 up_progress_event)
456 up_thread = threading.Thread(target=FetchRevision,
457 name='up_fetch',
458 args=fetchargs)
459 up_thread.start()
460 479
461 # Run test on the pivot revision. 480 # Run test on the pivot revision.
462 (status, stdout, stderr) = RunRevision(context, 481 (status, stdout, stderr) = RunRevision(context,
463 rev, 482 rev,
464 zipfile, 483 zipfile,
465 profile, 484 profile,
466 num_runs, 485 num_runs,
467 try_args) 486 try_args)
468 os.unlink(zipfile) 487 os.unlink(zipfile)
469 zipfile = None 488 zipfile = None
470 489
471 # Call the predicate function to see if the current revision is good or bad. 490 # Call the evaluate function to see if the current revision is good or bad.
472 # On that basis, kill one of the background downloads and complete the 491 # On that basis, kill one of the background downloads and complete the
473 # other, as described in the comments above. 492 # other, as described in the comments above.
474 try: 493 try:
475 if predicate(rev, official_builds, status, stdout, stderr): 494 answer = evaluate(rev, official_builds, status, stdout, stderr)
495 if answer == 'g':
476 good = pivot 496 good = pivot
477 if down_thread: 497 if down_fetch:
478 down_quit_event.set() # Kill the download of older revision. 498 down_fetch.Stop() # Kill the download of the older revision.
479 down_thread.join() 499 if up_fetch:
480 os.unlink(down_zipfile) 500 up_fetch.WaitFor()
481 if up_thread:
482 print "Downloading revision %s..." % str(up_rev)
483 up_progress_event.set() # Display progress of download.
484 up_thread.join() # Wait for newer revision to finish downloading.
485 pivot = up_pivot 501 pivot = up_pivot
486 zipfile = up_zipfile 502 zipfile = up_fetch.zipfile
503 elif answer == 'b':
504 bad = pivot
505 if up_fetch:
506 up_fetch.Stop() # Kill the download of the newer revision.
507 if down_fetch:
508 down_fetch.WaitFor()
509 pivot = down_pivot
510 zipfile = down_fetch.zipfile
511 elif answer == 'u':
512 # Nuke the revision from the revlist and choose a new pivot.
513 revlist.pop(pivot)
514 bad -= 1 # Assumes bad >= pivot.
515
516 fetch = None
517 if bad - good > 1:
518 # Alternate between using down_pivot or up_pivot for the new pivot
519 # point, without affecting the range. Do this instead of setting the
520 # pivot to the midpoint of the new range because adjacent revisions
521 # are likely affected by the same issue that caused the (u)nknown
522 # response.
523 if up_fetch and down_fetch:
524 fetch = [up_fetch, down_fetch][len(revlist) % 2]
525 elif up_fetch:
526 fetch = up_fetch
527 else:
528 fetch = down_fetch
529 fetch.WaitFor()
530 if fetch == up_fetch:
531 pivot = up_pivot - 1 # Subtracts 1 because revlist was resized.
532 else:
533 pivot = down_pivot
534 zipfile = fetch.zipfile
535
536 if down_fetch and fetch != down_fetch:
537 down_fetch.Stop()
538 if up_fetch and fetch != up_fetch:
539 up_fetch.Stop()
487 else: 540 else:
488 bad = pivot 541 assert False, "Unexpected return value from evaluate(): " + answer
489 if up_thread:
490 up_quit_event.set() # Kill download of newer revision.
491 up_thread.join()
492 os.unlink(up_zipfile)
493 if down_thread:
494 print "Downloading revision %s..." % str(down_rev)
495 down_progress_event.set() # Display progress of download.
496 down_thread.join() # Wait for older revision to finish downloading.
497 pivot = down_pivot
498 zipfile = down_zipfile
499 except SystemExit: 542 except SystemExit:
500 print "Cleaning up..." 543 print "Cleaning up..."
501 for f in [_GetDownloadPath(revlist[down_pivot]), 544 for f in [_GetDownloadPath(revlist[down_pivot]),
502 _GetDownloadPath(revlist[up_pivot])]: 545 _GetDownloadPath(revlist[up_pivot])]:
503 try: 546 try:
504 os.unlink(f) 547 os.unlink(f)
505 except OSError: 548 except OSError:
506 pass 549 pass
507 sys.exit(0) 550 sys.exit(0)
508 551
(...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after
630 print ' ' + WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev, 673 print ' ' + WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev,
631 last_known_good_webkit_rev) 674 last_known_good_webkit_rev)
632 print 'CHANGELOG URL:' 675 print 'CHANGELOG URL:'
633 if opts.official_builds: 676 if opts.official_builds:
634 print OFFICIAL_CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev) 677 print OFFICIAL_CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)
635 else: 678 else:
636 print ' ' + CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev) 679 print ' ' + CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)
637 680
638 if __name__ == '__main__': 681 if __name__ == '__main__':
639 sys.exit(main()) 682 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698