1c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu#!/usr/bin/python
2c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu#
3c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
4c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu# Use of this source code is governed by a BSD-style license that can be
5c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu# found in the LICENSE file.
6c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
7c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
8c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu"""Script to calculate timing stats for suites.
9c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
10c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK RyuThis script measures nine stats for a suite run.
11c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu1. Net suite runtime.
12c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu2. Suite scheduling overhead.
13c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu3. Average scheduling overhead.
14c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu4. Average Queuing time.
15c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu5. Average Resetting time.
16c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu6. Average provisioning time.
17c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu7. Average Running time.
18c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu8. Average Parsing time.
19c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu9. Average Gathering time.
20c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
21c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK RyuWhen the cron_mode is enabled, this script throws all stats but the first one
22c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu(Net suite runtime) to Graphite because the first one is already
23c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryubeing sent to Graphite by Autotest online.
24c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
25c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK RyuNet suite runtime is end-to-end time for a suite from the beginning
26c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryuto the end.
27c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK RyuIt is stored in a field, "duration", of a type, "suite_runtime" in
28c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryuelasticsearch (ES).
29c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
30c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK RyuSuite scheduling overhead is defined by the average of DUT overheads.
31c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK RyuSuite is composed of one or more jobs, and those jobs are run on
32c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryuone or more DUTs that are available.
33c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK RyuA DUT overhead is defined by:
34c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    DUT_i overhead = sum(net time for job_k - runtime for job_k
35c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                         - runtime for special tasks of job_k)
36c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    Job_k are the jobs run on DUT_i.
37c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
38c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK RyuNet time for a job is the time from job_queued_time to hqe_finished_time.
39c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryujob_queued_time is stored in the "queued_time" column of "tko_jobs" table.
40c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryuhqe_finished_time is stored in the "finished_on" of "afe_host_queue_entries"
41c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryutable.
42c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK RyuWe do not use "job_finished_time" of "tko_jobs" as job_finished_time is
43c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryurecorded before gathering/parsing/archiving.
44c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK RyuWe do not use hqe started time ("started_on" of "afe_host_queue_entries"),
45c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryuas it does not account for the lag from a host is assigned to the job till
46c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryuthe scheduler sees the assignment.
47c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
48c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK RyuRuntime for job_k is the sum of durations for the records of
49c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu"job_time_breakdown" type in ES that have "Queued" or "Running" status.
50c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK RyuIt is possible that a job has multiple "Queued" records when the job's test
51c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryufailed and tried again.
52c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK RyuWe take into account only the last "Queued" record.
53c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
54c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK RyuRuntime for special tasks of job_k is the sum of durations for the records
55c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryuof "job_time_breakdown" type in ES that have "Resetting", "Provisioning",
56c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu"Gathering", or "Parsing" status.
57c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK RyuWe take into account only the records whose timestamp is larger than
58c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryuthe timestamp of the last "Queued" record.
59c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu"""
60c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
61c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryuimport argparse
62c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryufrom datetime import datetime
63c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryufrom datetime import timedelta
64c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
65c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryuimport common
66c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryufrom autotest_lib.client.common_lib import host_queue_entry_states
67c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryufrom autotest_lib.client.common_lib import time_utils
68b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Blackfrom autotest_lib.client.common_lib.cros.graphite import autotest_es
691e1c41b1b4a1b97c0b7086b8430856ed45e064d3Gabe Blackfrom autotest_lib.client.common_lib.cros.graphite import autotest_stats
70c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryufrom autotest_lib.frontend import setup_django_environment
71c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryufrom autotest_lib.frontend.afe import models
72c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryufrom autotest_lib.frontend.tko import models as tko_models
73c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryufrom autotest_lib.server import utils
74c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryufrom autotest_lib.site_utils import job_overhead
75c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
76c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
77c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu_options = None
78c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
79c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu_hqes = host_queue_entry_states.Status
80c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu_states = [
81c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        _hqes.QUEUED, _hqes.RESETTING, _hqes.PROVISIONING,
82c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        _hqes.RUNNING, _hqes.GATHERING, _hqes.PARSING]
83c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
84c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
85c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryudef mean(l):
86c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
87c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    Calculates an Arithmetic Mean for the numbers in a list.
88c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
89c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param l: A list of numbers.
90c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @return: Arithmetic mean if the list is not empty.
91c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu             Otherwise, returns zero.
92c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
93c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    return float(sum(l)) / len(l) if l else 0
94c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
95c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
96c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryudef print_verbose(string, *args):
97c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    if _options.verbose:
98c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        print(string % args)
99c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
100c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
101c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryudef get_nontask_runtime(job_id, dut, job_info_dict):
102c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
103c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    Get sum of durations for "Queued", "Running", "Parsing", and "Gathering"
104c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    status records.
105c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    job_info_dict will be modified in this function to store the duration
106c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    for each status.
107c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
108c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param job_id: The job id of interest.
109c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param dut: Hostname of a DUT that the job ran on.
110c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param job_info_dict: Dictionary that has information for jobs.
111c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @return: Tuple of sum of durations and the timestamp for the last
112c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu             Queued record.
113c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
114b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    results = autotest_es.query(
115c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            fields_returned=['status', 'duration', 'time_recorded'],
116c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            equality_constraints=[('_type', 'job_time_breakdown'),
117c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                                  ('job_id', job_id),
118c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                                  ('hostname', dut)],
119c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            sort_specs=[{'time_recorded': 'desc'}])
120c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
121c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    sum = 0
122c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    last_queued_timestamp = 0
123c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    # There could be multiple "Queued" records.
124c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    # Get sum of durations for the records after the last "Queued" records
125c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    # (including the last "Queued" record).
126c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    # Exploits the fact that "results" are ordered in the descending order
127c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    # of time_recorded.
128b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    for hit in results.hits:
129c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        job_info_dict[job_id][hit['status']] = float(hit['duration'])
130c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        if hit['status'] == 'Queued':
131c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            # The first Queued record is the last one because of the descending
132c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            # order of "results".
133c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            last_queued_timestamp = float(hit['time_recorded'])
134c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            sum += float(hit['duration'])
135c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            break
136c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        else:
137c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            sum += float(hit['duration'])
138c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    return (sum, last_queued_timestamp)
139c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
140c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
141c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryudef get_tasks_runtime(task_list, dut, t_start, job_id, job_info_dict):
142c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
143c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    Get sum of durations for special tasks.
144c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    job_info_dict will be modified in this function to store the duration
145c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    for each special task.
146c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
147c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param task_list: List of task id.
148c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param dut: Hostname of a DUT that the tasks ran on.
149c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param t_start: Beginning timestamp.
150c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param job_id: The job id that is related to the tasks.
151c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                   This is used only for debugging purpose.
152c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param job_info_dict: Dictionary that has information for jobs.
153c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @return: Sum of durations of the tasks.
154c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
15555bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black    t_start_epoch = time_utils.to_epoch_time(t_start)
156b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    results = autotest_es.query(
157c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            fields_returned=['status', 'task_id', 'duration'],
158c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            equality_constraints=[('_type', 'job_time_breakdown'),
159c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                                  ('hostname', dut)],
16055bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black            range_constraints=[('time_recorded', t_start_epoch, None)],
161c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            batch_constraints=[('task_id', task_list)])
162c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    sum = 0
163b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    for hit in results.hits:
164c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        sum += float(hit['duration'])
165c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        job_info_dict[job_id][hit['status']] = float(hit['duration'])
166c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        print_verbose('Task %s for Job %s took %s',
167c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                      hit['task_id'], job_id, hit['duration'])
168c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    return sum
169c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
170c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
171c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryudef get_job_runtime(job_id, dut, job_info_dict):
172c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
173c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    Get sum of durations for the entries that are related to a job.
174c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    job_info_dict will be modified in this function.
175c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
176c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param job_id: The job id of interest.
177c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param dut: Hostname of a DUT that the job ran on.
178c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param job_info_dict: Dictionary that has information for jobs.
179c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @return: Total duration taken by a job.
180c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
181c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    sum, t_last_queued = get_nontask_runtime(job_id, dut, job_info_dict)
182c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    print_verbose('Job %s took %f, last Queued: %s',
183c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                  job_id, sum, t_last_queued)
184c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    sum += get_tasks_runtime(
185c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            list(job_info_dict[job_id]['tasks']), dut, t_last_queued,
186c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            job_id, job_info_dict)
187c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    return sum
188c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
189c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
190c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryudef get_dut_overhead(dut, jobs, job_info_dict):
191c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
192c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    Calculates the scheduling overhead of a DUT.
193c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
194c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    The scheduling overhead of a DUT is defined by the sum of scheduling
195c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    overheads for the jobs that ran on the DUT.
196c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    The scheduling overhead for a job is defined by the difference
197c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    of net job runtime and real job runtime.
198c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    job_info_dict will be modified in this function.
199c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
200c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param dut: Hostname of a DUT.
201c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param jobs: The list of jobs that ran on the DUT.
202c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param job_info_dict: Dictionary that has information for jobs.
203c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @return: Scheduling overhead of a DUT in a floating point value.
204c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu             The unit is a second.
205c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
206c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    overheads = []
207c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    for job_id in jobs:
208c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        (t_start, t_end) = job_info_dict[job_id]['timestamps']
209c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        runtime = get_job_runtime(job_id, dut, job_info_dict)
210c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        overheads.append(t_end - t_start - runtime)
211c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        print_verbose('Job: %s, Net runtime: %f, Real runtime: %f, '
212c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                      'Overhead: %f', job_id, t_end - t_start, runtime,
213c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                      t_end - t_start - runtime)
214c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    return sum(overheads)
215c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
216c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
217c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryudef get_child_jobs_info(suite_job_id, num_child_jobs, sanity_check):
218c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
219c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    Gets information about child jobs of a suite.
220c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
221c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param suite_job_id: Job id of a suite.
222c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param num_child_jobs: Number of child jobs of the suite.
223c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param sanity_check: Do sanity check if True.
224c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @return: A tuple of (dictionary, list). For dictionary, the key is
225c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu             a DUT's hostname and the value is a list of jobs that ran on
226c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu             the DUT. List is the list of all jobs of the suite.
227c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
228b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    results = autotest_es.query(
229c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            fields_returned=['job_id', 'hostname'],
230c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            equality_constraints=[('_type', 'host_history'),
231c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                                  ('parent_job_id', suite_job_id),
232c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                                  ('status', 'Running'),])
233c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
234c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    dut_jobs_dict = {}
235c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    job_filter = set()
236b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    for hit in results.hits:
237c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        job_id = hit['job_id']
238c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        dut = hit['hostname']
239c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        if job_id in job_filter:
240c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            continue
241c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        job_list = dut_jobs_dict.setdefault(dut, [])
242c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        job_list.append(job_id)
243c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        job_filter.add(job_id)
244c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
245c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    if sanity_check and len(job_filter) != num_child_jobs:
246c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        print('WARNING: Mismatch number of child jobs of a suite (%d): '
247c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu              '%d != %d' % (suite_job_id, len(job_filter), num_child_jobs))
248c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    return dut_jobs_dict, list(job_filter)
249c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
250c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
251c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryudef get_job_timestamps(job_list, job_info_dict):
252c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
253c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    Get beginning time and ending time for each job.
254c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
255c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    The beginning time of a job is "queued_time" of "tko_jobs" table.
256c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    The ending time of a job is "finished_on" of "afe_host_queue_entries" table.
257c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    job_info_dict will be modified in this function to store the timestamps.
258c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
259c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param job_list: List of job ids
260c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param job_info_dict: Dictionary that timestamps for each job will be stored
261c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
262c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    tko = tko_models.Job.objects.filter(afe_job_id__in=job_list)
263c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    hqe = models.HostQueueEntry.objects.filter(job_id__in=job_list)
264c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    job_start = {}
265c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    for t in tko:
266c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        job_start[t.afe_job_id] = time_utils.to_epoch_time(t.queued_time)
267c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    job_end = {}
268c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    for h in hqe:
269c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        job_end[h.job_id] = time_utils.to_epoch_time(h.finished_on)
270c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
271c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    for job_id in job_list:
272c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        info_dict = job_info_dict.setdefault(job_id, {})
273c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        info_dict.setdefault('timestamps', (job_start[job_id], job_end[job_id]))
274c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
275c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
276c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryudef get_job_tasks(job_list, job_info_dict):
277c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
278c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    Get task ids for each job.
279c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    job_info_dict will be modified in this function to store the task ids.
280c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
281c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param job_list: List of job ids
282c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param job_info_dict: Dictionary that task ids for each job will be stored.
283c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
284b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    results = autotest_es.query(
285c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            fields_returned=['job_id', 'task_id'],
286c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            equality_constraints=[('_type', 'host_history')],
287c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            batch_constraints=[('job_id', job_list)])
288b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    for hit in results.hits:
289c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        if 'task_id' in hit:
290c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            info_dict = job_info_dict.setdefault(hit['job_id'], {})
291c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            task_set = info_dict.setdefault('tasks', set())
292c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            task_set.add(hit['task_id'])
293c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
294c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
295c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryudef get_scheduling_overhead(suite_job_id, num_child_jobs, sanity_check=True):
296c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
297c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    Calculates a scheduling overhead.
298c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
299c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    A scheduling overhead is defined by the average of DUT overheads
300c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    for the DUTs that the child jobs of a suite ran on.
301c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
302c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param suite_job_id: Job id of a suite.
303c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param num_child_jobs: Number of child jobs of the suite.
304c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param sanity_check: Do sanity check if True.
305c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @return: Dictionary storing stats.
306c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
307c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    dut_jobs_dict, job_list = get_child_jobs_info(
308c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            suite_job_id, num_child_jobs, sanity_check)
309c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    job_info_dict = {}
310c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    get_job_timestamps(job_list, job_info_dict)
311c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    get_job_tasks(job_list, job_info_dict)
312c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
313c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    dut_overheads = []
314c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    avg_overhead = 0
315c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    for dut, jobs in dut_jobs_dict.iteritems():
316c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        print_verbose('Dut: %s, Jobs: %s', dut, jobs)
317c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        overhead = get_dut_overhead(dut, jobs, job_info_dict)
318c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        avg_overhead += overhead
319c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        print_verbose('Dut overhead: %f', overhead)
320c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        dut_overheads.append(overhead)
321c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
322c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    if job_list:
323c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        avg_overhead = avg_overhead / len(job_list)
324c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
325c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    state_samples_dict = {}
326c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    for info in job_info_dict.itervalues():
327c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        for state in _states:
328c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            if state in info:
329c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                samples = state_samples_dict.setdefault(state, [])
330c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                samples.append(info[state])
331c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
332c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    if state_samples_dict:
333c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        result = {state: mean(state_samples_dict[state])
334c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                  if state in state_samples_dict else 0
335c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                  for state in _states}
336c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    result['suite_overhead'] = mean(dut_overheads)
337c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    result['overhead'] = avg_overhead
338c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    result['num_duts'] = len(dut_jobs_dict)
339c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    return result
340c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
341c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
342c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryudef print_suite_stats(suite_stats):
343c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """Prints out statistics for a suite to standard output."""
344c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    print('suite_overhead: %(suite_overhead)f, overhead: %(overhead)f,' %
345c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu          suite_stats),
346c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    for state in _states:
347c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        if state in suite_stats:
348c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            print('%s: %f,' % (state, suite_stats[state])),
349c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    print('num_duts: %(num_duts)d' % suite_stats)
350c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
351c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
352c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryudef analyze_suites(start_time, end_time):
353c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
354c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    Calculates timing stats (i.e., suite runtime, scheduling overhead)
355c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    for the suites that finished within the timestamps given by parameters.
356c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
357c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param start_time: Beginning timestamp.
358c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    @param end_time: Ending timestamp.
359c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """
360c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    print('Analyzing suites from %s to %s...' % (
361c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu          time_utils.epoch_time_to_date_string(start_time),
362c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu          time_utils.epoch_time_to_date_string(end_time)))
363c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
364c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    if _options.bvtonly:
365c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        batch_constraints = [
366c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                ('suite_name', ['bvt-inline', 'bvt-cq', 'bvt-perbuild'])]
367c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    else:
368c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        batch_constraints = []
369c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
37055bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black    start_time_epoch = time_utils.to_epoch_time(start_time)
37155bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black    end_time_epoch = time_utils.to_epoch_time(end_time)
372b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    results = autotest_es.query(
373c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            fields_returned=['suite_name', 'suite_job_id', 'board', 'build',
374c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                             'num_child_jobs', 'duration'],
375c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            equality_constraints=[('_type', job_overhead.SUITE_RUNTIME_KEY),],
37655bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black            range_constraints=[('time_recorded', start_time_epoch,
37755bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black                                end_time_epoch)],
378c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            sort_specs=[{'time_recorded': 'asc'}],
379c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            batch_constraints=batch_constraints)
380b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    print('Found %d suites' % (results.total))
381c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
382b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    for hit in results.hits:
383c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        suite_job_id = hit['suite_job_id']
384c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
385c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        try:
386c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            suite_name = hit['suite_name']
387c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            num_child_jobs = int(hit['num_child_jobs'])
388c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            suite_runtime = float(hit['duration'])
389c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
390c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            print('Suite: %s (%s), Board: %s, Build: %s, Num child jobs: %d' % (
391c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                    suite_name, suite_job_id, hit['board'], hit['build'],
392c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                    num_child_jobs))
393c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
394c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            suite_stats = get_scheduling_overhead(suite_job_id, num_child_jobs)
395c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            print('Suite: %s (%s) runtime: %f,' % (
396c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                    suite_name, suite_job_id, suite_runtime)),
397c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            print_suite_stats(suite_stats)
398c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
399c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            if _options.cron_mode:
40079bb42c9087b2a220bcba9b48e6dbc3438954613MK Ryu                key = utils.get_data_key(
401c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                        'suite_time_stats', suite_name, hit['build'],
402c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                        hit['board'])
4031e1c41b1b4a1b97c0b7086b8430856ed45e064d3Gabe Black                autotest_stats.Timer(key).send('suite_runtime', suite_runtime)
40479bb42c9087b2a220bcba9b48e6dbc3438954613MK Ryu                for stat, val in suite_stats.iteritems():
4051e1c41b1b4a1b97c0b7086b8430856ed45e064d3Gabe Black                    autotest_stats.Timer(key).send(stat, val)
406c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        except Exception as e:
407c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            print('ERROR: Exception is raised while processing suite %s' % (
408c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                    suite_job_id))
409c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            print e
410c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
411c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
412c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryudef analyze_suite(suite_job_id):
413c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    suite_stats = get_scheduling_overhead(suite_job_id, 0, False)
414c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    print('Suite (%s)' % suite_job_id),
415c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    print_suite_stats(suite_stats)
416c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
417c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
418c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryudef main():
419c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    """main script."""
420c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    parser = argparse.ArgumentParser(
421c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu            formatter_class=argparse.ArgumentDefaultsHelpFormatter)
422c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    parser.add_argument('-c', dest='cron_mode', action='store_true',
423c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                        help=('Run in a cron mode. Cron mode '
424c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                              'sends calculated stat data to Graphite.'),
425c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                        default=False)
426c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    parser.add_argument('-s', type=int, dest='span',
427c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                        help=('Number of hours that stats should be '
428c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                              'collected.'),
429c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                        default=1)
430c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    parser.add_argument('--bvtonly', dest='bvtonly', action='store_true',
431c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                        help=('Gets bvt suites only (i.e., bvt-inline,'
432c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                              'bvt-cq, bvt-perbuild).'),
433c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                        default=False)
434c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    parser.add_argument('--suite', type=int, dest='suite_job_id',
435c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                        help=('Job id of a suite.'))
436c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    parser.add_argument('--verbose', dest='verbose', action='store_true',
437c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                        help=('Prints out more info if True.'),
438c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu                        default=False)
439c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    global _options
440c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    _options = parser.parse_args()
441c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
442c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    if _options.suite_job_id:
443c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        analyze_suite(_options.suite_job_id)
444c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    else:
445c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        end_time = time_utils.to_epoch_time(datetime.now())
446c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        start_time = end_time - timedelta(hours=_options.span).total_seconds()
447c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu        analyze_suites(start_time, end_time)
448c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
449c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu
450c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryuif __name__ == '__main__':
451c9c0c3ff2e8800bfad3f0c79fe5879be0ec78489MK Ryu    main()
452