#!/usr/bin/env python
import common
import json
import re
import sys
from autotest_lib.client.common_lib import time_utils
from autotest_lib.server import frontend
from autotest_lib.server.lib import status_history
from autotest_lib.server.lib import suite_report
from chromite.lib import cidb
from chromite.lib import commandline
from chromite.lib import cros_logging as logging
HostJobHistory = status_history.HostJobHistory
def GetParser():
"""Creates the argparse parser."""
parser = commandline.ArgumentParser(description=__doc__)
parser.add_argument('--input', type=str, action='store',
help='Input JSON file')
parser.add_argument('--output', type=str, action='store',
help='Output JSON file')
parser.add_argument('--name_filter', type=str, action='store',
help='Name of task to look for')
parser.add_argument('--status_filter', type=str, action='store',
help='Status fo task to look for')
parser.add_argument('--afe', type=str, action='store',
help='AFE server to connect to')
parser.add_argument('suite_ids', type=str, nargs='*', action='store',
help='Suite ids to resolve')
return parser
def GetSuiteHQEs(suite_job_id, look_past_seconds, afe=None, tko=None):
"""Get the host queue entries for active DUTs during a suite job.
@param suite_job_id: Suite's AFE job id.
@param look_past_seconds: Number of seconds past the end of the suite
job to look for next HQEs.
@param afe: AFE database handle.
@param tko: TKO database handle.
@returns A dictionary keyed on hostname to a list of host queue entry
dictionaries. HQE dictionary contains the following keys:
name, hostname, job_status, job_url, gs_url, start_time, end_time
"""
if afe is None:
afe = frontend.AFE()
if tko is None:
tko = frontend.TKO()
# Find the suite job and when it ran.
statuses = tko.get_job_test_statuses_from_db(suite_job_id)
if len(statuses):
for s in statuses:
if s.test_started_time == 'None' or s.test_finished_time == 'None':
logging.error(
'TKO entry missing time: %s %s %s %s %s %s %s %s %s' %
(s.id, s.test_name, s.status, s.reason,
s.test_started_time, s.test_finished_time,
s.job_owner, s.hostname, s.job_tag))
start_time = min(int(time_utils.to_epoch_time(s.test_started_time))
for s in statuses if s.test_started_time != 'None')
finish_time = max(int(time_utils.to_epoch_time(
s.test_finished_time)) for s in statuses
if s.test_finished_time != 'None')
else:
start_time = None
finish_time = None
# If there is no start time or finish time, won't be able to get HQEs.
if start_time is None or finish_time is None:
return {}
# Find all the HQE entries.
child_jobs = afe.get_jobs(parent_job_id=suite_job_id)
child_job_ids = {j.id for j in child_jobs}
hqes = afe.get_host_queue_entries(job_id__in=list(child_job_ids))
hostnames = {h.host.hostname for h in hqes if h.host}
host_hqes = {}
for hostname in hostnames:
history = HostJobHistory.get_host_history(afe, hostname,
start_time,
finish_time +
look_past_seconds)
for h in history:
gs_url = re.sub(r'http://.*/tko/retrieve_logs.cgi\?job=/results',
r'gs://chromeos-autotest-results',
h.job_url)
entry = {
'name': h.name,
'hostname': history.hostname,
'job_status': h.job_status,
'job_url': h.job_url,
'gs_url': gs_url,
'start_time': h.start_time,
'end_time': h.end_time,
}
host_hqes.setdefault(history.hostname, []).append(entry)
return host_hqes
def FindSpecialTasks(suite_job_id, look_past_seconds=1800,
name_filter=None, status_filter=None, afe=None, tko=None):
"""Find special tasks that happened around a suite job.
@param suite_job_id: Suite's AFE job id.
@param look_past_seconds: Number of seconds past the end of the suite
job to look for next HQEs.
@param name_filter: If not None, only return tasks with this name.
@param status_filter: If not None, only return tasks with this status.
@param afe: AFE database handle.
@param tko: TKO database handle.
@returns A dictionary keyed on hostname to a list of host queue entry
dictionaries. HQE dictionary contains the following keys:
name, hostname, job_status, job_url, gs_url, start_time, end_time,
next_entry
"""
host_hqes = GetSuiteHQEs(suite_job_id, look_past_seconds=look_past_seconds,
afe=afe, tko=tko)
task_entries = []
for hostname in host_hqes:
host_hqes[hostname] = sorted(host_hqes[hostname],
key=lambda k: k['start_time'])
current = None
for e in host_hqes[hostname]:
# Check if there is an entry to finish off by adding a pointer
# to this new entry.
if current:
logging.debug(' next task: %(name)s %(job_status)s '
'%(gs_url)s %(start_time)s %(end_time)s' % e)
# Only record a pointer to the next entry if filtering some out.
if name_filter or status_filter:
current['next_entry'] = e
task_entries.append(current)
current = None
# Perform matching.
if ((name_filter and e['name'] != name_filter) or
(status_filter and e['job_status'] != status_filter)):
continue
# Instead of appending right away, wait until the next entry
# to add a point to it.
current = e
logging.debug('Task %(name)s: %(job_status)s %(hostname)s '
'%(gs_url)s %(start_time)s %(end_time)s' % e)
# Add the last one even if a next entry wasn't found.
if current:
task_entries.append(current)
return task_entries
def main(argv):
parser = GetParser()
options = parser.parse_args(argv)
afe = None
if options.afe:
afe = frontend.AFE(server=options.afe)
tko = frontend.TKO()
special_tasks = []
builds = []
# Handle a JSON file being specified.
if options.input:
with open(options.input) as f:
data = json.load(f)
for build in data.get('builds', []):
# For each build, amend it to include the list of
# special tasks for its suite's jobs.
build.setdefault('special_tasks', {})
for suite_job_id in build['suite_ids']:
suite_tasks = FindSpecialTasks(
suite_job_id, name_filter=options.name_filter,
status_filter=options.status_filter,
afe=afe, tko=tko)
special_tasks.extend(suite_tasks)
build['special_tasks'][suite_job_id] = suite_tasks
logging.debug(build)
builds.append(build)
# Handle and specifically specified suite IDs.
for suite_job_id in options.suite_ids:
special_tasks.extend(FindSpecialTasks(
suite_job_id, name_filter=options.name_filter,
status_filter=options.status_filter, afe=afe, tko=tko))
# Output a resulting JSON file.
with open(options.output, 'w') if options.output else sys.stdout as f:
output = {
'special_tasks': special_tasks,
'name_filter': options.name_filter,
'status_filter': options.status_filter,
}
if len(builds):
output['builds'] = builds
json.dump(output, f)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))