blob: 40b3297f6b460d4896b4c9698abc866c2efd19a3 [file] [log] [blame]
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A Model that represents one bisect or perf test try job.
TryJob entities are checked in /update_bug_with_results to check completed
bisect jobs and update bugs with results.
They are also used in /auto_bisect to restart unsuccessful bisect jobs.
"""
import datetime
import json
import logging
from google.appengine.ext import ndb
from dashboard import bisect_stats
from dashboard import buildbucket_service
from dashboard.models import bug_data
from dashboard.models import internal_only_model
class TryJob(internal_only_model.InternalOnlyModel):
"""Stores config and tracking info about a single try job."""
bot = ndb.StringProperty()
config = ndb.TextProperty()
bug_id = ndb.IntegerProperty()
email = ndb.StringProperty()
rietveld_issue_id = ndb.IntegerProperty()
rietveld_patchset_id = ndb.IntegerProperty()
master_name = ndb.StringProperty(default='ChromiumPerf', indexed=False)
buildbucket_job_id = ndb.StringProperty()
internal_only = ndb.BooleanProperty(default=False, indexed=True)
# Bisect run status (e.g., started, failed).
status = ndb.StringProperty(
default='pending',
choices=[
'pending', # Created, but job start has not been confirmed.
'started', # Job is confirmed started.
'failed', # Job terminated, red build.
'staled', # No updates from bots.
'completed', # Job terminated, green build.
'aborted', # Job terminated with abort (purple, early abort).
],
indexed=True)
# Number of times this job has been tried.
run_count = ndb.IntegerProperty(default=0)
# Last time this job was started.
last_ran_timestamp = ndb.DateTimeProperty()
job_type = ndb.StringProperty(
default='bisect',
choices=['bisect', 'bisect-fyi', 'perf-try'])
# job_name attribute is used by try jobs of bisect FYI.
job_name = ndb.StringProperty(default=None)
# Results data coming from bisect bots.
results_data = ndb.JsonProperty(indexed=False)
log_record_id = ndb.StringProperty(indexed=False)
# Sets of emails of users who has confirmed this TryJob result is bad.
bad_result_emails = ndb.PickleProperty()
def SetStarted(self):
self.status = 'started'
self.run_count += 1
self.last_ran_timestamp = datetime.datetime.now()
self.put()
if self.bug_id:
bug_data.SetBisectStatus(self.bug_id, 'started')
def SetFailed(self):
self.status = 'failed'
self.put()
if self.bug_id:
bug_data.SetBisectStatus(self.bug_id, 'failed')
bisect_stats.UpdateBisectStats(self.bot, 'failed')
def SetStaled(self):
self.status = 'staled'
self.put()
logging.info('Updated status to staled')
# TODO(sullivan, dtu): what is the purpose of 'staled' status? Doesn't it
# just prevent updating jobs older than 24 hours???
# TODO(chrisphan): Add 'staled' state to bug_data and bisect_stats.
if self.bug_id:
bug_data.SetBisectStatus(self.bug_id, 'failed')
bisect_stats.UpdateBisectStats(self.bot, 'failed')
def SetCompleted(self):
logging.info('Updated status to completed')
self.status = 'completed'
self.put()
if self.bug_id:
bug_data.SetBisectStatus(self.bug_id, 'completed')
bisect_stats.UpdateBisectStats(self.bot, 'completed')
def GetConfigDict(self):
return json.loads(self.config.split('=', 1)[1])
def CheckFailureFromBuildBucket(self):
# Buildbucket job id is not always set.
if not self.buildbucket_job_id:
return
job_info = buildbucket_service.GetJobStatus(self.buildbucket_job_id)
data = job_info.get('build', {})
# Since the job is completed successfully, results_data must
# have been set appropriately by the bisector.
# The buildbucket job's 'status' and 'result' fields are documented here:
# https://goto.google.com/bb_status
if data.get('status') == 'COMPLETED' and data.get('result') == 'SUCCESS':
return
# Proceed if the job failed or cancelled
logging.info('Job failed. Buildbucket id %s', self.buildbucket_job_id)
data['result_details'] = json.loads(data['result_details_json'])
# There are various failure and cancellation reasons for a buildbucket
# job to fail as listed in https://goto.google.com/bb_status.
job_updates = {
'status': 'failed',
'failure_reason': (data.get('cancelation_reason') or
data.get('failure_reason')),
'buildbot_log_url': data.get('url')
}
details = data.get('result_details')
if details:
properties = details.get('properties')
if properties:
job_updates['bisect_bot'] = properties.get('buildername')
job_updates['extra_result_code'] = properties.get(
'extra_result_code')
bisect_config = properties.get('bisect_config')
if bisect_config:
job_updates['try_job_id'] = bisect_config.get('try_job_id')
job_updates['bug_id'] = bisect_config.get('bug_id')
job_updates['command'] = bisect_config.get('command')
job_updates['test_type'] = bisect_config.get('test_type')
job_updates['metric'] = bisect_config.get('metric')
job_updates['good_revision'] = bisect_config.get('good_revision')
job_updates['bad_revision'] = bisect_config.get('bad_revision')
if not self.results_data:
self.results_data = {}
self.results_data.update(job_updates)
self.status = 'failed'
self.last_ran_timestamp = datetime.datetime.fromtimestamp(
float(data['updated_ts'])/1000000)
self.put()
logging.info('updated status to failed.')