普通文本  |  210行  |  8.26 KB

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