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

Side by Side Diff: scripts/slave/recipes/findit/chromium/compile.py

Issue 1869223002: [Findit] Use results from heuristic analysis to do faster culprit finding. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/build.git@setup_local_test
Patch Set: Created 4 years, 8 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 | scripts/slave/recipes/findit/chromium/compile.expected/compile_affected_targets_only.json » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2015 The Chromium Authors. All rights reserved. 1 # Copyright 2015 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import json 5 import json
6 6
7 from recipe_engine.config import List 7 from recipe_engine.config import List
8 from recipe_engine.config import Single 8 from recipe_engine.config import Single
9 from recipe_engine.recipe_api import Property 9 from recipe_engine.recipe_api import Property
10 10
11 11
12 DEPS = [ 12 DEPS = [
13 'chromium', 13 'chromium',
14 'chromium_tests', 14 'chromium_tests',
15 'findit', 15 'findit',
16 'depot_tools/gclient', 16 'depot_tools/gclient',
17 'recipe_engine/json', 17 'recipe_engine/json',
18 'recipe_engine/path', 18 'recipe_engine/path',
19 'recipe_engine/platform', 19 'recipe_engine/platform',
20 'recipe_engine/properties', 20 'recipe_engine/properties',
21 'recipe_engine/python', 21 'recipe_engine/python',
22 'recipe_engine/raw_io',
22 'recipe_engine/step', 23 'recipe_engine/step',
23 ] 24 ]
24 25
25 26
26 PROPERTIES = { 27 PROPERTIES = {
27 'target_mastername': Property( 28 'target_mastername': Property(
28 kind=str, help='The target master to match compile config to.'), 29 kind=str, help='The target master to match compile config to.'),
29 'target_buildername': Property( 30 'target_buildername': Property(
30 kind=str, help='The target builder to match compile config to.'), 31 kind=str, help='The target builder to match compile config to.'),
31 'good_revision': Property( 32 'good_revision': Property(
32 kind=str, help='The last known good chromium revision.'), 33 kind=str, help='The last known good chromium revision.'),
33 'bad_revision': Property( 34 'bad_revision': Property(
34 kind=str, help='The first known bad chromium revision.'), 35 kind=str, help='The first known bad chromium revision.'),
35 'compile_targets': Property( 36 'compile_targets': Property(
36 kind=List(basestring), default=None, 37 kind=List(basestring), default=None,
37 help='The failed compile targets, eg: browser_tests, ' 38 help='The failed compile targets, eg: browser_tests, '
38 'obj/path/to/source.o, gen/path/to/generated.cc, etc.'), 39 'obj/path/to/source.o, gen/path/to/generated.cc, etc.'),
39 'use_analyze': Property( 40 'use_analyze': Property(
40 kind=Single(bool, empty_val=False, required=False), default=True, 41 kind=Single(bool, empty_val=False, required=False), default=True,
41 help='Use analyze to filter out affected targets.'), 42 help='Use analyze to filter out affected targets.'),
43 'suspected_revisions': Property(
44 kind=List(basestring), default=[],
45 help='A list of suspected revisions from heuristic analysis.'),
42 } 46 }
43 47
44 48
45 class CompileResult(object): 49 class CompileResult(object):
46 SKIPPED = 'skipped' # No compile is needed. 50 SKIPPED = 'skipped' # No compile is needed.
47 PASSED = 'passed' # Compile passed. 51 PASSED = 'passed' # Compile passed.
48 FAILED = 'failed' # Compile failed. 52 FAILED = 'failed' # Compile failed.
53 INFRA_FAILED = 'infra_failed' # Infra failed.
49 54
50 55
51 def _run_compile_at_revision(api, target_mastername, target_buildername, 56 def _run_compile_at_revision(api, target_mastername, target_buildername,
52 revision, compile_targets, use_analyze): 57 revision, compile_targets, use_analyze):
53 with api.step.nest('test %s' % str(revision)): 58 with api.step.nest('test %s' % str(revision)):
54 # Checkout code at the given revision to recompile. 59 # Checkout code at the given revision to recompile.
55 bot_config = api.chromium_tests.create_bot_config_object( 60 bot_config = api.chromium_tests.create_bot_config_object(
56 target_mastername, target_buildername) 61 target_mastername, target_buildername)
57 bot_update_step, bot_db = api.chromium_tests.prepare_checkout( 62 bot_update_step, bot_db = api.chromium_tests.prepare_checkout(
58 bot_config, root_solution_revision=revision) 63 bot_config, root_solution_revision=revision)
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
100 override_bot_type='builder_tester') 105 override_bot_type='builder_tester')
101 return CompileResult.PASSED 106 return CompileResult.PASSED
102 except api.step.InfraFailure: 107 except api.step.InfraFailure:
103 raise 108 raise
104 except api.step.StepFailure: 109 except api.step.StepFailure:
105 return CompileResult.FAILED 110 return CompileResult.FAILED
106 111
107 112
108 def RunSteps(api, target_mastername, target_buildername, 113 def RunSteps(api, target_mastername, target_buildername,
109 good_revision, bad_revision, 114 good_revision, bad_revision,
110 compile_targets, use_analyze): 115 compile_targets, use_analyze, suspected_revisions):
111 bot_config = api.chromium_tests.create_bot_config_object( 116 bot_config = api.chromium_tests.create_bot_config_object(
112 target_mastername, target_buildername) 117 target_mastername, target_buildername)
113 api.chromium_tests.configure_build( 118 api.chromium_tests.configure_build(
114 bot_config, override_bot_type='builder_tester') 119 bot_config, override_bot_type='builder_tester')
115 120
116 # Sync to bad revision, and retrieve revisions in the regression range. 121 # Sync to bad revision, and retrieve revisions in the regression range.
117 api.chromium_tests.prepare_checkout( 122 api.chromium_tests.prepare_checkout(
118 bot_config, 123 bot_config,
119 root_solution_revision=bad_revision) 124 root_solution_revision=bad_revision)
125
126 # Retrieve revisions in the regression range. The returned revisions are in
127 # order from older one to newer one.
lijeffrey 2016/04/11 23:52:58 nit: in order from oldest to newest.
stgao 2016/04/12 01:29:56 Done.
120 revisions_to_check = api.findit.revisions_between(good_revision, bad_revision) 128 revisions_to_check = api.findit.revisions_between(good_revision, bad_revision)
121 129
130 # If suspected commits are provided, divide the big regression range into
131 # a list of smaller sub-ranges. A sub-range starts at the revision right
132 # before a suspected revision.
133 #
134 # Example (previous build cycle passed at r0):
135 # Entire regression range: [r1, r2, r3, r4, ..., r10]
136 # Suspected commits: [r5, r8]
137 # Then the sub-ranges are:
138 # sub-range1: r7 and [r8, r9, r10]
139 # sub-range2: r4 and [r5, r6, r7]
lijeffrey 2016/04/11 23:52:58 nit: shouldn't this be r4 and [r5, r6]?
stgao 2016/04/12 01:29:56 Good catch.
140 # sub-range3: None and [r1, r2, r3]
141 #
142 # Sub-ranges with new revisions are checked first, because a compile failure
lijeffrey 2016/04/11 23:52:58 per discussion, maybe include a comment how if the
stgao 2016/04/12 01:29:56 Done. Good idea.
143 # might be due to conflicting commits in which case the later one should be
144 # the beginning of the compile breakage.
145 #
146 # In a sub-range, we check the revision right before the suspected one (e.g.
147 # r7 and r4 above) first:
148 # * if that revision fails, the remaining revisions in the same sub-range
149 # need no checking, because there are only two cases:
150 # case 1: the culprit is that revision itself
151 # case 2: the culprit is in a sub-range with older revisions
152 # * if that revision passes, we keep checking remaining revisions in the same
153 # sub-range:
154 # case 3: if any revision fails, the culprit is found since it is the
155 # first failure after a row of pass.
156 # case 4: if no revision fails, the culprit is either case 1 above or
157 # there is no culprit and the compile failure is a flaky one.
158 #
159 # In the example above:
160 # * if r8 is the actual culprit, r7 should pass. We continue checking r8 which
161 # should fail and we find r8.
162 # * if r5 is the actual culprit, r7 should fail. We skip [r8, r9, r10],
163 # instead check r4 and r5 in the sub-range2 and find r5.
164 # * if r7 is the actual culprit, r7 should fail. We skip [r8, r9, r10],
165 # instead check revisions in the sub-range2 which should all pass, and we
166 # find r7.
167 # * if r1 is the actual culprit, we check r7 and r4 before checking the
lijeffrey 2016/04/11 23:52:58 Question: So in this case, this is the worst, beca
stgao 2016/04/12 01:29:56 If the failure is a CC or CXX compile failure, it
168 # sub-range3 in which r0 is skipped because it is known as good revision in
169 # previous build cycle.
170 suspected_revision_index = [
lijeffrey 2016/04/11 23:52:58 nit: remove 1 space before =
stgao 2016/04/12 01:29:56 Done.
171 revisions_to_check.index(r)
172 for r in set(suspected_revisions) if r in revisions_to_check]
173 if suspected_revision_index:
174 sub_ranges = []
175 remaining_revisions = revisions_to_check[:]
176 for index in sorted(suspected_revision_index, reverse=True):
177 if index > 0:
178 sub_ranges.append(remaining_revisions[index - 1:])
179 remaining_revisions = remaining_revisions[:index - 1]
180 # None is a placeholder for the known good revision.
181 sub_ranges.append([None] + remaining_revisions)
182 else:
183 sub_ranges = [[None] + revisions_to_check]
184
122 compile_results = {} 185 compile_results = {}
123 try_job_metadata = { 186 try_job_metadata = {
124 'regression_range_size': len(revisions_to_check) 187 'regression_range_size': len(revisions_to_check),
188 'sub_ranges': sub_ranges[:],
125 } 189 }
126 report = { 190 report = {
127 'result': compile_results, 191 'result': compile_results,
128 'metadata': try_job_metadata, 192 'metadata': try_job_metadata,
129 } 193 }
130 194
195 suspected_revision = None
196 revision_being_checked = None
197 found = False
131 try: 198 try:
132 for current_revision in revisions_to_check: 199 while not found and sub_ranges:
133 last_revision = None 200 first_revision = sub_ranges[0][0]
134 compile_result = _run_compile_at_revision( 201 following_revisions = sub_ranges[0][1:]
135 api, target_mastername, target_buildername, 202 sub_ranges.pop(0)
136 current_revision, compile_targets, use_analyze)
137 203
138 compile_results[current_revision] = compile_result 204 if first_revision is not None: # No compile for the known good revision.
139 last_revision = current_revision 205 revision_being_checked = first_revision
140 if compile_result == CompileResult.FAILED: 206 compile_result = _run_compile_at_revision(
141 break # Found the culprit, no need to check later revisions. 207 api, target_mastername, target_buildername,
208 first_revision, compile_targets, use_analyze)
209 compile_results[first_revision] = compile_result
210 if compile_result == CompileResult.FAILED:
211 # The first revision of this range already failed, thus either it is
212 # the culprit or the culprit is in a range with older revisions.
213 suspected_revision = first_revision
214 continue
215
216 # If first revision passed, the culprit is either in the current range or
217 # is the first revision of previous range as identified above.
lijeffrey 2016/04/11 23:52:58 is this comment always true? for example in our c
stgao 2016/04/12 01:29:56 Sorry for the confusion. I updated the wording and
218 for revision in following_revisions:
219 revision_being_checked = revision
220 compile_result = _run_compile_at_revision(
221 api, target_mastername, target_buildername,
222 revision, compile_targets, use_analyze)
223 compile_results[revision] = compile_result
224 if compile_result == CompileResult.FAILED:
225 # First failure after a serial of pass.
lijeffrey 2016/04/11 23:52:58 nit: a series of passes
stgao 2016/04/12 01:29:56 Done.
226 suspected_revision = revision
227 found = True
228 break
229
230 if not found and suspected_revision is not None:
231 # If all revisions in the current range passed, and the first revision
232 # of previous range failed, the culprit is found too.
233 found = True
234 except api.step.InfraFailure:
235 compile_results[revision_being_checked] = CompileResult.INFRA_FAILED
236 raise
142 finally: 237 finally:
143 # Report the result. 238 # Report the result.
144 step_result = api.python.succeeding_step( 239 step_result = api.python.succeeding_step(
145 'report', [json.dumps(report, indent=2)], as_log='report') 240 'report', [json.dumps(report, indent=2)], as_log='report')
146 241
147 if (last_revision and 242 if found:
148 compile_results.get(last_revision) == CompileResult.FAILED): 243 step_result.presentation.step_text = (
149 step_result.presentation.step_text = '<br/>Culprit: %s' % last_revision 244 '<br/>Culprit: <a href="https://crrev.com/%s">%s</a>' % (
245 suspected_revision, suspected_revision))
150 246
151 # Set the report as a build property too, so that it will be reported back 247 # Set the report as a build property too, so that it will be reported back
152 # to Buildbucket and Findit will pull from there instead of buildbot master. 248 # to Buildbucket and Findit will pull from there instead of buildbot master.
153 step_result.presentation.properties['report'] = report 249 step_result.presentation.properties['report'] = report
154 250
155 return report 251 return report
156 252
157 253
158 def GenTests(api): 254 def GenTests(api):
159 def props(compile_targets=None, use_analyze=False): 255 def props(compile_targets=None, use_analyze=False,
256 good_revision=None, bad_revision=None,
257 suspected_revisions=None):
160 properties = { 258 properties = {
161 'mastername': 'tryserver.chromium.linux', 259 'mastername': 'tryserver.chromium.linux',
162 'buildername': 'linux_variable', 260 'buildername': 'linux_variable',
163 'slavename': 'build1-a1', 261 'slavename': 'build1-a1',
164 'buildnumber': '1', 262 'buildnumber': '1',
165 'target_mastername': 'chromium.linux', 263 'target_mastername': 'chromium.linux',
166 'target_buildername': 'Linux Builder', 264 'target_buildername': 'Linux Builder',
167 'good_revision': 'r0', 265 'good_revision': good_revision or 'r0',
168 'bad_revision': 'r1', 266 'bad_revision': bad_revision or 'r1',
169 'use_analyze': use_analyze, 267 'use_analyze': use_analyze,
170 } 268 }
171 if compile_targets: 269 if compile_targets:
172 properties['compile_targets'] = compile_targets 270 properties['compile_targets'] = compile_targets
271 if suspected_revisions:
272 properties['suspected_revisions'] = suspected_revisions
173 return api.properties(**properties) + api.platform.name('linux') 273 return api.properties(**properties) + api.platform.name('linux')
174 274
175 yield ( 275 yield (
176 api.test('compile_specified_targets') + 276 api.test('compile_specified_targets') +
177 props(compile_targets=['target_name']) + 277 props(compile_targets=['target_name']) +
178 api.override_step_data('test r1.check_targets', 278 api.override_step_data('test r1.check_targets',
179 api.json.output({ 279 api.json.output({
180 'found': ['target_name'], 280 'found': ['target_name'],
181 'not_found': [], 281 'not_found': [],
182 })) 282 }))
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after
310 })) + 410 })) +
311 api.override_step_data( 411 api.override_step_data(
312 'test r1.analyze', 412 'test r1.analyze',
313 api.json.output({ 413 api.json.output({
314 'status': 'Found dependency', 414 'status': 'Found dependency',
315 'compile_targets': ['a', 'a_run'], 415 'compile_targets': ['a', 'a_run'],
316 'test_targets': ['a', 'a_run'], 416 'test_targets': ['a', 'a_run'],
317 }) 417 })
318 ) 418 )
319 ) 419 )
420
421 # Entire regression range: (r1, r6]
422 # Suspected_revisions: [r4]
423 # Expected smaller ranges: [r3, [r4, r5, r6]], [None, [r2]]
424 # Actual culprit: r4
425 # Should only run compile on r3, and then r4.
426 yield (
427 api.test('find_culprit_in_middle_of_a_sub_range') +
428 props(compile_targets=['target_name'],
429 good_revision='r1',
430 bad_revision='r6',
431 suspected_revisions=['r4']) +
432 api.override_step_data(
433 'git commits in range',
434 api.raw_io.stream_output(
435 '\n'.join('r%d' % i for i in reversed(range(2, 7))))) +
436 api.override_step_data('test r3.check_targets',
437 api.json.output({
438 'found': ['target_name'],
439 'not_found': [],
440 })) +
441 api.override_step_data('test r4.check_targets',
442 api.json.output({
443 'found': ['target_name'],
444 'not_found': [],
445 })) +
446 api.override_step_data('test r4.compile', retcode=1)
447 )
448
449 # Entire regression range: (r1, r6]
450 # Suspected_revisions: [r4]
451 # Expected smaller ranges: [r3, [r4, r5, r6]], [None, [r2]]
452 # Actual culprit: r3
453 # Should only run compile on r3, and then r2.
454 yield (
455 api.test('find_culprit_at_first_revision_of_a_sub_range') +
456 props(compile_targets=['target_name'],
457 good_revision='r1',
458 bad_revision='r6',
459 suspected_revisions=['r4']) +
460 api.override_step_data(
461 'git commits in range',
462 api.raw_io.stream_output(
463 '\n'.join('r%d' % i for i in reversed(range(2, 7))))) +
464 api.override_step_data('test r3.check_targets',
465 api.json.output({
466 'found': ['target_name'],
467 'not_found': [],
468 })) +
469 api.override_step_data('test r3.compile', retcode=1) +
470 api.override_step_data('test r2.check_targets',
471 api.json.output({
472 'found': ['target_name'],
473 'not_found': [],
474 }))
475 )
476
477 # Entire regression range: (r1, r10]
478 # Suspected_revisions: [r4, r8]
479 # Expected smaller ranges:
480 # [r7, [r8, r9, r10]], [r3, [r4, r5, r6]], [None, [r2]]
481 # Actual culprit: r4
482 # Should only run compile on r7(failed), then r3(pass) and r4(failed).
483 yield (
484 api.test('find_culprit_in_second_sub_range') +
485 props(compile_targets=['target_name'],
486 good_revision='r1',
487 bad_revision='r6',
488 suspected_revisions=['r4', 'r8']) +
489 api.override_step_data(
490 'git commits in range',
491 api.raw_io.stream_output(
492 '\n'.join('r%d' % i for i in reversed(range(2, 11))))) +
493 api.override_step_data('test r7.check_targets',
494 api.json.output({
495 'found': ['target_name'],
496 'not_found': [],
497 })) +
498 api.override_step_data('test r7.compile', retcode=1) +
499 api.override_step_data('test r3.check_targets',
500 api.json.output({
501 'found': ['target_name'],
502 'not_found': [],
503 })) +
504 api.override_step_data('test r4.check_targets',
505 api.json.output({
506 'found': ['target_name'],
507 'not_found': [],
508 })) +
509 api.override_step_data('test r4.compile', retcode=1)
510 )
511
512 # Entire regression range: (r1, r5]
513 # Suspected_revisions: [r2]
514 # Expected smaller ranges:
515 # [None, r2, r3, r4, r5]
516 # Actual culprit: r2
517 # Should only run compile on r2(failed).
518 yield (
519 api.test('find_culprit_as_first_revision_of_entire_range') +
520 props(compile_targets=['target_name'],
521 good_revision='r1',
522 bad_revision='r5',
523 suspected_revisions=['r2']) +
524 api.override_step_data(
525 'git commits in range',
526 api.raw_io.stream_output(
527 '\n'.join('r%d' % i for i in reversed(range(2, 6))))) +
528 api.override_step_data('test r2.check_targets',
529 api.json.output({
530 'found': ['target_name'],
531 'not_found': [],
532 })) +
533 api.override_step_data('test r2.compile', retcode=1)
534 )
OLDNEW
« no previous file with comments | « no previous file | scripts/slave/recipes/findit/chromium/compile.expected/compile_affected_targets_only.json » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698