xref: /aosp_15_r20/external/autotest/utils/site_check_dut_usage.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li#!/usr/bin/python3
2*9c5db199SXin Li
3*9c5db199SXin Li# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
4*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
5*9c5db199SXin Li# found in the LICENSE file.
6*9c5db199SXin Li
7*9c5db199SXin Li"""
8*9c5db199SXin LiTool to check DUT usage by querying the Autotest DB.
9*9c5db199SXin Li
10*9c5db199SXin LiSample usage:
11*9c5db199SXin Li
12*9c5db199SXin Liutils/site_check_dut_usage.py 11/1/2011 11/5/2011 netbook_LABEL
13*9c5db199SXin Li"""
14*9c5db199SXin Li
15*9c5db199SXin Lifrom __future__ import absolute_import
16*9c5db199SXin Lifrom __future__ import division
17*9c5db199SXin Lifrom __future__ import print_function
18*9c5db199SXin Li
19*9c5db199SXin Liimport datetime
20*9c5db199SXin Liimport optparse
21*9c5db199SXin Liimport sys
22*9c5db199SXin Li
23*9c5db199SXin Liimport common
24*9c5db199SXin Lifrom autotest_lib.database import database_connection
25*9c5db199SXin Li
26*9c5db199SXin Li_DATE_FORMAT = '%m/%d/%Y'
27*9c5db199SXin Li
28*9c5db199SXin Li
29*9c5db199SXin Liclass CheckDutUsageRunner(object):
30*9c5db199SXin Li    """Checks DUT usage for given label or hostname during a time period."""
31*9c5db199SXin Li
32*9c5db199SXin Li    def __init__(self, start_time, end_time, label, hostname, list_hostnames):
33*9c5db199SXin Li        """
34*9c5db199SXin Li        Instantiates a CheckDUTUsageRunner.
35*9c5db199SXin Li
36*9c5db199SXin Li        @start_time: start date of time period we are interested in.
37*9c5db199SXin Li        @end_time: end date of time period we are interested in.  Note the
38*9c5db199SXin Li            time period is (start_date, end_date].
39*9c5db199SXin Li        @label: If not None, the platform label of the hostnames we are
40*9c5db199SXin Li            interested in.
41*9c5db199SXin Li        @hostname: If not None, the hostname we are intersted in.
42*9c5db199SXin Li        @list_hostnames: If set, print out the list of hostnames found that ran
43*9c5db199SXin Li            jobs during the given time period.
44*9c5db199SXin Li        """
45*9c5db199SXin Li        self._start_time = start_time
46*9c5db199SXin Li        self._end_time = end_time
47*9c5db199SXin Li        self._list_hostnames = list_hostnames
48*9c5db199SXin Li        self._label = label
49*9c5db199SXin Li        self._hostname = hostname
50*9c5db199SXin Li        self._database_connection = None
51*9c5db199SXin Li
52*9c5db199SXin Li
53*9c5db199SXin Li    def find_all_durations(self):
54*9c5db199SXin Li        """
55*9c5db199SXin Li        Returns all list of tuples containing durations.
56*9c5db199SXin Li
57*9c5db199SXin Li        A duration is a 4-tuple containing |queued_time|, |started_time|,
58*9c5db199SXin Li        |finished_time|, |hostname|.
59*9c5db199SXin Li        """
60*9c5db199SXin Li        query = ('select queued_time, started_time, finished_time, '
61*9c5db199SXin Li                 '  hostname '
62*9c5db199SXin Li                 'from tko_jobs left join tko_machines on '
63*9c5db199SXin Li                 '  tko_jobs.machine_idx=tko_machines.machine_idx '
64*9c5db199SXin Li                 'where tko_jobs.started_time>=DATE(%s) and '
65*9c5db199SXin Li                 '  tko_jobs.finished_time<DATE(%s)')
66*9c5db199SXin Li        if self._label:
67*9c5db199SXin Li            query += ' and tko_machines.machine_group=%s'
68*9c5db199SXin Li            filter_value = self._label
69*9c5db199SXin Li        else:
70*9c5db199SXin Li            query += ' and tko_machines.hostname=%s'
71*9c5db199SXin Li            filter_value = self._hostname
72*9c5db199SXin Li
73*9c5db199SXin Li        results = self._database_connection.execute(
74*9c5db199SXin Li                query, [self._start_time, self._end_time, filter_value])
75*9c5db199SXin Li        return results
76*9c5db199SXin Li
77*9c5db199SXin Li
78*9c5db199SXin Li    @staticmethod
79*9c5db199SXin Li    def _total_seconds(time_delta):
80*9c5db199SXin Li        """
81*9c5db199SXin Li        Returns a float that has the total seconds in a datetime.timedelta.
82*9c5db199SXin Li        """
83*9c5db199SXin Li        return float(time_delta.days * 86400 + time_delta.seconds)
84*9c5db199SXin Li
85*9c5db199SXin Li
86*9c5db199SXin Li    def calculate_usage(self, durations):
87*9c5db199SXin Li        """
88*9c5db199SXin Li        Calculates and prints out usage information given list of durations.
89*9c5db199SXin Li        """
90*9c5db199SXin Li        total_run_time = datetime.timedelta()
91*9c5db199SXin Li        total_queued_time = datetime.timedelta()
92*9c5db199SXin Li        machines = set()
93*9c5db199SXin Li        for q_time, s_time, f_time, machine in durations:
94*9c5db199SXin Li            total_run_time += f_time - s_time
95*9c5db199SXin Li            total_queued_time += s_time - q_time
96*9c5db199SXin Li            machines.add(machine)
97*9c5db199SXin Li
98*9c5db199SXin Li        num_machines = len(machines)
99*9c5db199SXin Li        avg_run_time = total_run_time / num_machines
100*9c5db199SXin Li        avg_job_run_time = self._total_seconds(total_run_time) / len(durations)
101*9c5db199SXin Li        avg_job_queued_time = (self._total_seconds(total_queued_time) /
102*9c5db199SXin Li                               len(durations))
103*9c5db199SXin Li        duration = self._end_time - self._start_time
104*9c5db199SXin Li        usage = self._total_seconds(avg_run_time) / self._total_seconds(
105*9c5db199SXin Li                duration)
106*9c5db199SXin Li
107*9c5db199SXin Li        # Print the list of hostnames if the user requested.
108*9c5db199SXin Li        if self._list_hostnames:
109*9c5db199SXin Li            print('==================================================')
110*9c5db199SXin Li            print('Machines with label:')
111*9c5db199SXin Li            for machine in machines:
112*9c5db199SXin Li                print(machine)
113*9c5db199SXin Li            print('==================================================')
114*9c5db199SXin Li
115*9c5db199SXin Li        # Print the usage summary.
116*9c5db199SXin Li        print('==================================================')
117*9c5db199SXin Li        print('Total running time', total_run_time)
118*9c5db199SXin Li        print('Total queued time', total_queued_time)
119*9c5db199SXin Li        print('Total number of machines', num_machines)
120*9c5db199SXin Li        print('Average time spent running tests per machine ', avg_run_time)
121*9c5db199SXin Li        print('Average Job Time ', datetime.timedelta(seconds=int(
122*9c5db199SXin Li                avg_job_run_time)))
123*9c5db199SXin Li        print('Average Time Job Queued ', datetime.timedelta(seconds=int(
124*9c5db199SXin Li                avg_job_queued_time)))
125*9c5db199SXin Li        print('Total duration ', duration)
126*9c5db199SXin Li        print('Usage ', usage)
127*9c5db199SXin Li        print('==================================================')
128*9c5db199SXin Li
129*9c5db199SXin Li
130*9c5db199SXin Li    def run(self):
131*9c5db199SXin Li        """Connects to SQL DB and calculates DUT usage given args."""
132*9c5db199SXin Li        # Force the database connection to use the read the readonly options.
133*9c5db199SXin Li        database_connection._GLOBAL_CONFIG_NAMES.update(
134*9c5db199SXin Li                {'username': 'readonly_user',
135*9c5db199SXin Li                 'password': 'readonly_password',
136*9c5db199SXin Li                })
137*9c5db199SXin Li        self._database_connection = database_connection.DatabaseConnection(
138*9c5db199SXin Li                global_config_section='AUTOTEST_WEB')
139*9c5db199SXin Li        self._database_connection.connect()
140*9c5db199SXin Li
141*9c5db199SXin Li        durations = self.find_all_durations()
142*9c5db199SXin Li        if not durations:
143*9c5db199SXin Li            print('Query returned no results.')
144*9c5db199SXin Li        else:
145*9c5db199SXin Li            self.calculate_usage(durations)
146*9c5db199SXin Li
147*9c5db199SXin Li        self._database_connection.disconnect()
148*9c5db199SXin Li
149*9c5db199SXin Li
150*9c5db199SXin Lidef parse_args(options, args, parser):
151*9c5db199SXin Li    """Returns a tuple containing start time, end time, and label, hostname."""
152*9c5db199SXin Li    label, hostname = None, None
153*9c5db199SXin Li
154*9c5db199SXin Li    if len(args) != 4:
155*9c5db199SXin Li        parser.error('Should have exactly 3 arguments.')
156*9c5db199SXin Li
157*9c5db199SXin Li    if options.hostname:
158*9c5db199SXin Li        hostname = args[-1]
159*9c5db199SXin Li    else:
160*9c5db199SXin Li        label = args[-1]
161*9c5db199SXin Li
162*9c5db199SXin Li    start_time, end_time = args[1:3]
163*9c5db199SXin Li    return (datetime.datetime.strptime(start_time, _DATE_FORMAT).date(),
164*9c5db199SXin Li            datetime.datetime.strptime(end_time, _DATE_FORMAT).date(),
165*9c5db199SXin Li            label, hostname)
166*9c5db199SXin Li
167*9c5db199SXin Li
168*9c5db199SXin Lidef main(argv):
169*9c5db199SXin Li    """Main method.  Parses options and runs main program."""
170*9c5db199SXin Li    usage = ('usage: %prog [options] start_date end_date platform_Label|'
171*9c5db199SXin Li             'hostname')
172*9c5db199SXin Li    parser = optparse.OptionParser(usage=usage)
173*9c5db199SXin Li    parser.add_option('--hostname', action='store_true', default=False,
174*9c5db199SXin Li                      help='If set, interpret argument as hostname.')
175*9c5db199SXin Li    parser.add_option('--list', action='store_true', default=False,
176*9c5db199SXin Li                      help='If set, print out list of hostnames with '
177*9c5db199SXin Li                      'the given label that ran jobs during this time.')
178*9c5db199SXin Li    options, args = parser.parse_args(argv)
179*9c5db199SXin Li
180*9c5db199SXin Li    start_time, end_time, label, hostname = parse_args(options, args, parser)
181*9c5db199SXin Li    runner = CheckDutUsageRunner(start_time, end_time, label, hostname,
182*9c5db199SXin Li                                 options.list)
183*9c5db199SXin Li    runner.run()
184*9c5db199SXin Li
185*9c5db199SXin Li
186*9c5db199SXin Liif __name__ == '__main__':
187*9c5db199SXin Li    main(sys.argv)
188