15952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
25952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang# Use of this source code is governed by a BSD-style license that can be
35952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang# found in the LICENSE file.
45952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
55952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang# This file contains utility functions for host_history.
65952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
75952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liangimport collections
81ccb65208c26615096985b5f8b52368f7342a077Dan Shiimport copy
97cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shiimport multiprocessing.pool
1017ecbbf63319dabd5514827a34f04b5a0b724352Dan Shifrom itertools import groupby
115952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
125952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liangimport common
13dfea368e5c830b1d7950ced5ee7b191e3b141ca3Dan Shifrom autotest_lib.client.common_lib import time_utils
14b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Blackfrom autotest_lib.client.common_lib.cros.graphite import autotest_es
155952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liangfrom autotest_lib.frontend import setup_django_environment
165952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liangfrom autotest_lib.frontend.afe import models
1717ecbbf63319dabd5514827a34f04b5a0b724352Dan Shifrom autotest_lib.site_utils import host_label_utils
181ccb65208c26615096985b5f8b52368f7342a077Dan Shifrom autotest_lib.site_utils import job_history
195952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
2017ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
2117ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi_HOST_HISTORY_TYPE = 'host_history'
2217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi_LOCK_HISTORY_TYPE = 'lock_history'
2317ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
247cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi# The maximum number of days that the script will lookup for history.
257cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi_MAX_DAYS_FOR_HISTORY = 90
261c3b0d1836ec77e92851d59a0fd24cdefdac44f3Dan Shi
271c3b0d1836ec77e92851d59a0fd24cdefdac44f3Dan Shiclass NoHostFoundException(Exception):
281c3b0d1836ec77e92851d59a0fd24cdefdac44f3Dan Shi    """Exception raised when no host is found to search for history.
291c3b0d1836ec77e92851d59a0fd24cdefdac44f3Dan Shi    """
301c3b0d1836ec77e92851d59a0fd24cdefdac44f3Dan Shi
311c3b0d1836ec77e92851d59a0fd24cdefdac44f3Dan Shi
3217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shidef get_matched_hosts(board, pool):
3317ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    """Get duts with matching board and pool labels from metaDB.
3417ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
3517ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param board: board of DUT, set to None if board doesn't need to match.
3617ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param pool: pool of DUT, set to None if pool doesn't need to match.
3717ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @return: A list of duts that match the specified board and pool.
3817ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    """
3917ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    labels = []
4017ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    if pool:
4117ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi        labels.append('pool:%s' % pool)
4217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    if board:
4317ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi        labels.append('board:%s' % board)
4417ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    host_labels = host_label_utils.get_host_labels(labels=labels)
4517ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    return host_labels.keys()
4617ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
4717ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
485952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liangdef prepopulate_dict(keys, value, extras=None):
495952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    """Creates a dictionary with val=value for each key.
505952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
515952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param keys: list of keys
525952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param value: the value of each entry in the dict.
535952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param extras: list of additional keys
545952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @returns: dictionary
555952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    """
565952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    result = collections.OrderedDict()
575952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    extra_keys = tuple(extras if extras else [])
585952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    for key in keys + extra_keys:
595952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        result[key] = value
605952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    return result
615952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
625952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
635952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liangdef lock_history_to_intervals(initial_lock_val, t_start, t_end, lock_history):
645952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    """Converts lock history into a list of intervals of locked times.
655952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
665952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param initial_lock_val: Initial value of the lock (False or True)
675952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param t_start: beginning of the time period we are interested in.
685952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param t_end: end of the time period we are interested in.
695952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param lock_history: Result of querying es for locks (dict)
705952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang           This dictionary should contain keys 'locked' and 'time_recorded'
715952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @returns: Returns a list of tuples where the elements of each tuples
725952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang           represent beginning and end of intervals of locked, respectively.
735952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    """
745952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    locked_intervals = []
755952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    t_prev = t_start
765952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    state_prev = initial_lock_val
77b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    for entry in lock_history.hits:
78f242c20641311794fbddd022d8dda076eb3e8ea6Dan Shi        t_curr = entry['time_recorded']
795952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
805952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        #If it is locked, then we put into locked_intervals
815952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        if state_prev:
825952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            locked_intervals.append((t_prev, t_curr))
835952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
845952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        # update vars
855952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        t_prev = t_curr
86f242c20641311794fbddd022d8dda076eb3e8ea6Dan Shi        state_prev = entry['locked']
875952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    if state_prev:
885952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        locked_intervals.append((t_prev, t_end))
895952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    return locked_intervals
905952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
915952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
9217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shidef find_most_recent_entry_before(t, type_str, hostname, fields):
935952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    """Returns the fields of the most recent entry before t.
945952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
955952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param t: time we are interested in.
965952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param type_str: _type in esdb, such as 'host_history' (string)
975952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param hostname: hostname of DUT (string)
985952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param fields: list of fields we are interested in
995952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @returns: time, field_value of the latest entry.
1005952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    """
1017cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi    # History older than 90 days are ignored. This helps the ES query faster.
10255bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black    t_epoch = time_utils.to_epoch_time(t)
103b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    result = autotest_es.query(
1045952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            fields_returned=fields,
1055952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            equality_constraints=[('_type', type_str),
1065952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang                                  ('hostname', hostname)],
1077cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi            range_constraints=[('time_recorded',
1087cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi                               t_epoch-3600*24*_MAX_DAYS_FOR_HISTORY, t_epoch)],
1095952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            size=1,
1105952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            sort_specs=[{'time_recorded': 'desc'}])
111b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    if result.total > 0:
112b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black        return result.hits[0]
1135952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    return {}
1145952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
1155952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
1167cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shidef get_host_history_intervals(input):
1175952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    """Gets stats for a host.
1185952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
11917ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    This method uses intervals found in metaDB to build a full history of the
12017ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    host. The intervals argument contains a list of metadata from querying ES
12117ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    for records between t_start and t_end. To get the status from t_start to
12217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    the first record logged in ES, we need to look back to the last record
12317ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    logged in ES before t_start.
12417ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
1257cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi    @param input: A dictionary of input args, which including following args:
1267cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi            t_start: beginning of time period we are interested in.
1277cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi            t_end: end of time period we are interested in.
1287cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi            hostname: hostname for the host we are interested in (string)
1297cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi            intervals: intervals from ES query.
1305952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @returns: dictionary, num_entries_found
1315952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        dictionary of status: time spent in that status
1325952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        num_entries_found: number of host history entries
1335952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang                           found in [t_start, t_end]
1345952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
1355952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    """
1367cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi    t_start = input['t_start']
1377cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi    t_end = input['t_end']
1387cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi    hostname = input['hostname']
1397cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi    intervals = input['intervals']
1405952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    lock_history_recent = find_most_recent_entry_before(
14117ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            t=t_start, type_str=_LOCK_HISTORY_TYPE, hostname=hostname,
14217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            fields=['time_recorded', 'locked'])
1435952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    # I use [0] and [None] because lock_history_recent's type is list.
1447cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi    t_lock = lock_history_recent.get('time_recorded', None)
1457cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi    t_lock_val = lock_history_recent.get('locked', None)
146b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    t_metadata = find_most_recent_entry_before(
14717ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            t=t_start, type_str=_HOST_HISTORY_TYPE, hostname=hostname,
14817ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            fields=None)
149b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    t_host = t_metadata.pop('time_recorded', None)
150b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    t_host_stat = t_metadata.pop('status', None)
151f242c20641311794fbddd022d8dda076eb3e8ea6Dan Shi    status_first = t_host_stat if t_host else 'Ready'
1525952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    t = min([t for t in [t_lock, t_host, t_start] if t])
1535952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
15455bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black    t_epoch = time_utils.to_epoch_time(t)
15555bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black    t_end_epoch = time_utils.to_epoch_time(t_end)
156b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    lock_history_entries = autotest_es.query(
1575952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            fields_returned=['locked', 'time_recorded'],
15817ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            equality_constraints=[('_type', _LOCK_HISTORY_TYPE),
1595952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang                                  ('hostname', hostname)],
16055bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black            range_constraints=[('time_recorded', t_epoch, t_end_epoch)],
1615952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            sort_specs=[{'time_recorded': 'asc'}])
1625952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
1635d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi    # Validate lock history. If an unlock event failed to be recorded in metadb,
1645d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi    # lock history will show the dut being locked while host still has status
1655d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi    # changed over the time. This check tries to remove the lock event in lock
1665d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi    # history if:
1675d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi    # 1. There is only one entry in lock_history_entries (it's a good enough
1685d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi    #    assumption to avoid the code being over complicated.
1695d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi    # 2. The host status has changes after the lock history starts as locked.
1705d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi    if (len(lock_history_entries.hits) == 1 and t_lock_val and
1715d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi        len(intervals) >1):
1725d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi        locked_intervals = None
1735d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi        print ('Lock history of dut %s is ignored, the dut may have missing '
1745d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi               'data in lock history in metadb. Try to lock and unlock the dut '
1755d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi               'in AFE will force the lock history to be updated in metadb.'
1765d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi               % hostname)
1775d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi    else:
1785d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi        locked_intervals = lock_history_to_intervals(t_lock_val, t, t_end,
1795d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi                                                     lock_history_entries)
18017ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    num_entries_found = len(intervals)
1815952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    t_prev = t_start
1825952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    status_prev = status_first
1837cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi    metadata_prev = t_metadata
1845952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    intervals_of_statuses = collections.OrderedDict()
1855952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
18617ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    for entry in intervals:
187b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black        metadata = entry.copy()
188b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black        t_curr = metadata.pop('time_recorded')
189b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black        status_curr = metadata.pop('status')
1907cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi        intervals_of_statuses.update(calculate_status_times(
1917cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi                t_prev, t_curr, status_prev, metadata_prev, locked_intervals))
1925952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        # Update vars
1935952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        t_prev = t_curr
1945952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        status_prev = status_curr
1957cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi        metadata_prev = metadata
1965952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
1975952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    # Do final as well.
1987cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi    intervals_of_statuses.update(calculate_status_times(
1997cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi            t_prev, t_end, status_prev, metadata_prev, locked_intervals))
2007cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi    return hostname, intervals_of_statuses, num_entries_found
2015952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
2025952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
2035952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liangdef calculate_total_times(intervals_of_statuses):
2045952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    """Calculates total times in each status.
2055952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
2065952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param intervals_of_statuses: ordereddict where key=(ti, tf) and val=status
2075952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @returns: dictionary where key=status value=time spent in that status
2085952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    """
2095952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    total_times = prepopulate_dict(models.Host.Status.names, 0.0,
2105952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang                                   extras=['Locked'])
2115952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    for key, status_info in intervals_of_statuses.iteritems():
2125952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        ti, tf = key
2135952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        total_times[status_info['status']] += tf - ti
2145952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    return total_times
2155952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
2165952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
2171ccb65208c26615096985b5f8b52368f7342a077Dan Shidef aggregate_hosts(intervals_of_statuses_list):
21893b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    """Aggregates history of multiple hosts
21993b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang
22093b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    @param intervals_of_statuses_list: A list of dictionaries where keys
2217cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi        are tuple (ti, tf), and value is the status along with other metadata.
22293b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    @returns: A dictionary where keys are strings, e.g. 'status' and
22393b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang              value is total time spent in that status among all hosts.
22493b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    """
22593b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    stats_all = prepopulate_dict(models.Host.Status.names, 0.0,
22693b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang                                 extras=['Locked'])
22793b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    num_hosts = len(intervals_of_statuses_list)
22893b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    for intervals_of_statuses in intervals_of_statuses_list:
22993b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang        total_times = calculate_total_times(intervals_of_statuses)
23093b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang        for status, delta in total_times.iteritems():
23193b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang            stats_all[status] += delta
23293b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    return stats_all, num_hosts
23393b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang
23493b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang
23593b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liangdef get_stats_string_aggregate(labels, t_start, t_end, aggregated_stats,
23693b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang                               num_hosts):
23793b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    """Returns string reporting overall host history for a group of hosts.
23893b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang
23993b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    @param labels: A list of labels useful for describing the group
24093b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang                   of hosts these overall stats represent.
24193b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    @param t_start: beginning of time period we are interested in.
24293b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    @param t_end: end of time period we are interested in.
24393b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    @param aggregated_stats: A dictionary where keys are string, e.g. 'status'
24493b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang        value is total time spent in that status among all hosts.
24593b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    @returns: string representing the aggregate stats report.
24693b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    """
24793b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    result = 'Overall stats for hosts: %s \n' % (', '.join(labels))
248dfea368e5c830b1d7950ced5ee7b191e3b141ca3Dan Shi    result += ' %s - %s \n' % (time_utils.epoch_time_to_date_string(t_start),
249dfea368e5c830b1d7950ced5ee7b191e3b141ca3Dan Shi                               time_utils.epoch_time_to_date_string(t_end))
25093b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    result += ' Number of total hosts: %s \n' % (num_hosts)
25193b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    # This is multiplied by time_spent to get percentage_spent
25293b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    multiplication_factor = 100.0 / ((t_end - t_start) * num_hosts)
25393b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    for status, time_spent in aggregated_stats.iteritems():
25493b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang        # Normalize by the total time we are interested in among ALL hosts.
25593b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang        spaces = ' ' * (15 - len(status))
25693b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang        percent_spent = multiplication_factor * time_spent
25793b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang        result += '    %s: %s %.2f %%\n' % (status, spaces, percent_spent)
25893b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    result += '- -- --- ---- ----- ---- --- -- -\n'
25993b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    return result
26093b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang
26193b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang
26293b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liangdef get_overall_report(label, t_start, t_end, intervals_of_statuses_list):
26393b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    """Returns string reporting overall host history for a group of hosts.
26493b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang
26593b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    @param label: A string that can be useful for showing what type group
26693b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang        of hosts these overall stats represent.
26793b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    @param t_start: beginning of time period we are interested in.
26893b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    @param t_end: end of time period we are interested in.
26993b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    @param intervals_of_statuses_list: A list of dictionaries where keys
2707cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi        are tuple (ti, tf), and value is the status along with other metadata,
2717cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi        e.g., task_id, task_name, job_id etc.
27293b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    """
2731ccb65208c26615096985b5f8b52368f7342a077Dan Shi    stats_all, num_hosts = aggregate_hosts(
27493b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang            intervals_of_statuses_list)
27593b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    return get_stats_string_aggregate(
2761ccb65208c26615096985b5f8b52368f7342a077Dan Shi            label, t_start, t_end, stats_all, num_hosts)
27793b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang
27893b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang
27917ecbbf63319dabd5514827a34f04b5a0b724352Dan Shidef get_intervals_for_host(t_start, t_end, hostname):
28017ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    """Gets intervals for the given.
28117ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
28217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    Query metaDB to return all intervals between given start and end time.
28317ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    Note that intervals found in metaDB may miss the history from t_start to
28417ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    the first interval found.
28517ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
28617ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param t_start: beginning of time period we are interested in.
28717ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param t_end: end of time period we are interested in.
28817ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param hosts: A list of hostnames to look for history.
28917ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param board: Name of the board to look for history. Default is None.
29017ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param pool: Name of the pool to look for history. Default is None.
29117ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @returns: A dictionary of hostname: intervals.
29217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    """
29355bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black    t_start_epoch = time_utils.to_epoch_time(t_start)
29455bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black    t_end_epoch = time_utils.to_epoch_time(t_end)
295b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black    host_history_entries = autotest_es.query(
29617ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi                fields_returned=None,
29717ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi                equality_constraints=[('_type', _HOST_HISTORY_TYPE),
29817ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi                                      ('hostname', hostname)],
29955bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black                range_constraints=[('time_recorded', t_start_epoch,
30055bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black                                    t_end_epoch)],
30117ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi                sort_specs=[{'time_recorded': 'asc'}])
302f242c20641311794fbddd022d8dda076eb3e8ea6Dan Shi    return host_history_entries.hits
30317ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
30417ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
30517ecbbf63319dabd5514827a34f04b5a0b724352Dan Shidef get_intervals_for_hosts(t_start, t_end, hosts=None, board=None, pool=None):
30617ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    """Gets intervals for given hosts or board/pool.
30717ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
30817ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    Query metaDB to return all intervals between given start and end time.
30917ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    If a list of hosts is provided, the board and pool constraints are ignored.
31017ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    If hosts is set to None, and board or pool is set, this method will attempt
31117ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    to search host history with labels for all hosts, to help the search perform
31217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    faster.
31317ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    If hosts, board and pool are all set to None, return intervals for all
31417ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    hosts.
31517ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    Note that intervals found in metaDB may miss the history from t_start to
31617ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    the first interval found.
31717ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
31817ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param t_start: beginning of time period we are interested in.
31917ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param t_end: end of time period we are interested in.
32017ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param hosts: A list of hostnames to look for history.
32117ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param board: Name of the board to look for history. Default is None.
32217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param pool: Name of the pool to look for history. Default is None.
32317ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @returns: A dictionary of hostname: intervals.
32417ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    """
32517ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    hosts_intervals = {}
32617ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    if hosts:
32717ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi        for host in hosts:
32817ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            hosts_intervals[host] = get_intervals_for_host(t_start, t_end, host)
32917ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    else:
33017ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi        hosts = get_matched_hosts(board, pool)
33117ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi        if not hosts:
3321c3b0d1836ec77e92851d59a0fd24cdefdac44f3Dan Shi            raise NoHostFoundException('No host is found for board:%s, pool:%s.'
3331c3b0d1836ec77e92851d59a0fd24cdefdac44f3Dan Shi                                       % (board, pool))
33417ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi        equality_constraints=[('_type', _HOST_HISTORY_TYPE),]
33517ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi        if board:
33617ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            equality_constraints.append(('labels', 'board:'+board))
33717ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi        if pool:
33817ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            equality_constraints.append(('labels', 'pool:'+pool))
33955bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black        t_start_epoch = time_utils.to_epoch_time(t_start)
34055bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black        t_end_epoch = time_utils.to_epoch_time(t_end)
341b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black        results =  autotest_es.query(
34217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi                equality_constraints=equality_constraints,
34355bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black                range_constraints=[('time_recorded', t_start_epoch,
34455bfe14a6c2cc2710593ecf4d461af64181915c0Gabe Black                                    t_end_epoch)],
34517ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi                sort_specs=[{'hostname': 'asc'}])
34617ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi        results_group_by_host = {}
347b72f4fbcf1583da27f09f4abb9d8162530bf4559Gabe Black        for hostname,intervals_for_host in groupby(results.hits,
34817ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi                                                   lambda h: h['hostname']):
34917ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            results_group_by_host[hostname] = intervals_for_host
35017ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi        for host in hosts:
35117ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            intervals = results_group_by_host.get(host, None)
35217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            # In case the host's board or pool label was modified after
35317ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            # the last status change event was reported, we need to run a
35417ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            # separate query to get its history. That way the host's
35517ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            # history won't be shown as blank.
35617ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            if not intervals:
35717ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi                intervals = get_intervals_for_host(t_start, t_end, host)
35817ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            hosts_intervals[host] = intervals
35917ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    return hosts_intervals
36017ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
36117ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
36217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shidef get_report(t_start, t_end, hosts=None, board=None, pool=None,
36317ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi                print_each_interval=False):
36417ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    """Gets history for given hosts or board/pool
36517ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
36617ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    If a list of hosts is provided, the board and pool constraints are ignored.
36717ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
36817ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param t_start: beginning of time period we are interested in.
36917ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param t_end: end of time period we are interested in.
37017ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param hosts: A list of hostnames to look for history.
37117ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param board: Name of the board to look for history. Default is None.
37217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param pool: Name of the pool to look for history. Default is None.
37317ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @param print_each_interval: True display all intervals, default is False.
37417ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    @returns: stats report for this particular host. The report is a list of
37517ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi              tuples (stat_string, intervals, hostname), intervals is a sorted
37617ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi              dictionary.
37717ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    """
37817ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    if hosts:
37917ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi        board=None
38017ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi        pool=None
38117ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
38217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    hosts_intervals = get_intervals_for_hosts(t_start, t_end, hosts, board,
38317ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi                                              pool)
38417ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    history = {}
385c1a81e55c53729df443180f1763423218dbb5706Dan Shi    pool = multiprocessing.pool.ThreadPool(processes=16)
3867cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi    args = []
38717ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    for hostname,intervals in hosts_intervals.items():
3887cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi        args.append({'t_start': t_start,
3897cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi                     't_end': t_end,
3907cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi                     'hostname': hostname,
3917cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi                     'intervals': intervals})
3927cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi    results = pool.imap_unordered(get_host_history_intervals, args)
3937cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi    for hostname, intervals, count in results:
3947cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi        history[hostname] = (intervals, count)
39517ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    report = []
39617ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    for hostname,intervals in history.items():
39717ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi        total_times = calculate_total_times(intervals[0])
39817ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi        stats = get_stats_string(
39917ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi                t_start, t_end, total_times, intervals[0], hostname,
40017ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi                intervals[1], print_each_interval)
40117ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi        report.append((stats, intervals[0], hostname))
40217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    return report
40317ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
40417ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi
40517ecbbf63319dabd5514827a34f04b5a0b724352Dan Shidef get_report_for_host(t_start, t_end, hostname, print_each_interval):
40693b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    """Gets stats report for a host
40793b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang
40893b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    @param t_start: beginning of time period we are interested in.
40993b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    @param t_end: end of time period we are interested in.
41093b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    @param hostname: hostname for the host we are interested in (string)
41193b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    @param print_each_interval: True or False, whether we want to
41293b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang                                display all intervals
41393b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    @returns: stats report for this particular host (string)
41493b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    """
41517ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    # Search for status change intervals during given time range.
41617ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    intervals = get_intervals_for_host(t_start, t_end, hostname)
41717ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    num_entries_found = len(intervals)
41817ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    # Update the status change intervals with status before the first entry and
41917ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi    # host's lock history.
4207cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi    _, intervals_of_statuses = get_host_history_intervals(
4217cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi            {'t_start': t_start,
4227cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi             't_end': t_end,
4237cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi             'hostname': hostname,
4247cde53d430e208139638ee65116c0cd1ae7a7a36Dan Shi             'intervals': intervals})
42593b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    total_times = calculate_total_times(intervals_of_statuses)
42693b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang    return (get_stats_string(
42793b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang                    t_start, t_end, total_times, intervals_of_statuses,
42893b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang                    hostname, num_entries_found, print_each_interval),
42993b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang                    intervals_of_statuses)
43093b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang
43193b4ba492c4af35fa025e3f995904fdeb8dbf7c4Michael Liang
4325952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liangdef get_stats_string(t_start, t_end, total_times, intervals_of_statuses,
4335952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang                     hostname, num_entries_found, print_each_interval):
4345952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    """Returns string reporting host_history for this host.
4355952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param t_start: beginning of time period we are interested in.
4365952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param t_end: end of time period we are interested in.
4375952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param total_times: dictionary where key=status,
4385952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang                        value=(time spent in that status)
4395952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param intervals_of_statuses: dictionary where keys is tuple (ti, tf),
4407cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi              and value is the status along with other metadata.
4415952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param hostname: hostname for the host we are interested in (string)
4425952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param num_entries_found: Number of entries found for the host in es
4435952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @param print_each_interval: boolean, whether to print each interval
4445952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    """
4455952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    delta = t_end - t_start
4465952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    result = 'usage stats for host: %s \n' % (hostname)
447dfea368e5c830b1d7950ced5ee7b191e3b141ca3Dan Shi    result += ' %s - %s \n' % (time_utils.epoch_time_to_date_string(t_start),
448dfea368e5c830b1d7950ced5ee7b191e3b141ca3Dan Shi                               time_utils.epoch_time_to_date_string(t_end))
4495952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    result += ' Num entries found in this interval: %s\n' % (num_entries_found)
4505952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    for status, value in total_times.iteritems():
4515952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        spaces = (15 - len(status)) * ' '
4525952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        result += '    %s: %s %.2f %%\n' % (status, spaces, 100*value/delta)
4535952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    result += '- -- --- ---- ----- ---- --- -- -\n'
4545952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    if print_each_interval:
4555952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        for interval, status_info in intervals_of_statuses.iteritems():
4565952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            t0, t1 = interval
457dfea368e5c830b1d7950ced5ee7b191e3b141ca3Dan Shi            t0_string = time_utils.epoch_time_to_date_string(t0)
458dfea368e5c830b1d7950ced5ee7b191e3b141ca3Dan Shi            t1_string = time_utils.epoch_time_to_date_string(t1)
4595952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            status = status_info['status']
4605952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            delta = int(t1-t0)
46117ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            id_info = status_info['metadata'].get(
46217ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi                    'task_id', status_info['metadata'].get('job_id', ''))
46317ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi            result += ('    %s  :  %s %-15s %-10s %ss\n' %
46417ecbbf63319dabd5514827a34f04b5a0b724352Dan Shi                       (t0_string, t1_string, status, id_info, delta))
4655952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    return result
4665952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
4675952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
4687cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shidef calculate_status_times(t_start, t_end, int_status, metadata,
4697cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi                           locked_intervals):
4705952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    """Returns a list of intervals along w/ statuses associated with them.
4715952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
472f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi    If the dut is in status Ready, i.e., int_status==Ready, the lock history
473f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi    should be applied so that the time period when dut is locked is considered
474f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi    as not available. Any other status is considered that dut is doing something
475f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi    and being used. `Repair Failed` and Repairing are not checked with lock
476f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi    status, since these two statuses indicate the dut is not available any way.
477f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi
4787cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi    @param t_start: start time
4797cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi    @param t_end: end time
4807cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi    @param int_status: status of [t_start, t_end] if not locked
4817cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi    @param metadata: metadata of the status change, e.g., task_id, task_name.
4827cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi    @param locked_intervals: list of tuples denoting intervals of locked states
4835952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    @returns: dictionary where key = (t_interval_start, t_interval_end),
4847cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi                               val = (status, metadata)
4855952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang              t_interval_start: beginning of interval for that status
4865952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang              t_interval_end: end of the interval for that status
4875952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang              status: string such as 'Repair Failed', 'Locked', etc.
4887cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi              metadata: A dictionary of metadata, e.g.,
4897cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi                              {'task_id':123, 'task_name':'Reset'}
4905952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    """
4915952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    statuses = collections.OrderedDict()
4925952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
4937cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi    prev_interval_end = t_start
4945952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang
4955952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    # TODO: Put allow more information here in info/locked status
4965952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    status_info = {'status': int_status,
4977cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi                   'metadata': metadata}
4985952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    locked_info = {'status': 'Locked',
4997cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi                   'metadata': {}}
5005d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi    if not locked_intervals:
5017cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi        statuses[(t_start, t_end)] = status_info
5025952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        return statuses
5035952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang    for lock_start, lock_end in locked_intervals:
5045d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi        if prev_interval_end >= t_end:
5055d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi            break
5067cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi        if lock_start > t_end:
5075952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            # optimization to break early
5085952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            # case 0
5097cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi            # Timeline of status change: t_start t_end
5107cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi            # Timeline of lock action:                   lock_start lock_end
5115952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            break
512f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi        elif lock_end < prev_interval_end:
5135952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            # case 1
514f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi            #                      prev_interval_end    t_end
5157cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi            # lock_start lock_end
5165952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            continue
517f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi        elif lock_end <= t_end and lock_start >= prev_interval_end:
5185952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            # case 2
519f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi            # prev_interval_end                       t_end
520f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi            #                    lock_start lock_end
521f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi            # Lock happened in the middle, while the host stays in the same
522f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi            # status, consider the lock has no effect on host history.
5235d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi            statuses[(prev_interval_end, lock_end)] = status_info
524f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi            prev_interval_end = lock_end
525f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi        elif lock_end > prev_interval_end and lock_start < prev_interval_end:
5265952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            # case 3
527f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi            #             prev_interval_end          t_end
5285d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi            # lock_start                    lock_end        (or lock_end)
529f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi            # If the host status changed in the middle of being locked, consider
530f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi            # the new status change as part of the host history.
5315d53f70f710af3d552fe1f97720471973a98cbc9Dan Shi            statuses[(prev_interval_end, min(lock_end, t_end))] = locked_info
532f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi            prev_interval_end = lock_end
5337cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi        elif lock_start < t_end and lock_end > t_end:
5345952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang            # case 4
535f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi            # prev_interval_end             t_end
536f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi            #                    lock_start        lock_end
537f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi            # If the lock happens in the middle of host status change, consider
538f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi            # the lock has no effect on the host history for that status.
539f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi            statuses[(prev_interval_end, t_end)] = status_info
5407cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi            statuses[(lock_start, t_end)] = locked_info
541f867da40b1e52ab6ea80b93bf6e3e875d50e0d51Dan Shi            prev_interval_end = t_end
5427cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi        # Otherwise we are in the case where lock_end < t_start OR
5437cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi        # lock_start > t_end, which means the lock doesn't apply.
5447cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi    if t_end > prev_interval_end:
5455952fbe77ee7f33a642d19aa1d13ffeae0b92117Michael Liang        # This is to avoid logging the same time
5467cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi        statuses[(prev_interval_end, t_end)] = status_info
5477cf3d84fda609f6402543bb7e0bf3e3b7f93d539Dan Shi    return statuses
5481ccb65208c26615096985b5f8b52368f7342a077Dan Shi
5491ccb65208c26615096985b5f8b52368f7342a077Dan Shi
5501ccb65208c26615096985b5f8b52368f7342a077Dan Shidef get_log_url(hostname, metadata):
5511ccb65208c26615096985b5f8b52368f7342a077Dan Shi    """Compile a url to job's debug log from debug string.
5521ccb65208c26615096985b5f8b52368f7342a077Dan Shi
5531ccb65208c26615096985b5f8b52368f7342a077Dan Shi    @param hostname: Hostname of the dut.
5541ccb65208c26615096985b5f8b52368f7342a077Dan Shi    @param metadata: A dictionary of other metadata, e.g.,
5551ccb65208c26615096985b5f8b52368f7342a077Dan Shi                                     {'task_id':123, 'task_name':'Reset'}
5561ccb65208c26615096985b5f8b52368f7342a077Dan Shi    @return: Url of the debug log for special task or job url for test job.
5571ccb65208c26615096985b5f8b52368f7342a077Dan Shi    """
5581ccb65208c26615096985b5f8b52368f7342a077Dan Shi    log_url = None
5591ccb65208c26615096985b5f8b52368f7342a077Dan Shi    if 'task_id' in metadata and 'task_name' in metadata:
5601ccb65208c26615096985b5f8b52368f7342a077Dan Shi        log_url = job_history.TASK_URL % {'hostname': hostname,
5611ccb65208c26615096985b5f8b52368f7342a077Dan Shi                                          'task_id': metadata['task_id'],
5621ccb65208c26615096985b5f8b52368f7342a077Dan Shi                                          'task_name': metadata['task_name']}
5631ccb65208c26615096985b5f8b52368f7342a077Dan Shi    elif 'job_id' in metadata and 'owner' in metadata:
5641ccb65208c26615096985b5f8b52368f7342a077Dan Shi        log_url = job_history.JOB_URL % {'hostname': hostname,
5651ccb65208c26615096985b5f8b52368f7342a077Dan Shi                                         'job_id': metadata['job_id'],
5661ccb65208c26615096985b5f8b52368f7342a077Dan Shi                                         'owner': metadata['owner']}
5671ccb65208c26615096985b5f8b52368f7342a077Dan Shi
5681ccb65208c26615096985b5f8b52368f7342a077Dan Shi    return log_url
5691ccb65208c26615096985b5f8b52368f7342a077Dan Shi
5701ccb65208c26615096985b5f8b52368f7342a077Dan Shi
5711ccb65208c26615096985b5f8b52368f7342a077Dan Shidef build_history(hostname, status_intervals):
5721ccb65208c26615096985b5f8b52368f7342a077Dan Shi    """Get host history information from given state intervals.
5731ccb65208c26615096985b5f8b52368f7342a077Dan Shi
5741ccb65208c26615096985b5f8b52368f7342a077Dan Shi    @param hostname: Hostname of the dut.
5751ccb65208c26615096985b5f8b52368f7342a077Dan Shi    @param status_intervals: A ordered dictionary with
5761ccb65208c26615096985b5f8b52368f7342a077Dan Shi                    key as (t_start, t_end) and value as (status, metadata)
5771ccb65208c26615096985b5f8b52368f7342a077Dan Shi                    status = status of the host. e.g. 'Repair Failed'
5781ccb65208c26615096985b5f8b52368f7342a077Dan Shi                    t_start is the beginning of the interval where the DUT's has
5791ccb65208c26615096985b5f8b52368f7342a077Dan Shi                            that status
5801ccb65208c26615096985b5f8b52368f7342a077Dan Shi                    t_end is the end of the interval where the DUT has that
5811ccb65208c26615096985b5f8b52368f7342a077Dan Shi                            status
5821ccb65208c26615096985b5f8b52368f7342a077Dan Shi                    metadata: A dictionary of other metadata, e.g.,
5831ccb65208c26615096985b5f8b52368f7342a077Dan Shi                                        {'task_id':123, 'task_name':'Reset'}
5841ccb65208c26615096985b5f8b52368f7342a077Dan Shi    @return: A list of host history, e.g.,
5851ccb65208c26615096985b5f8b52368f7342a077Dan Shi             [{'status': 'Resetting'
5861ccb65208c26615096985b5f8b52368f7342a077Dan Shi               'start_time': '2014-08-07 10:02:16',
5871ccb65208c26615096985b5f8b52368f7342a077Dan Shi               'end_time': '2014-08-07 10:03:16',
5881ccb65208c26615096985b5f8b52368f7342a077Dan Shi               'log_url': 'http://autotest/reset-546546/debug',
5891ccb65208c26615096985b5f8b52368f7342a077Dan Shi               'task_id': 546546},
5901ccb65208c26615096985b5f8b52368f7342a077Dan Shi              {'status': 'Running'
5911ccb65208c26615096985b5f8b52368f7342a077Dan Shi               'start_time': '2014-08-07 10:03:18',
5921ccb65208c26615096985b5f8b52368f7342a077Dan Shi               'end_time': '2014-08-07 10:13:00',
5931ccb65208c26615096985b5f8b52368f7342a077Dan Shi               'log_url': 'http://autotest/afe/#tab_id=view_job&object_id=1683',
5941ccb65208c26615096985b5f8b52368f7342a077Dan Shi               'job_id': 1683}
5951ccb65208c26615096985b5f8b52368f7342a077Dan Shi             ]
5961ccb65208c26615096985b5f8b52368f7342a077Dan Shi    """
5971ccb65208c26615096985b5f8b52368f7342a077Dan Shi    history = []
5981ccb65208c26615096985b5f8b52368f7342a077Dan Shi    for time_interval, status_info in status_intervals.items():
5991ccb65208c26615096985b5f8b52368f7342a077Dan Shi        start_time = time_utils.epoch_time_to_date_string(time_interval[0])
6001ccb65208c26615096985b5f8b52368f7342a077Dan Shi        end_time = time_utils.epoch_time_to_date_string(time_interval[1])
6011ccb65208c26615096985b5f8b52368f7342a077Dan Shi        interval = {'status': status_info['status'],
6021ccb65208c26615096985b5f8b52368f7342a077Dan Shi                    'start_time': start_time,
6031ccb65208c26615096985b5f8b52368f7342a077Dan Shi                    'end_time': end_time}
6041ccb65208c26615096985b5f8b52368f7342a077Dan Shi        interval['log_url'] = get_log_url(hostname, status_info['metadata'])
6051ccb65208c26615096985b5f8b52368f7342a077Dan Shi        interval.update(status_info['metadata'])
6061ccb65208c26615096985b5f8b52368f7342a077Dan Shi        history.append(interval)
6071ccb65208c26615096985b5f8b52368f7342a077Dan Shi    return history
6081ccb65208c26615096985b5f8b52368f7342a077Dan Shi
6091ccb65208c26615096985b5f8b52368f7342a077Dan Shi
6101ccb65208c26615096985b5f8b52368f7342a077Dan Shidef get_status_intervals(history_details):
6111ccb65208c26615096985b5f8b52368f7342a077Dan Shi    """Get a list of status interval from history details.
6121ccb65208c26615096985b5f8b52368f7342a077Dan Shi
6131ccb65208c26615096985b5f8b52368f7342a077Dan Shi    This is a reverse method of above build_history. Caller gets the history
6141ccb65208c26615096985b5f8b52368f7342a077Dan Shi    details from RPC get_host_history, and use this method to get the list of
6151ccb65208c26615096985b5f8b52368f7342a077Dan Shi    status interval, which can be used to calculate stats from
6161ccb65208c26615096985b5f8b52368f7342a077Dan Shi    host_history_utils.aggregate_hosts.
6171ccb65208c26615096985b5f8b52368f7342a077Dan Shi
6181ccb65208c26615096985b5f8b52368f7342a077Dan Shi    @param history_details: A dictionary of host history for each host, e.g.,
6191ccb65208c26615096985b5f8b52368f7342a077Dan Shi            {'172.22.33.51': [{'status': 'Resetting'
6201ccb65208c26615096985b5f8b52368f7342a077Dan Shi                               'start_time': '2014-08-07 10:02:16',
6211ccb65208c26615096985b5f8b52368f7342a077Dan Shi                               'end_time': '2014-08-07 10:03:16',
6221ccb65208c26615096985b5f8b52368f7342a077Dan Shi                               'log_url': 'http://autotest/reset-546546/debug',
6231ccb65208c26615096985b5f8b52368f7342a077Dan Shi                               'task_id': 546546},]
6241ccb65208c26615096985b5f8b52368f7342a077Dan Shi            }
6251ccb65208c26615096985b5f8b52368f7342a077Dan Shi    @return: A list of dictionaries where keys are tuple (start_time, end_time),
6261ccb65208c26615096985b5f8b52368f7342a077Dan Shi             and value is a dictionary containing at least key 'status'.
6271ccb65208c26615096985b5f8b52368f7342a077Dan Shi    """
6281ccb65208c26615096985b5f8b52368f7342a077Dan Shi    status_intervals = []
6291ccb65208c26615096985b5f8b52368f7342a077Dan Shi    for host,history in history_details.iteritems():
6301ccb65208c26615096985b5f8b52368f7342a077Dan Shi        intervals = collections.OrderedDict()
6311ccb65208c26615096985b5f8b52368f7342a077Dan Shi        for interval in history:
6321ccb65208c26615096985b5f8b52368f7342a077Dan Shi            start_time = time_utils.to_epoch_time(interval['start_time'])
6331ccb65208c26615096985b5f8b52368f7342a077Dan Shi            end_time = time_utils.to_epoch_time(interval['end_time'])
6341ccb65208c26615096985b5f8b52368f7342a077Dan Shi            metadata = copy.deepcopy(interval)
6351ccb65208c26615096985b5f8b52368f7342a077Dan Shi            metadata['hostname'] = host
6361ccb65208c26615096985b5f8b52368f7342a077Dan Shi            intervals[(start_time, end_time)] = {'status': interval['status'],
6371ccb65208c26615096985b5f8b52368f7342a077Dan Shi                                                 'metadata': metadata}
6381ccb65208c26615096985b5f8b52368f7342a077Dan Shi        status_intervals.append(intervals)
6391ccb65208c26615096985b5f8b52368f7342a077Dan Shi    return status_intervals
6401ccb65208c26615096985b5f8b52368f7342a077Dan Shi
6411ccb65208c26615096985b5f8b52368f7342a077Dan Shi
6421ccb65208c26615096985b5f8b52368f7342a077Dan Shidef get_machine_utilization_rate(stats):
6431ccb65208c26615096985b5f8b52368f7342a077Dan Shi    """Get machine utilization rate from given stats.
6441ccb65208c26615096985b5f8b52368f7342a077Dan Shi
6451ccb65208c26615096985b5f8b52368f7342a077Dan Shi    @param stats: A dictionary with a status as key and value is the total
6461ccb65208c26615096985b5f8b52368f7342a077Dan Shi                  number of seconds spent on the status.
6471ccb65208c26615096985b5f8b52368f7342a077Dan Shi    @return: The percentage of time when dut is running test jobs.
6481ccb65208c26615096985b5f8b52368f7342a077Dan Shi    """
6491ccb65208c26615096985b5f8b52368f7342a077Dan Shi    not_utilized_status = ['Repairing', 'Repair Failed', 'Ready', 'Verifying']
6501ccb65208c26615096985b5f8b52368f7342a077Dan Shi    excluded_status = ['Locked']
6511ccb65208c26615096985b5f8b52368f7342a077Dan Shi    total_time = 0
6521ccb65208c26615096985b5f8b52368f7342a077Dan Shi    total_time_not_utilized = 0.0
6531ccb65208c26615096985b5f8b52368f7342a077Dan Shi    for status, interval in stats.iteritems():
6541ccb65208c26615096985b5f8b52368f7342a077Dan Shi        if status in excluded_status:
6551ccb65208c26615096985b5f8b52368f7342a077Dan Shi            continue
6561ccb65208c26615096985b5f8b52368f7342a077Dan Shi        total_time += interval
6571ccb65208c26615096985b5f8b52368f7342a077Dan Shi        if status in not_utilized_status:
6581ccb65208c26615096985b5f8b52368f7342a077Dan Shi            total_time_not_utilized += interval
6591ccb65208c26615096985b5f8b52368f7342a077Dan Shi    if total_time == 0:
6601ccb65208c26615096985b5f8b52368f7342a077Dan Shi        # All duts are locked, assume MUR is 0%
6611ccb65208c26615096985b5f8b52368f7342a077Dan Shi        return 0
6621ccb65208c26615096985b5f8b52368f7342a077Dan Shi    else:
6631ccb65208c26615096985b5f8b52368f7342a077Dan Shi        return 1 - total_time_not_utilized/total_time
6641ccb65208c26615096985b5f8b52368f7342a077Dan Shi
6651ccb65208c26615096985b5f8b52368f7342a077Dan Shi
6661ccb65208c26615096985b5f8b52368f7342a077Dan Shidef get_machine_availability_rate(stats):
6671ccb65208c26615096985b5f8b52368f7342a077Dan Shi    """Get machine availability rate from given stats.
6681ccb65208c26615096985b5f8b52368f7342a077Dan Shi
6691ccb65208c26615096985b5f8b52368f7342a077Dan Shi    @param stats: A dictionary with a status as key and value is the total
6701ccb65208c26615096985b5f8b52368f7342a077Dan Shi                  number of seconds spent on the status.
6711ccb65208c26615096985b5f8b52368f7342a077Dan Shi    @return: The percentage of time when dut is available to run jobs.
6721ccb65208c26615096985b5f8b52368f7342a077Dan Shi    """
6731ccb65208c26615096985b5f8b52368f7342a077Dan Shi    not_available_status = ['Repairing', 'Repair Failed', 'Verifying']
6741ccb65208c26615096985b5f8b52368f7342a077Dan Shi    excluded_status = ['Locked']
6751ccb65208c26615096985b5f8b52368f7342a077Dan Shi    total_time = 0
6761ccb65208c26615096985b5f8b52368f7342a077Dan Shi    total_time_not_available = 0.0
6771ccb65208c26615096985b5f8b52368f7342a077Dan Shi    for status, interval in stats.iteritems():
6781ccb65208c26615096985b5f8b52368f7342a077Dan Shi        if status in excluded_status:
6791ccb65208c26615096985b5f8b52368f7342a077Dan Shi            continue
6801ccb65208c26615096985b5f8b52368f7342a077Dan Shi        total_time += interval
6811ccb65208c26615096985b5f8b52368f7342a077Dan Shi        if status in not_available_status:
6821ccb65208c26615096985b5f8b52368f7342a077Dan Shi            total_time_not_available += interval
6831ccb65208c26615096985b5f8b52368f7342a077Dan Shi    if total_time == 0:
6841ccb65208c26615096985b5f8b52368f7342a077Dan Shi        # All duts are locked, assume MAR is 0%
6851ccb65208c26615096985b5f8b52368f7342a077Dan Shi        return 0
6861ccb65208c26615096985b5f8b52368f7342a077Dan Shi    else:
6871ccb65208c26615096985b5f8b52368f7342a077Dan Shi        return 1 - total_time_not_available/total_time
688