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

Side by Side Diff: scripts/slave/recipe_modules/auto_bisect/revision_state.py

Issue 1758603004: Checking for failure in requested builds. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/build.git@master
Patch Set: Updating expectations after rebase and sync. Yet again. Created 4 years, 9 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
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 """An interface for holding state and result of revisions in a bisect job. 5 """An interface for holding state and result of revisions in a bisect job.
6 6
7 When implementing support for tests other than perf, one should extend this 7 When implementing support for tests other than perf, one should extend this
8 class so that the bisect module and recipe can use it. 8 class so that the bisect module and recipe can use it.
9 9
10 See perf_revision_state for an example. 10 See perf_revision_state for an example.
11 """ 11 """
12 12
13 import hashlib 13 import hashlib
14 import json 14 import json
15 import math 15 import math
16 import os 16 import os
17 import tempfile 17 import tempfile
18 import re 18 import re
19 import uuid 19 import uuid
20 20
21 from . import depot_config 21 from . import depot_config
22 22
23 # These relate to how to increase the number of repetitions during re-test 23 # These relate to how to increase the number of repetitions during re-test
24 MINIMUM_SAMPLE_SIZE = 5 24 MINIMUM_SAMPLE_SIZE = 5
25 INCREASE_FACTOR = 1.5 25 INCREASE_FACTOR = 1.5
26 26 # Buildbot job result codes.
27 # See http://docs.buildbot.net/current/developer/results.html
28 SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION = range(5)
27 29
28 class RevisionState(object): 30 class RevisionState(object):
29 """Abstracts the state of a single revision on a bisect job.""" 31 """Abstracts the state of a single revision on a bisect job."""
30 32
31 # Possible values for the status attribute of RevisionState: 33 # Possible values for the status attribute of RevisionState:
32 ( 34 (
33 NEW, # A revision_state object that has just been initialized. 35 NEW, # A revision_state object that has just been initialized.
34 BUILDING, # Requested a build for this revision, waiting for it. 36 BUILDING, # Requested a build for this revision, waiting for it.
35 TESTING, # A test job for this revision was triggered, waiting for it. 37 TESTING, # A test job for this revision was triggered, waiting for it.
36 TESTED, # The test job completed with non-failing results. 38 TESTED, # The test job completed with non-failing results.
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
84 self.deps = dict(base_revision.deps) 86 self.deps = dict(base_revision.deps)
85 self.deps[self.depot_name] = self.commit_hash 87 self.deps[self.depot_name] = self.commit_hash
86 else: 88 else:
87 self.needs_patch = False 89 self.needs_patch = False
88 self.build_url = self.bisector.get_platform_gs_prefix() + self._gs_suffix() 90 self.build_url = self.bisector.get_platform_gs_prefix() + self._gs_suffix()
89 self.values = [] 91 self.values = []
90 self.mean_value = None 92 self.mean_value = None
91 self.std_dev = None 93 self.std_dev = None
92 self.repeat_count = MINIMUM_SAMPLE_SIZE 94 self.repeat_count = MINIMUM_SAMPLE_SIZE
93 self._test_config = None 95 self._test_config = None
96 self.build_number = None
94 97
95 @property 98 @property
96 def tested(self): 99 def tested(self):
97 return self.status in (RevisionState.TESTED,) 100 return self.status in (RevisionState.TESTED,)
98 101
99 @property 102 @property
100 def in_progress(self): 103 def in_progress(self):
101 return self.status in (RevisionState.BUILDING, RevisionState.TESTING, 104 return self.status in (RevisionState.BUILDING, RevisionState.TESTING,
102 RevisionState.NEED_MORE_DATA) 105 RevisionState.NEED_MORE_DATA)
103 106
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after
212 215
213 def update_status(self): 216 def update_status(self):
214 """Checks on the pending jobs and updates status accordingly. 217 """Checks on the pending jobs and updates status accordingly.
215 218
216 This method will check for the build to complete and then trigger the test, 219 This method will check for the build to complete and then trigger the test,
217 or will wait for the test as appropriate. 220 or will wait for the test as appropriate.
218 221
219 To wait for the test we try to get the buildbot job url from GS, and if 222 To wait for the test we try to get the buildbot job url from GS, and if
220 available, we query the status of such job. 223 available, we query the status of such job.
221 """ 224 """
222 if self.status == RevisionState.BUILDING and self._is_build_archived(): 225 if self.status == RevisionState.BUILDING:
223 self.start_job() 226 if self._is_build_archived():
227 self.start_job()
228 elif self._is_build_failed():
229 self.status = RevisionState.FAILED
224 elif (self.status in (RevisionState.TESTING, RevisionState.NEED_MORE_DATA) 230 elif (self.status in (RevisionState.TESTING, RevisionState.NEED_MORE_DATA)
225 and self._results_available()): 231 and self._results_available()):
226 # If we have already decided whether the revision is good or bad we 232 # If we have already decided whether the revision is good or bad we
227 # shouldn't check again 233 # shouldn't check again
228 check_revision_goodness = not(self.good or self.bad) 234 check_revision_goodness = not(self.good or self.bad)
229 self._read_test_results( 235 self._read_test_results(
230 check_revision_goodness=check_revision_goodness) 236 check_revision_goodness=check_revision_goodness)
231 # We assume _read_test_results may have changed the status to a broken 237 # We assume _read_test_results may have changed the status to a broken
232 # state such as FAILED or ABORTED. 238 # state such as FAILED or ABORTED.
233 if self.status in (RevisionState.TESTING, RevisionState.NEED_MORE_DATA): 239 if self.status in (RevisionState.TESTING, RevisionState.NEED_MORE_DATA):
234 self.status = RevisionState.TESTED 240 self.status = RevisionState.TESTED
235 241
236 def _is_build_archived(self): 242 def _is_build_archived(self):
237 """Checks if the revision is already built and archived.""" 243 """Checks if the revision is already built and archived."""
238 if not self.build_archived: 244 if not self.build_archived:
239 api = self.bisector.api 245 api = self.bisector.api
240 self.build_archived = api.gsutil_file_exists(self.build_url) 246 self.build_archived = api.gsutil_file_exists(self.build_url)
241 247
242 if self.bisector.dummy_builds: 248 if self.bisector.dummy_builds:
243 self.build_archived = self.in_progress 249 self.build_archived = self.in_progress
244 250
245 return self.build_archived 251 return self.build_archived
246 252
253 def _fetch_build_info(self, base_url, build_number):
254 api = self.bisector.api
255 build_url = '%s/builds/%s?as_text=1' % (base_url, build_number)
256 fetch_result = api.m.url.fetch_to_file(
257 build_url, None, step_name='fetch build details',
258 stdout=api.m.raw_io.output())
259 return json.loads(fetch_result.stdout or '{}')
260
261 def _is_build_failed(self):
262 api = self.bisector.api
263 current_build = None
264 base_url = '%sjson/builders/%s'% (
265 # If this variable is not set assume local dev master.
266 os.environ.get('BUILDBOT_URL', 'http://localhost:8041/'),
267 self.bisector.get_builder_bot_for_this_platform())
268 if self.build_number is None:
269 try:
270 # Get all the current builds.
271 builder_state_url = base_url + '?as_text=1'
272 fetch_step = api.m.url.fetch_to_file(
273 builder_state_url, None,
274 step_name='fetch builder state',
275 stdout=api.m.raw_io.output())
276 builder_state = fetch_step.stdout
277 builder_state = json.loads(builder_state or '{}')
278 for build_number in builder_state.get('cachedBuilds', []):
279 build = self._fetch_build_info(base_url, build_number)
280 build_properties = dict(build.get('properties', []))
281 if build_properties.get('build_archive_url') == self.build_url:
282 self.build_number = build_number
283 current_build = build
284 break
285 except api.m.step.StepFailure: # pragma: no cover
286 # If we cannot get json from buildbot, we cannot determine if a build is
287 # failed, hence we consider it in progress until it times out.
288 return False
289 if self.build_number is None:
290 # The build hasn't started yet, therefore it's not failed.
291 return False
292 if not current_build:
293 current_build = self._fetch_build_info(base_url, self.build_number)
294 return current_build.get('results') in [FAILURE, SKIPPED, EXCEPTION]
295
247 def _results_available(self): 296 def _results_available(self):
248 """Checks if the results for the test job have been uploaded.""" 297 """Checks if the results for the test job have been uploaded."""
249 api = self.bisector.api 298 api = self.bisector.api
250 result = api.gsutil_file_exists(self.test_results_url) 299 result = api.gsutil_file_exists(self.test_results_url)
251 if self.bisector.dummy_builds: 300 if self.bisector.dummy_builds:
252 return self.in_progress 301 return self.in_progress
253 return result # pragma: no cover 302 return result # pragma: no cover
254 303
255 def _gs_suffix(self): 304 def _gs_suffix(self):
256 """Provides the expected right half of the build filename. 305 """Provides the expected right half of the build filename.
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
310 file_name = str(api.m.path['tmp_base'].join(build_name + '.diff')) 359 file_name = str(api.m.path['tmp_base'].join(build_name + '.diff'))
311 api.m.file.write('Saving diff patch for ' + self.commit_hash, 360 api.m.file.write('Saving diff patch for ' + self.commit_hash,
312 file_name, self.deps_patch + self.deps_sha_patch) 361 file_name, self.deps_patch + self.deps_sha_patch)
313 return file_name 362 return file_name
314 363
315 def _request_build(self): 364 def _request_build(self):
316 """Posts a request to buildbot to build this revision and archive it.""" 365 """Posts a request to buildbot to build this revision and archive it."""
317 # TODO: Rewrite using the trigger module. 366 # TODO: Rewrite using the trigger module.
318 api = self.bisector.api 367 api = self.bisector.api
319 bot_name = self.bisector.get_builder_bot_for_this_platform() 368 bot_name = self.bisector.get_builder_bot_for_this_platform()
320 if self.bisector.dummy_builds: 369 if self.bisector.bisect_config.get('dummy_job_names'):
321 self.job_name = self.commit_hash + '-build' 370 self.job_name = self.commit_hash + '-build'
322 else: # pragma: no cover 371 else: # pragma: no cover
323 self.job_name = uuid.uuid4().hex 372 self.job_name = uuid.uuid4().hex
324 if self.needs_patch: 373 if self.needs_patch:
325 self.patch_file = self._write_deps_patch_file( 374 self.patch_file = self._write_deps_patch_file(
326 self.job_name) 375 self.job_name)
327 else: 376 else:
328 self.patch_file = '/dev/null' 377 self.patch_file = '/dev/null'
329 378
330 # To allow multiple nested levels, we go to the topmost revision. 379 # To allow multiple nested levels, we go to the topmost revision.
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
376 self._test_config = result 425 self._test_config = result
377 return result 426 return result
378 427
379 def _do_test(self): 428 def _do_test(self):
380 """Triggers tests for a revision, either locally or via try job. 429 """Triggers tests for a revision, either locally or via try job.
381 430
382 If local testing is enabled (i.e. director/tester merged) then 431 If local testing is enabled (i.e. director/tester merged) then
383 the test will be run on the same machine. Otherwise, this posts 432 the test will be run on the same machine. Otherwise, this posts
384 a request to buildbot to download and perf-test this build. 433 a request to buildbot to download and perf-test this build.
385 """ 434 """
386 if self.bisector.dummy_builds: 435 if self.bisector.bisect_config.get('dummy_job_names'):
387 self.job_name = self.commit_hash + '-test' 436 self.job_name = self.commit_hash + '-test'
388 else: # pragma: no cover 437 else: # pragma: no cover
389 self.job_name = uuid.uuid4().hex 438 self.job_name = uuid.uuid4().hex
390 api = self.bisector.api 439 api = self.bisector.api
391 top_revision = self 440 top_revision = self
392 while top_revision.base_revision: # pragma: no cover 441 while top_revision.base_revision: # pragma: no cover
393 top_revision = top_revision.base_revision 442 top_revision = top_revision.base_revision
394 perf_test_properties = { 443 perf_test_properties = {
395 'builder_name': self.bisector.get_perf_tester_name(), 444 'builder_name': self.bisector.get_perf_tester_name(),
396 'properties': { 445 'properties': {
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after
513 key=lambda x: len(x.values)) 562 key=lambda x: len(x.values))
514 if (len(self.bisector.last_tested_revision.values) == 563 if (len(self.bisector.last_tested_revision.values) ==
515 next_revision_to_test.values): 564 next_revision_to_test.values):
516 self.bisector.last_tested_revision.retest() 565 self.bisector.last_tested_revision.retest()
517 else: 566 else:
518 next_revision_to_test.retest() 567 next_revision_to_test.retest()
519 568
520 def __repr__(self): 569 def __repr__(self):
521 return ('RevisionState(rev=%s, values=%r, mean_value=%r, std_dev=%r)' % ( 570 return ('RevisionState(rev=%s, values=%r, mean_value=%r, std_dev=%r)' % (
522 self.revision_string(), self.values, self.mean_value, self.std_dev)) 571 self.revision_string(), self.values, self.mean_value, self.std_dev))
OLDNEW
« no previous file with comments | « scripts/slave/recipe_modules/auto_bisect/example.expected/windows_x64_bisector.json ('k') | scripts/slave/recipes/bisect.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698