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

Side by Side Diff: tools/bisect-perf-regression.py

Issue 16023021: Try to expand the revision range to include a change to .DEPS.git if the script detects a change to… (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 7 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
« 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) 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 476 matching lines...) Expand 10 before | Expand all | Expand 10 after
487 """Unstages a file and returns it to HEAD. 487 """Unstages a file and returns it to HEAD.
488 488
489 Returns: 489 Returns:
490 True if successful. 490 True if successful.
491 """ 491 """
492 # Reset doesn't seem to return 0 on success. 492 # Reset doesn't seem to return 0 on success.
493 RunGit(['reset', 'HEAD', bisect_utils.FILE_DEPS_GIT]) 493 RunGit(['reset', 'HEAD', bisect_utils.FILE_DEPS_GIT])
494 494
495 return not RunGit(['checkout', bisect_utils.FILE_DEPS_GIT])[1] 495 return not RunGit(['checkout', bisect_utils.FILE_DEPS_GIT])[1]
496 496
497 def QueryFileRevisionHistory(self, filename, revision_start, revision_end):
498 """Returns a list of commits that modified this file.
499
500 Args:
501 filename: Name of file.
502 revision_start: Start of revision range.
503 revision_end: End of revision range.
504
505 Returns:
506 Returns a list of commits that touched this file.
507 """
508 cmd = ['log', '--format=%H', '%s^1..%s' % (revision_start, revision_end),
509 filename]
510 (output, return_code) = RunGit(cmd)
511
512 assert not return_code, 'An error occurred while running'\
tonyg 2013/05/28 23:16:07 We already do this assert not return_code dance in
shatch 2013/05/28 23:38:17 Done.
513 ' "git %s"' % ' '.join(cmd)
514
515 return [o for o in output.split('\n') if o]
516
497 class BisectPerformanceMetrics(object): 517 class BisectPerformanceMetrics(object):
498 """BisectPerformanceMetrics performs a bisection against a list of range 518 """BisectPerformanceMetrics performs a bisection against a list of range
499 of revisions to narrow down where performance regressions may have 519 of revisions to narrow down where performance regressions may have
500 occurred.""" 520 occurred."""
501 521
502 def __init__(self, source_control, opts): 522 def __init__(self, source_control, opts):
503 super(BisectPerformanceMetrics, self).__init__() 523 super(BisectPerformanceMetrics, self).__init__()
504 524
505 self.opts = opts 525 self.opts = opts
506 self.source_control = source_control 526 self.source_control = source_control
507 self.src_cwd = os.getcwd() 527 self.src_cwd = os.getcwd()
508 self.depot_cwd = {} 528 self.depot_cwd = {}
509 self.cleanup_commands = [] 529 self.cleanup_commands = []
530 self.warnings = []
510 531
511 # This always starts true since the script grabs latest first. 532 # This always starts true since the script grabs latest first.
512 self.was_blink = True 533 self.was_blink = True
513 534
514 for d in DEPOT_NAMES: 535 for d in DEPOT_NAMES:
515 # The working directory of each depot is just the path to the depot, but 536 # The working directory of each depot is just the path to the depot, but
516 # since we're already in 'src', we can skip that part. 537 # since we're already in 'src', we can skip that part.
517 538
518 self.depot_cwd[d] = self.src_cwd + DEPOT_DEPS_NAME[d]['src'][3:] 539 self.depot_cwd[d] = self.src_cwd + DEPOT_DEPS_NAME[d]['src'][3:]
519 540
(...skipping 517 matching lines...) Expand 10 before | Expand all | Expand 10 after
1037 1058
1038 print 1059 print
1039 print 'Revisions to bisect on [%s]:' % depot 1060 print 'Revisions to bisect on [%s]:' % depot
1040 for revision_id in revision_list: 1061 for revision_id in revision_list:
1041 print ' -> %s' % (revision_id, ) 1062 print ' -> %s' % (revision_id, )
1042 print 1063 print
1043 1064
1044 if self.opts.output_buildbot_annotations: 1065 if self.opts.output_buildbot_annotations:
1045 bisect_utils.OutputAnnotationStepClosed() 1066 bisect_utils.OutputAnnotationStepClosed()
1046 1067
1068 def NudgeRevisionsIfDEPSChange(self, bad_revision, good_revision):
1069 """Checks to see if changes to DEPS file occurred, and that the revision
1070 range also includes the change to .DEPS.git. If it doesn't, attempts to
1071 expand the revision range to include it.
1072
1073 Args:
1074 good_revision: Last known good revision.
1075 bad_rev: First known bad revision.
tonyg 2013/05/28 23:16:07 Nit: args are in the wrong order here
shatch 2013/05/28 23:38:17 Done.
1076
1077 Returns:
1078 A tuple with the new good and bad revisions.
tonyg 2013/05/28 23:16:07 A tuple with the new bad and good revisions
shatch 2013/05/28 23:38:17 Done.
1079 """
1080 if self.source_control.IsGit():
1081 changes_to_deps = self.source_control.QueryFileRevisionHistory(
1082 'DEPS', good_revision, bad_revision)
1083
1084 if changes_to_deps:
1085 # DEPS file was changed, search from the oldest change to DEPS file to
1086 # bad_revision to see if there are matching .DEPS.git changes.
1087 oldest_deps_change = changes_to_deps[len(changes_to_deps) - 1]
tonyg 2013/05/28 23:16:07 Neat python trick: oldest_deps_change = changes_to
shatch 2013/05/28 23:38:17 Cool!
1088 changes_to_gitdeps = self.source_control.QueryFileRevisionHistory(
1089 bisect_utils.FILE_DEPS_GIT, oldest_deps_change, bad_revision)
1090
1091 if len(changes_to_deps) != len(changes_to_gitdeps):
1092 # Grab the timestamp of the last DEPS change
1093 cmd = ['log', '--format=%ct', '-1', changes_to_deps[0]]
1094 (output, return_code) = RunGit(cmd)
1095
1096 assert not return_code, 'An error occurred while running'\
1097 ' "git %s"' % ' '.join(cmd)
1098
1099 commit_time = int(output)
1100
1101 # Try looking for a commit that touches the .DEPS.git file in the
1102 # next 5 minutes after the DEPS file change.
tonyg 2013/05/28 23:16:07 I didn't look how long it usually takes, but 5 min
shatch 2013/05/28 23:38:17 Done.
1103 cmd = ['log', '--format=%H', '-1',
1104 '--before=%d' % (commit_time + 300), '--after=%d' % commit_time,
1105 'origin/master', bisect_utils.FILE_DEPS_GIT]
1106 (output, return_code) = RunGit(cmd)
1107
1108 assert not return_code, 'An error occurred while running'\
1109 ' "git %s"' % ' '.join(cmd)
1110
1111 output = output.strip()
1112 if output:
1113 self.warnings.append('Detected change to DEPS and modified '
1114 'revision range to include change to .DEPS.git')
1115 return (output, good_revision)
1116 else:
1117 self.warnings.append('Detected change to DEPS but couldn\'t find '
1118 'matching change to .DEPS.git')
1119 return (bad_revision, good_revision)
1120
1047 def Run(self, command_to_run, bad_revision_in, good_revision_in, metric): 1121 def Run(self, command_to_run, bad_revision_in, good_revision_in, metric):
1048 """Given known good and bad revisions, run a binary search on all 1122 """Given known good and bad revisions, run a binary search on all
1049 intermediate revisions to determine the CL where the performance regression 1123 intermediate revisions to determine the CL where the performance regression
1050 occurred. 1124 occurred.
1051 1125
1052 Args: 1126 Args:
1053 command_to_run: Specify the command to execute the performance test. 1127 command_to_run: Specify the command to execute the performance test.
1054 good_revision: Number/tag of the known good revision. 1128 good_revision: Number/tag of the known good revision.
1055 bad_revision: Number/tag of the known bad revision. 1129 bad_revision: Number/tag of the known bad revision.
1056 metric: The performance metric to monitor. 1130 metric: The performance metric to monitor.
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
1098 'src', -100) 1172 'src', -100)
1099 1173
1100 if bad_revision is None: 1174 if bad_revision is None:
1101 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (bad_revision_in,) 1175 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (bad_revision_in,)
1102 return results 1176 return results
1103 1177
1104 if good_revision is None: 1178 if good_revision is None:
1105 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (good_revision_in,) 1179 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (good_revision_in,)
1106 return results 1180 return results
1107 1181
1182 (bad_revision, good_revision) = self.NudgeRevisionsIfDEPSChange(
1183 bad_revision, good_revision)
1184
1108 if self.opts.output_buildbot_annotations: 1185 if self.opts.output_buildbot_annotations:
1109 bisect_utils.OutputAnnotationStepStart('Gathering Revisions') 1186 bisect_utils.OutputAnnotationStepStart('Gathering Revisions')
1110 1187
1111 print 'Gathering revision range for bisection.' 1188 print 'Gathering revision range for bisection.'
1112 1189
1113 # Retrieve a list of revisions to do bisection on. 1190 # Retrieve a list of revisions to do bisection on.
1114 src_revision_list = self.GetRevisionList(bad_revision, good_revision) 1191 src_revision_list = self.GetRevisionList(bad_revision, good_revision)
1115 1192
1116 if self.opts.output_buildbot_annotations: 1193 if self.opts.output_buildbot_annotations:
1117 bisect_utils.OutputAnnotationStepClosed() 1194 bisect_utils.OutputAnnotationStepClosed()
(...skipping 255 matching lines...) Expand 10 before | Expand all | Expand 10 after
1373 good_std_dev = revision_data[first_working_revision]['value']['std_dev'] 1450 good_std_dev = revision_data[first_working_revision]['value']['std_dev']
1374 good_mean = revision_data[first_working_revision]['value']['mean'] 1451 good_mean = revision_data[first_working_revision]['value']['mean']
1375 bad_mean = revision_data[last_broken_revision]['value']['mean'] 1452 bad_mean = revision_data[last_broken_revision]['value']['mean']
1376 1453
1377 # A standard deviation of 0 could indicate either insufficient runs 1454 # A standard deviation of 0 could indicate either insufficient runs
1378 # or a test that consistently returns the same value. 1455 # or a test that consistently returns the same value.
1379 if good_std_dev > 0: 1456 if good_std_dev > 0:
1380 deviations = math.fabs(bad_mean - good_mean) / good_std_dev 1457 deviations = math.fabs(bad_mean - good_mean) / good_std_dev
1381 1458
1382 if deviations < 1.5: 1459 if deviations < 1.5:
1383 print 'Warning: Regression was less than 1.5 standard deviations '\ 1460 self.warnings.append('Regression was less than 1.5 standard '
1384 'from "good" value. Results may not be accurate.' 1461 'deviations from "good" value. Results may not be accurate.')
tonyg 2013/05/28 23:16:07 Something for a future CL that I'll mention while
shatch 2013/05/28 23:38:17 Yeah that sounds reasonable, I'll get a version wi
1385 print
1386 elif self.opts.repeat_test_count == 1: 1462 elif self.opts.repeat_test_count == 1:
1387 print 'Warning: Tests were only set to run once. This may be '\ 1463 self.warnings.append('Tests were only set to run once. This '
1388 'insufficient to get meaningful results.' 1464 'may be insufficient to get meaningful results.')
1389 print
1390 1465
1391 # Check for any other possible regression ranges 1466 # Check for any other possible regression ranges
1392 prev_revision_data = revision_data_sorted[0][1] 1467 prev_revision_data = revision_data_sorted[0][1]
1393 prev_revision_id = revision_data_sorted[0][0] 1468 prev_revision_id = revision_data_sorted[0][0]
1394 possible_regressions = [] 1469 possible_regressions = []
1395 for current_id, current_data in revision_data_sorted: 1470 for current_id, current_data in revision_data_sorted:
1396 if current_data['value']: 1471 if current_data['value']:
1397 prev_mean = prev_revision_data['value']['mean'] 1472 prev_mean = prev_revision_data['value']['mean']
1398 cur_mean = current_data['value']['mean'] 1473 cur_mean = current_data['value']['mean']
1399 1474
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
1440 if percent_change is None: 1515 if percent_change is None:
1441 percent_change = 0 1516 percent_change = 0
1442 1517
1443 print ' %8s %s [%.2f%%, %s x std.dev]' % ( 1518 print ' %8s %s [%.2f%%, %s x std.dev]' % (
1444 previous_data['depot'], previous_id, 100 * percent_change, 1519 previous_data['depot'], previous_id, 100 * percent_change,
1445 deviations) 1520 deviations)
1446 print ' %8s %s' % ( 1521 print ' %8s %s' % (
1447 current_data['depot'], current_id) 1522 current_data['depot'], current_id)
1448 print 1523 print
1449 1524
1525 if self.warnings:
1526 print
1527 print 'The following warnings were generated:'
1528 print
1529 for w in self.warnings:
1530 print ' - %s' % w
1531 print
1532
1450 if self.opts.output_buildbot_annotations: 1533 if self.opts.output_buildbot_annotations:
1451 bisect_utils.OutputAnnotationStepClosed() 1534 bisect_utils.OutputAnnotationStepClosed()
1452 1535
1453 1536
1454 def DetermineAndCreateSourceControl(): 1537 def DetermineAndCreateSourceControl():
1455 """Attempts to determine the underlying source control workflow and returns 1538 """Attempts to determine the underlying source control workflow and returns
1456 a SourceControl object. 1539 a SourceControl object.
1457 1540
1458 Returns: 1541 Returns:
1459 An instance of a SourceControl object, or None if the current workflow 1542 An instance of a SourceControl object, or None if the current workflow
(...skipping 269 matching lines...) Expand 10 before | Expand all | Expand 10 after
1729 1812
1730 if not(bisect_results['error']): 1813 if not(bisect_results['error']):
1731 return 0 1814 return 0
1732 else: 1815 else:
1733 print 'Error: ' + bisect_results['error'] 1816 print 'Error: ' + bisect_results['error']
1734 print 1817 print
1735 return 1 1818 return 1
1736 1819
1737 if __name__ == '__main__': 1820 if __name__ == '__main__':
1738 sys.exit(main()) 1821 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