#!/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:]))