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

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 23 matching lines...) Expand all
34 34
35 DONE_MESSAGE = 'You are probably looking for a change made after ' \ 35 DONE_MESSAGE = 'You are probably looking for a change made after ' \
36 '%s (known good), but no later than %s (first known bad).' 36 '%s (known good), but no later than %s (first known bad).'
37 37
38 ############################################################################### 38 ###############################################################################
39 39
40 import math 40 import math
41 import optparse 41 import optparse
42 import os 42 import os
43 import pipes 43 import pipes
44 import random
44 import re 45 import re
45 import shutil 46 import shutil
46 import subprocess 47 import subprocess
47 import sys 48 import sys
48 import tempfile 49 import tempfile
49 import threading 50 import threading
50 import urllib 51 import urllib
51 from distutils.version import LooseVersion 52 from distutils.version import LooseVersion
52 from xml.etree import ElementTree 53 from xml.etree import ElementTree
53 import zipfile 54 import zipfile
(...skipping 280 matching lines...) Expand 10 before | Expand all | Expand 10 after
334 except Exception, e: 335 except Exception, e:
335 pass 336 pass
336 337
337 return (subproc.returncode, stdout, stderr) 338 return (subproc.returncode, stdout, stderr)
338 339
339 340
340 def AskIsGoodBuild(rev, official_builds, status, stdout, stderr): 341 def AskIsGoodBuild(rev, official_builds, status, stdout, stderr):
341 """Ask the user whether build |rev| is good or bad.""" 342 """Ask the user whether build |rev| is good or bad."""
342 # Loop until we get a response that we can parse. 343 # Loop until we get a response that we can parse.
343 while True: 344 while True:
344 response = raw_input('Revision %s is [(g)ood/(b)ad/(q)uit]: ' % str(rev)) 345 response = raw_input('Revision %s is [(g)ood/(b)ad/(u)nknown/(q)uit]: ' %
345 if response and response in ('g', 'b'): 346 str(rev))
346 return response == 'g' 347 if response and response in ('g', 'b', 'u'):
348 return response
347 if response and response == 'q': 349 if response and response == 'q':
348 raise SystemExit() 350 raise SystemExit()
349 351
350 352
353 class DownloadJob(object):
354 """DownloadJob represents a task to download a given Chromium revision."""
355 def __init__(self, context, name, rev, zipfile):
356 super(DownloadJob, self).__init__()
357 # Store off the input parameters.
358 self.context = context
359 self.name = name
360 self.rev = rev
361 self.zipfile = zipfile
362 self.quit_event = threading.Event()
363 self.progress_event = threading.Event()
364
365 def Start(self):
366 """Starts the download."""
367 fetchargs = (self.context,
368 self.rev,
369 self.zipfile,
370 self.quit_event,
371 self.progress_event)
372 self.thread = threading.Thread(target=FetchRevision,
373 name=self.name,
374 args=fetchargs)
375 self.thread.start()
376
377 def Stop(self):
378 """Stops the download which must have been started previously."""
379 self.quit_event.set()
380 self.thread.join()
381 os.unlink(self.zipfile)
382
383 def WaitFor(self):
384 """Prints a message and waits for the download to complete. The download
385 must have been started previously."""
386 print "Downloading revision %s..." % str(self.rev)
387 self.progress_event.set() # Display progress of download.
388 self.thread.join()
389
390
351 def Bisect(platform, 391 def Bisect(platform,
352 official_builds, 392 official_builds,
353 good_rev=0, 393 good_rev=0,
354 bad_rev=0, 394 bad_rev=0,
355 num_runs=1, 395 num_runs=1,
356 try_args=(), 396 try_args=(),
357 profile=None, 397 profile=None,
358 predicate=AskIsGoodBuild): 398 evaluate=AskIsGoodBuild):
359 """Given known good and known bad revisions, run a binary search on all 399 """Given known good and known bad revisions, run a binary search on all
360 archived revisions to determine the last known good revision. 400 archived revisions to determine the last known good revision.
361 401
362 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.). 402 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.).
363 @param official_builds Specify build type (Chromium or Official build). 403 @param official_builds Specify build type (Chromium or Official build).
364 @param good_rev Number/tag of the last known good revision. 404 @param good_rev Number/tag of the last known good revision.
365 @param bad_rev Number/tag of the first known bad revision. 405 @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. 406 @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. 407 @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. 408 @param profile The name of the user profile to run with.
369 @param predicate A predicate function which returns True iff the argument 409 @param evaluate A function which returns 'g' if the argument Chromium
Robert Sesek 2012/05/31 19:46:10 Since this can now bisect official builds, just sa
Alexei Svitkine (slow) 2012/05/31 20:08:15 Done.
370 chromium revision is good. 410 revision is good, 'b' if it's bad or 'u' if unknown.
371 411
372 Threading is used to fetch Chromium revisions in the background, speeding up 412 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 413 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 414 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 415 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 416 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 417 revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is
378 known: 418 known:
379 419
380 - If rev 50 is good, the download of rev 25 is cancelled, and the next test 420 - 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. 444 if len(revlist) < 2: # Don't have enough builds to bisect.
405 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist 445 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist
406 raise RuntimeError(msg) 446 raise RuntimeError(msg)
407 447
408 # Figure out our bookends and first pivot point; fetch the pivot revision. 448 # Figure out our bookends and first pivot point; fetch the pivot revision.
409 good = 0 449 good = 0
410 bad = len(revlist) - 1 450 bad = len(revlist) - 1
411 pivot = bad / 2 451 pivot = bad / 2
412 rev = revlist[pivot] 452 rev = revlist[pivot]
413 zipfile = _GetDownloadPath(rev) 453 zipfile = _GetDownloadPath(rev)
414 progress_event = threading.Event() 454 base_fetch = DownloadJob(context, 'base_fetch', rev, zipfile)
415 progress_event.set() 455 base_fetch.Start()
416 print "Downloading revision %s..." % str(rev) 456 base_fetch.WaitFor()
417 FetchRevision(context, rev, zipfile,
418 quit_event=None, progress_event=progress_event)
419 457
420 # Binary search time! 458 # Binary search time!
421 while zipfile and bad - good > 1: 459 while zipfile and bad - good > 1:
422 # Pre-fetch next two possible pivots 460 # Pre-fetch next two possible pivots
423 # - down_pivot is the next revision to check if the current revision turns 461 # - down_pivot is the next revision to check if the current revision turns
424 # out to be bad. 462 # out to be bad.
425 # - up_pivot is the next revision to check if the current revision turns 463 # - up_pivot is the next revision to check if the current revision turns
426 # out to be good. 464 # out to be good.
427 down_pivot = int((pivot - good) / 2) + good 465 down_pivot = int((pivot - good) / 2) + good
428 down_thread = None 466 down_fetch = None
429 if down_pivot != pivot and down_pivot != good: 467 if down_pivot != pivot and down_pivot != good:
430 down_rev = revlist[down_pivot] 468 down_rev = revlist[down_pivot]
431 down_zipfile = _GetDownloadPath(down_rev) 469 down_fetch = DownloadJob(context, 'down_fetch', down_rev,
432 down_quit_event = threading.Event() 470 _GetDownloadPath(down_rev))
433 down_progress_event = threading.Event() 471 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 472
444 up_pivot = int((bad - pivot) / 2) + pivot 473 up_pivot = int((bad - pivot) / 2) + pivot
445 up_thread = None 474 up_fetch = None
446 if up_pivot != pivot and up_pivot != bad: 475 if up_pivot != pivot and up_pivot != bad:
447 up_rev = revlist[up_pivot] 476 up_rev = revlist[up_pivot]
448 up_zipfile = _GetDownloadPath(up_rev) 477 up_fetch = DownloadJob(context, 'up_fetch', up_rev,
449 up_quit_event = threading.Event() 478 _GetDownloadPath(up_rev))
450 up_progress_event = threading.Event() 479 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 480
461 # Run test on the pivot revision. 481 # Run test on the pivot revision.
462 (status, stdout, stderr) = RunRevision(context, 482 (status, stdout, stderr) = RunRevision(context,
463 rev, 483 rev,
464 zipfile, 484 zipfile,
465 profile, 485 profile,
466 num_runs, 486 num_runs,
467 try_args) 487 try_args)
468 os.unlink(zipfile) 488 os.unlink(zipfile)
469 zipfile = None 489 zipfile = None
470 490
471 # Call the predicate function to see if the current revision is good or bad. 491 # 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 492 # On that basis, kill one of the background downloads and complete the
473 # other, as described in the comments above. 493 # other, as described in the comments above.
474 try: 494 try:
475 if predicate(rev, official_builds, status, stdout, stderr): 495 answer = evaluate(rev, official_builds, status, stdout, stderr)
496 if answer == 'g':
476 good = pivot 497 good = pivot
477 if down_thread: 498 if down_fetch:
478 down_quit_event.set() # Kill the download of older revision. 499 down_fetch.Stop() # Kill the download of the older revision.
479 down_thread.join() 500 if up_fetch:
480 os.unlink(down_zipfile) 501 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 502 pivot = up_pivot
486 zipfile = up_zipfile 503 zipfile = up_fetch.zipfile
487 else: 504 elif answer == 'b':
488 bad = pivot 505 bad = pivot
489 if up_thread: 506 if up_fetch:
490 up_quit_event.set() # Kill download of newer revision. 507 up_fetch.Stop() # Kill the download of the newer revision.
491 up_thread.join() 508 if down_fetch:
492 os.unlink(up_zipfile) 509 down_fetch.WaitFor()
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 510 pivot = down_pivot
498 zipfile = down_zipfile 511 zipfile = down_fetch.zipfile
512 else: # answer == 'u'
Robert Sesek 2012/05/31 19:46:10 I'd just make this an elseif and then have an else
Alexei Svitkine (slow) 2012/05/31 20:08:15 Done.
513 # Nuke the revision from the revlist and choose a new pivot.
514 revlist.pop(pivot)
515 bad -= 1 # Assumes bad >= pivot.
516
517 fetch = None
518 if bad - good > 1:
519 # Randomly use down_pivot or up_pivot without affecting the range.
520 # Do this instead of setting the pivot to the midpoint of the new
521 # range because adjacent revisions are likely affected by the same
522 # issue that caused the (u)nknown response.
523 if up_fetch and down_fetch:
524 fetch = random.choice([up_fetch, down_fetch])
Robert Sesek 2012/05/31 19:46:10 Any reason to do this instead of [up_fetch, down_f
Alexei Svitkine (slow) 2012/05/31 20:08:15 I like len(revlist), since this will alternate bet
525 elif up_fetch:
526 fetch = up_fetch
527 else:
528 fetch = down_fetch
529 if fetch:
Robert Sesek 2012/05/31 19:46:10 When would |fetch| be None still?
Alexei Svitkine (slow) 2012/05/31 20:08:15 If |down_fetch| is None, which I assumed could hap
Alexei Svitkine (slow) 2012/05/31 20:26:46 So, this condition could happen if there are only
Alexei Svitkine (slow) 2012/05/31 20:32:52 Done.
530 fetch.WaitFor()
531 if fetch == up_fetch:
532 pivot = up_pivot - 1 # Subtracts 1 because revlist was resized.
533 else:
534 pivot = down_pivot
535 zipfile = fetch.zipfile
536
537 if down_fetch and fetch != down_fetch:
538 down_fetch.Stop()
539 if up_fetch and fetch != up_fetch:
540 up_fetch.Stop()
499 except SystemExit: 541 except SystemExit:
500 print "Cleaning up..." 542 print "Cleaning up..."
501 for f in [_GetDownloadPath(revlist[down_pivot]), 543 for f in [_GetDownloadPath(revlist[down_pivot]),
502 _GetDownloadPath(revlist[up_pivot])]: 544 _GetDownloadPath(revlist[up_pivot])]:
503 try: 545 try:
504 os.unlink(f) 546 os.unlink(f)
505 except OSError: 547 except OSError:
506 pass 548 pass
507 sys.exit(0) 549 sys.exit(0)
508 550
(...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after
630 print ' ' + WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev, 672 print ' ' + WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev,
631 last_known_good_webkit_rev) 673 last_known_good_webkit_rev)
632 print 'CHANGELOG URL:' 674 print 'CHANGELOG URL:'
633 if opts.official_builds: 675 if opts.official_builds:
634 print OFFICIAL_CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev) 676 print OFFICIAL_CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)
635 else: 677 else:
636 print ' ' + CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev) 678 print ' ' + CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)
637 679
638 if __name__ == '__main__': 680 if __name__ == '__main__':
639 sys.exit(main()) 681 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