1#!/usr/bin/env python
2
3# Copyright (c) 2014 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"""This script is to be run daily to report machine utilization stats across
8each board and pool.
9"""
10
11
12import argparse
13from datetime import date
14from datetime import datetime
15from datetime import timedelta
16
17import common
18from autotest_lib.client.common_lib import time_utils
19from autotest_lib.client.common_lib.cros.graphite import autotest_stats
20from autotest_lib.site_utils import gmail_lib
21from autotest_lib.site_utils import host_history
22from autotest_lib.site_utils import host_history_utils
23from autotest_lib.site_utils import host_label_utils
24
25
26def report_stats(board, pool, start_time, end_time, span):
27    """Report machine stats for given board, pool and time period.
28
29    @param board: Name of board.
30    @param pool: Name of pool.
31    @param start_time: start time to collect stats.
32    @param end_time: end time to collect stats.
33    @param span: Number of hours that the stats should be collected for.
34    @return: Error message collected when calculating the stats.
35    """
36    print '================ %-12s %-12s ================' % (board, pool)
37    try:
38        history = host_history.get_history_details(start_time=start_time,
39                                                   end_time=end_time,
40                                                   board=board,
41                                                   pool=pool)
42    except host_history_utils.NoHostFoundException as e:
43        print 'No history found. Error:\n%s' % e
44        history = None
45        mur = -1
46        mar = -1
47        mir = -1
48
49    if history:
50        status_intervals = host_history_utils.get_status_intervals(history)
51        stats_all, num_hosts = host_history_utils.aggregate_hosts(
52                status_intervals)
53        total = 0
54        total_time = span*3600*num_hosts
55        for status, interval in stats_all.iteritems():
56            total += interval
57        if abs(total - total_time) > 10:
58            error = ('Status intervals do not add up. No stats will be '
59                     'collected for board: %s, pool: %s, diff: %s' %
60                     (board, pool, total - total_time))
61            hosts = []
62            for history_for_host in status_intervals:
63                total = 0
64                for interval in history_for_host.keys():
65                    total += interval[1] - interval[0]
66                if total > span*3600:
67                    hosts.append(history_for_host.values()[0]['metadata']['hostname'])
68            error += ' hosts: %s' % ','.join(hosts)
69            print error
70            return error
71
72        mur = host_history_utils.get_machine_utilization_rate(stats_all)
73        mar = host_history_utils.get_machine_availability_rate(stats_all)
74        mir = mar - mur
75
76        for status, interval in stats_all.iteritems():
77            print '%-18s %-16s %-10.2f%%' % (status, interval,
78                                             100*interval/total_time)
79        print 'Machine utilization rate  = %-4.2f%%' % (100*mur)
80        print 'Machine availability rate = %-4.2f%%' % (100*mar)
81
82    autotest_stats.Gauge('machine_utilization_rate').send('%s_hours.%s.%s' %
83                                                          (span, board, pool),
84                                                          mur)
85    autotest_stats.Gauge('machine_availability_rate').send('%s_hours.%s.%s' %
86                                                           (span, board, pool),
87                                                           mar)
88    autotest_stats.Gauge('machine_idle_rate').send('%s_hours.%s.%s' %
89                                                   (span, board, pool), mir)
90
91
92def main():
93    """main script. """
94    parser = argparse.ArgumentParser()
95    parser.add_argument('--span', type=int, dest='span', default=1,
96                        help=('Number of hours that stats should be collected. '
97                              'If it is set to 24, the end time of stats being '
98                              'collected will set to the mid of the night. '
99                              'Default is set to 1 hour.'))
100    parser.add_argument('-e', '--email', dest='email', default=None,
101                        help='Email any errors to the given email address.')
102    options = parser.parse_args()
103
104    boards = host_label_utils.get_all_boards()
105    pools = ['bvt', 'suites', 'cq']
106
107    if options.span == 24:
108        today = datetime.combine(date.today(), datetime.min.time())
109        end_time = time_utils.to_epoch_time(today)
110    else:
111        now = datetime.now()
112        end_time = datetime(year=now.year, month=now.month, day=now.day,
113                            hour=now.hour)
114        end_time = time_utils.to_epoch_time(end_time)
115
116    start_time = end_time - timedelta(hours=options.span).total_seconds()
117    print ('Collecting host stats from %s to %s...' %
118           (time_utils.epoch_time_to_date_string(start_time),
119            time_utils.epoch_time_to_date_string(end_time)))
120
121    errors = []
122    if not boards:
123        errors.append('Error! No board found in metadb.')
124    for board in boards:
125        for pool in pools:
126            error = report_stats(board, pool, start_time, end_time,
127                                 options.span)
128            if error:
129                errors.append(error)
130    if options.email and errors:
131        gmail_lib.send_email(options.email,
132                             'Error occured when collecting host stats.',
133                             '\n'.join(errors))
134
135
136if __name__ == '__main__':
137    main()
138