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