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