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