xref: /aosp_15_r20/external/autotest/site_utils/dut_status.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li#!/usr/bin/env python3
2*9c5db199SXin Li# Copyright 2014 The Chromium OS Authors. All rights reserved.
3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
4*9c5db199SXin Li# found in the LICENSE file.
5*9c5db199SXin Li
6*9c5db199SXin Li"""Report whether DUTs are working or broken.
7*9c5db199SXin Li
8*9c5db199SXin Liusage: dut_status [ <options> ] [hostname ...]
9*9c5db199SXin Li
10*9c5db199SXin LiReports on the history and status of selected DUT hosts, to
11*9c5db199SXin Lidetermine whether they're "working" or "broken".  For purposes of
12*9c5db199SXin Lithe script, "broken" means "the DUT requires manual intervention
13*9c5db199SXin Libefore it can be used for further testing", and "working" means "not
14*9c5db199SXin Libroken".  The status determination is based on the history of
15*9c5db199SXin Licompleted jobs for the DUT in a given time interval; still-running
16*9c5db199SXin Lijobs are not considered.
17*9c5db199SXin Li
18*9c5db199SXin LiTime Interval Selection
19*9c5db199SXin Li~~~~~~~~~~~~~~~~~~~~~~~
20*9c5db199SXin LiA DUT's reported status is based on the DUT's job history in a time
21*9c5db199SXin Liinterval determined by command line options.  The interval is
22*9c5db199SXin Lispecified with up to two of three options:
23*9c5db199SXin Li  --until/-u DATE/TIME - Specifies an end time for the search
24*9c5db199SXin Li      range.  (default: now)
25*9c5db199SXin Li  --since/-s DATE/TIME - Specifies a start time for the search
26*9c5db199SXin Li      range. (no default)
27*9c5db199SXin Li  --duration/-d HOURS - Specifies the length of the search interval
28*9c5db199SXin Li      in hours. (default: 24 hours)
29*9c5db199SXin Li
30*9c5db199SXin LiAny two time options completely specify the time interval.  If only
31*9c5db199SXin Lione option is provided, these defaults are used:
32*9c5db199SXin Li  --until - Use the given end time with the default duration.
33*9c5db199SXin Li  --since - Use the given start time with the default end time.
34*9c5db199SXin Li  --duration - Use the given duration with the default end time.
35*9c5db199SXin Li
36*9c5db199SXin LiIf no time options are given, use the default end time and duration.
37*9c5db199SXin Li
38*9c5db199SXin LiDATE/TIME values are of the form '2014-11-06 17:21:34'.
39*9c5db199SXin Li
40*9c5db199SXin LiDUT Selection
41*9c5db199SXin Li~~~~~~~~~~~~~
42*9c5db199SXin LiBy default, information is reported for DUTs named as command-line
43*9c5db199SXin Liarguments.  Options are also available for selecting groups of
44*9c5db199SXin Lihosts:
45*9c5db199SXin Li  --board/-b BOARD - Only include hosts with the given board.
46*9c5db199SXin Li  --pool/-p POOL - Only include hosts in the given pool. The user
47*9c5db199SXin Li      might be interested in the following pools: bvt, cq,
48*9c5db199SXin Li      continuous, cts, or suites.
49*9c5db199SXin Li
50*9c5db199SXin Li
51*9c5db199SXin LiThe selected hosts may also be filtered based on status:
52*9c5db199SXin Li  -w/--working - Only include hosts in a working state.
53*9c5db199SXin Li  -n/--broken - Only include hosts in a non-working state.  Hosts
54*9c5db199SXin Li      with no job history are considered non-working.
55*9c5db199SXin Li
56*9c5db199SXin LiOutput Formats
57*9c5db199SXin Li~~~~~~~~~~~~~~
58*9c5db199SXin LiThere are four available output formats:
59*9c5db199SXin Li  * A simple list of host names.
60*9c5db199SXin Li  * A status summary showing one line per host.
61*9c5db199SXin Li  * A detailed job history for all selected DUTs, sorted by
62*9c5db199SXin Li    time of execution.
63*9c5db199SXin Li  * A job history for all selected DUTs showing only the history
64*9c5db199SXin Li    surrounding the DUT's last change from working to broken,
65*9c5db199SXin Li    or vice versa.
66*9c5db199SXin Li
67*9c5db199SXin LiThe default format depends on whether hosts are filtered by
68*9c5db199SXin Listatus:
69*9c5db199SXin Li  * With the --working or --broken options, the list of host names
70*9c5db199SXin Li    is the default format.
71*9c5db199SXin Li  * Without those options, the default format is the one-line status
72*9c5db199SXin Li    summary.
73*9c5db199SXin Li
74*9c5db199SXin LiThese options override the default formats:
75*9c5db199SXin Li  -o/--oneline - Use the one-line summary with the --working or
76*9c5db199SXin Li      --broken options.
77*9c5db199SXin Li  -f/--full_history - Print detailed per-host job history.
78*9c5db199SXin Li  -g/--diagnosis - Print the job history surrounding a status
79*9c5db199SXin Li      change.
80*9c5db199SXin Li
81*9c5db199SXin LiExamples
82*9c5db199SXin Li~~~~~~~~
83*9c5db199SXin Li    $ dut_status chromeos2-row4-rack2-host12
84*9c5db199SXin Li    hostname                     S   last checked         URL
85*9c5db199SXin Li    chromeos2-row4-rack2-host12  NO  2014-11-06 15:25:29  http://...
86*9c5db199SXin Li
87*9c5db199SXin Li'NO' means the DUT is broken.  That diagnosis is based on a job that
88*9c5db199SXin Lifailed:  'last checked' is the time of the failed job, and the URL
89*9c5db199SXin Lipoints to the job's logs.
90*9c5db199SXin Li
91*9c5db199SXin Li    $ dut_status.py -u '2014-11-06 15:30:00' -d 1 -f chromeos2-row4-rack2-host12
92*9c5db199SXin Li    chromeos2-row4-rack2-host12
93*9c5db199SXin Li        2014-11-06 15:25:29  NO http://...
94*9c5db199SXin Li        2014-11-06 14:44:07  -- http://...
95*9c5db199SXin Li        2014-11-06 14:42:56  OK http://...
96*9c5db199SXin Li
97*9c5db199SXin LiThe times are the start times of the jobs; the URL points to the
98*9c5db199SXin Lijob's logs.  The status indicates the working or broken status after
99*9c5db199SXin Lithe job:
100*9c5db199SXin Li  'NO' Indicates that the DUT was believed broken after the job.
101*9c5db199SXin Li  'OK' Indicates that the DUT was believed working after the job.
102*9c5db199SXin Li  '--' Indicates that the job probably didn't change the DUT's
103*9c5db199SXin Li       status.
104*9c5db199SXin LiTypically, logs of the actual failure will be found at the last job
105*9c5db199SXin Lito report 'OK', or the first job to report '--'.
106*9c5db199SXin Li
107*9c5db199SXin Li"""
108*9c5db199SXin Li
109*9c5db199SXin Lifrom __future__ import absolute_import
110*9c5db199SXin Lifrom __future__ import division
111*9c5db199SXin Lifrom __future__ import print_function
112*9c5db199SXin Li
113*9c5db199SXin Liimport argparse
114*9c5db199SXin Liimport sys
115*9c5db199SXin Liimport time
116*9c5db199SXin Li
117*9c5db199SXin Liimport common
118*9c5db199SXin Lifrom autotest_lib.client.common_lib import time_utils
119*9c5db199SXin Lifrom autotest_lib.server import constants
120*9c5db199SXin Lifrom autotest_lib.server import frontend
121*9c5db199SXin Lifrom autotest_lib.server.lib import status_history
122*9c5db199SXin Lifrom autotest_lib.utils import labellib
123*9c5db199SXin Li
124*9c5db199SXin Li# The fully qualified name makes for lines that are too long, so
125*9c5db199SXin Li# shorten it locally.
126*9c5db199SXin LiHostJobHistory = status_history.HostJobHistory
127*9c5db199SXin Li
128*9c5db199SXin Li# _DIAGNOSIS_IDS -
129*9c5db199SXin Li#     Dictionary to map the known diagnosis codes to string values.
130*9c5db199SXin Li
131*9c5db199SXin Li_DIAGNOSIS_IDS = {
132*9c5db199SXin Li    status_history.UNUSED: '??',
133*9c5db199SXin Li    status_history.UNKNOWN: '--',
134*9c5db199SXin Li    status_history.WORKING: 'OK',
135*9c5db199SXin Li    status_history.BROKEN: 'NO'
136*9c5db199SXin Li}
137*9c5db199SXin Li
138*9c5db199SXin Li
139*9c5db199SXin Li# Default time interval for the --duration option when a value isn't
140*9c5db199SXin Li# specified on the command line.
141*9c5db199SXin Li_DEFAULT_DURATION = 24
142*9c5db199SXin Li
143*9c5db199SXin Li
144*9c5db199SXin Lidef _include_status(status, arguments):
145*9c5db199SXin Li    """Determine whether the given status should be filtered.
146*9c5db199SXin Li
147*9c5db199SXin Li    Checks the given `status` against the command line options in
148*9c5db199SXin Li    `arguments`.  Return whether a host with that status should be
149*9c5db199SXin Li    printed based on the options.
150*9c5db199SXin Li
151*9c5db199SXin Li    @param status Status of a host to be printed or skipped.
152*9c5db199SXin Li    @param arguments Parsed arguments object as returned by
153*9c5db199SXin Li                     ArgumentParser.parse_args().
154*9c5db199SXin Li
155*9c5db199SXin Li    @return Returns `True` if the command-line options call for
156*9c5db199SXin Li            printing hosts with the status, or `False` otherwise.
157*9c5db199SXin Li
158*9c5db199SXin Li    """
159*9c5db199SXin Li    if status == status_history.WORKING:
160*9c5db199SXin Li        return arguments.working
161*9c5db199SXin Li    else:
162*9c5db199SXin Li        return arguments.broken
163*9c5db199SXin Li
164*9c5db199SXin Li
165*9c5db199SXin Lidef _print_host_summaries(history_list, arguments):
166*9c5db199SXin Li    """Print one-line summaries of host history.
167*9c5db199SXin Li
168*9c5db199SXin Li    This function handles the output format of the --oneline option.
169*9c5db199SXin Li
170*9c5db199SXin Li    @param history_list A list of HostHistory objects to be printed.
171*9c5db199SXin Li    @param arguments    Parsed arguments object as returned by
172*9c5db199SXin Li                        ArgumentParser.parse_args().
173*9c5db199SXin Li
174*9c5db199SXin Li    """
175*9c5db199SXin Li    fmt = '%-30s %-2s  %-19s  %s'
176*9c5db199SXin Li    print(fmt % ('hostname', 'S', 'last checked', 'URL'))
177*9c5db199SXin Li    for history in history_list:
178*9c5db199SXin Li        status, event = history.last_diagnosis()
179*9c5db199SXin Li        if not _include_status(status, arguments):
180*9c5db199SXin Li            continue
181*9c5db199SXin Li        datestr = '---'
182*9c5db199SXin Li        url = '---'
183*9c5db199SXin Li        if event is not None:
184*9c5db199SXin Li            datestr = time_utils.epoch_time_to_date_string(
185*9c5db199SXin Li                event.start_time)
186*9c5db199SXin Li            url = event.job_url
187*9c5db199SXin Li
188*9c5db199SXin Li        print(fmt % (history.hostname,
189*9c5db199SXin Li                     _DIAGNOSIS_IDS[status],
190*9c5db199SXin Li                     datestr,
191*9c5db199SXin Li                     url))
192*9c5db199SXin Li
193*9c5db199SXin Li
194*9c5db199SXin Lidef _print_event_summary(event):
195*9c5db199SXin Li    """Print a one-line summary of a job or special task."""
196*9c5db199SXin Li    start_time = time_utils.epoch_time_to_date_string(
197*9c5db199SXin Li        event.start_time)
198*9c5db199SXin Li    print('    %s  %s %s' % (
199*9c5db199SXin Li          start_time,
200*9c5db199SXin Li          _DIAGNOSIS_IDS[event.diagnosis],
201*9c5db199SXin Li          event.job_url))
202*9c5db199SXin Li
203*9c5db199SXin Li
204*9c5db199SXin Lidef _print_hosts(history_list, arguments):
205*9c5db199SXin Li    """Print hosts, optionally with a job history.
206*9c5db199SXin Li
207*9c5db199SXin Li    This function handles both the default format for --working
208*9c5db199SXin Li    and --broken options, as well as the output for the
209*9c5db199SXin Li    --full_history and --diagnosis options.  The `arguments`
210*9c5db199SXin Li    parameter determines the format to use.
211*9c5db199SXin Li
212*9c5db199SXin Li    @param history_list A list of HostHistory objects to be printed.
213*9c5db199SXin Li    @param arguments    Parsed arguments object as returned by
214*9c5db199SXin Li                        ArgumentParser.parse_args().
215*9c5db199SXin Li
216*9c5db199SXin Li    """
217*9c5db199SXin Li    for history in history_list:
218*9c5db199SXin Li        status, _ = history.last_diagnosis()
219*9c5db199SXin Li        if not _include_status(status, arguments):
220*9c5db199SXin Li            continue
221*9c5db199SXin Li        print(history.hostname)
222*9c5db199SXin Li        if arguments.full_history:
223*9c5db199SXin Li            for event in history:
224*9c5db199SXin Li                _print_event_summary(event)
225*9c5db199SXin Li        elif arguments.diagnosis:
226*9c5db199SXin Li            for event in history.diagnosis_interval():
227*9c5db199SXin Li                _print_event_summary(event)
228*9c5db199SXin Li
229*9c5db199SXin Li
230*9c5db199SXin Lidef _validate_time_range(arguments):
231*9c5db199SXin Li    """Validate the time range requested on the command line.
232*9c5db199SXin Li
233*9c5db199SXin Li    Enforces the rules for the --until, --since, and --duration
234*9c5db199SXin Li    options are followed, and calculates defaults:
235*9c5db199SXin Li      * It isn't allowed to supply all three options.
236*9c5db199SXin Li      * If only two options are supplied, they completely determine
237*9c5db199SXin Li        the time interval.
238*9c5db199SXin Li      * If only one option is supplied, or no options, then apply
239*9c5db199SXin Li        specified defaults to the arguments object.
240*9c5db199SXin Li
241*9c5db199SXin Li    @param arguments Parsed arguments object as returned by
242*9c5db199SXin Li                     ArgumentParser.parse_args().
243*9c5db199SXin Li
244*9c5db199SXin Li    """
245*9c5db199SXin Li    if (arguments.duration is not None and
246*9c5db199SXin Li            arguments.since is not None and arguments.until is not None):
247*9c5db199SXin Li        print('FATAL: Can specify at most two of '
248*9c5db199SXin Li              '--since, --until, and --duration',
249*9c5db199SXin Li              file=sys.stderr)
250*9c5db199SXin Li        sys.exit(1)
251*9c5db199SXin Li    if (arguments.until is None and (arguments.since is None or
252*9c5db199SXin Li                                     arguments.duration is None)):
253*9c5db199SXin Li        arguments.until = int(time.time())
254*9c5db199SXin Li    if arguments.since is None:
255*9c5db199SXin Li        if arguments.duration is None:
256*9c5db199SXin Li            arguments.duration = _DEFAULT_DURATION
257*9c5db199SXin Li        arguments.since = (arguments.until -
258*9c5db199SXin Li                           arguments.duration * 60 * 60)
259*9c5db199SXin Li    elif arguments.until is None:
260*9c5db199SXin Li        arguments.until = (arguments.since +
261*9c5db199SXin Li                           arguments.duration * 60 * 60)
262*9c5db199SXin Li
263*9c5db199SXin Li
264*9c5db199SXin Lidef _get_host_histories(afe, arguments):
265*9c5db199SXin Li    """Return HostJobHistory objects for the requested hosts.
266*9c5db199SXin Li
267*9c5db199SXin Li    Checks that individual hosts specified on the command line are
268*9c5db199SXin Li    valid.  Invalid hosts generate a warning message, and are
269*9c5db199SXin Li    omitted from futher processing.
270*9c5db199SXin Li
271*9c5db199SXin Li    The return value is a list of HostJobHistory objects for the
272*9c5db199SXin Li    valid requested hostnames, using the time range supplied on the
273*9c5db199SXin Li    command line.
274*9c5db199SXin Li
275*9c5db199SXin Li    @param afe       Autotest frontend
276*9c5db199SXin Li    @param arguments Parsed arguments object as returned by
277*9c5db199SXin Li                     ArgumentParser.parse_args().
278*9c5db199SXin Li    @return List of HostJobHistory objects for the hosts requested
279*9c5db199SXin Li            on the command line.
280*9c5db199SXin Li
281*9c5db199SXin Li    """
282*9c5db199SXin Li    histories = []
283*9c5db199SXin Li    saw_error = False
284*9c5db199SXin Li    for hostname in arguments.hostnames:
285*9c5db199SXin Li        try:
286*9c5db199SXin Li            h = HostJobHistory.get_host_history(
287*9c5db199SXin Li                    afe, hostname, arguments.since, arguments.until)
288*9c5db199SXin Li            histories.append(h)
289*9c5db199SXin Li        except:
290*9c5db199SXin Li            print('WARNING: Ignoring unknown host %s' %
291*9c5db199SXin Li                  hostname, file=sys.stderr)
292*9c5db199SXin Li            saw_error = True
293*9c5db199SXin Li    if saw_error:
294*9c5db199SXin Li        # Create separation from the output that follows
295*9c5db199SXin Li        print(file=sys.stderr)
296*9c5db199SXin Li    return histories
297*9c5db199SXin Li
298*9c5db199SXin Li
299*9c5db199SXin Lidef _validate_host_list(afe, arguments):
300*9c5db199SXin Li    """Validate the user-specified list of hosts.
301*9c5db199SXin Li
302*9c5db199SXin Li    Hosts may be specified implicitly with --board or --pool, or
303*9c5db199SXin Li    explictly as command line arguments.  This enforces these
304*9c5db199SXin Li    rules:
305*9c5db199SXin Li      * If --board or --pool, or both are specified, individual
306*9c5db199SXin Li        hosts may not be specified.
307*9c5db199SXin Li      * However specified, there must be at least one host.
308*9c5db199SXin Li
309*9c5db199SXin Li    The return value is a list of HostJobHistory objects for the
310*9c5db199SXin Li    requested hosts, using the time range supplied on the command
311*9c5db199SXin Li    line.
312*9c5db199SXin Li
313*9c5db199SXin Li    @param afe       Autotest frontend
314*9c5db199SXin Li    @param arguments Parsed arguments object as returned by
315*9c5db199SXin Li                     ArgumentParser.parse_args().
316*9c5db199SXin Li    @return List of HostJobHistory objects for the hosts requested
317*9c5db199SXin Li            on the command line.
318*9c5db199SXin Li
319*9c5db199SXin Li    """
320*9c5db199SXin Li    if arguments.board or arguments.pool or arguments.model:
321*9c5db199SXin Li        if arguments.hostnames:
322*9c5db199SXin Li            print('FATAL: Hostname arguments provided '
323*9c5db199SXin Li                  'with --board or --pool', file=sys.stderr)
324*9c5db199SXin Li            sys.exit(1)
325*9c5db199SXin Li
326*9c5db199SXin Li        labels = labellib.LabelsMapping()
327*9c5db199SXin Li        labels['board'] = arguments.board
328*9c5db199SXin Li        labels['pool'] = arguments.pool
329*9c5db199SXin Li        labels['model'] = arguments.model
330*9c5db199SXin Li        histories = HostJobHistory.get_multiple_histories(
331*9c5db199SXin Li            afe, arguments.since, arguments.until, labels.getlabels())
332*9c5db199SXin Li    else:
333*9c5db199SXin Li        histories = _get_host_histories(afe, arguments)
334*9c5db199SXin Li    if not histories:
335*9c5db199SXin Li        print('FATAL: no valid hosts found', file=sys.stderr)
336*9c5db199SXin Li        sys.exit(1)
337*9c5db199SXin Li    return histories
338*9c5db199SXin Li
339*9c5db199SXin Li
340*9c5db199SXin Lidef _validate_format_options(arguments):
341*9c5db199SXin Li    """Check the options for what output format to use.
342*9c5db199SXin Li
343*9c5db199SXin Li    Enforce these rules:
344*9c5db199SXin Li      * If neither --broken nor --working was used, then --oneline
345*9c5db199SXin Li        becomes the selected format.
346*9c5db199SXin Li      * If neither --broken nor --working was used, included both
347*9c5db199SXin Li        working and broken DUTs.
348*9c5db199SXin Li
349*9c5db199SXin Li    @param arguments Parsed arguments object as returned by
350*9c5db199SXin Li                     ArgumentParser.parse_args().
351*9c5db199SXin Li
352*9c5db199SXin Li    """
353*9c5db199SXin Li    if (not arguments.oneline and not arguments.diagnosis and
354*9c5db199SXin Li            not arguments.full_history):
355*9c5db199SXin Li        arguments.oneline = (not arguments.working and
356*9c5db199SXin Li                             not arguments.broken)
357*9c5db199SXin Li    if not arguments.working and not arguments.broken:
358*9c5db199SXin Li        arguments.working = True
359*9c5db199SXin Li        arguments.broken = True
360*9c5db199SXin Li
361*9c5db199SXin Li
362*9c5db199SXin Lidef _validate_command(afe, arguments):
363*9c5db199SXin Li    """Check that the command's arguments are valid.
364*9c5db199SXin Li
365*9c5db199SXin Li    This performs command line checking to enforce command line
366*9c5db199SXin Li    rules that ArgumentParser can't handle.  Additionally, this
367*9c5db199SXin Li    handles calculation of default arguments/options when a simple
368*9c5db199SXin Li    constant default won't do.
369*9c5db199SXin Li
370*9c5db199SXin Li    Areas checked:
371*9c5db199SXin Li      * Check that a valid time range was provided, supplying
372*9c5db199SXin Li        defaults as necessary.
373*9c5db199SXin Li      * Identify invalid host names.
374*9c5db199SXin Li
375*9c5db199SXin Li    @param afe       Autotest frontend
376*9c5db199SXin Li    @param arguments Parsed arguments object as returned by
377*9c5db199SXin Li                     ArgumentParser.parse_args().
378*9c5db199SXin Li    @return List of HostJobHistory objects for the hosts requested
379*9c5db199SXin Li            on the command line.
380*9c5db199SXin Li
381*9c5db199SXin Li    """
382*9c5db199SXin Li    _validate_time_range(arguments)
383*9c5db199SXin Li    _validate_format_options(arguments)
384*9c5db199SXin Li    return _validate_host_list(afe, arguments)
385*9c5db199SXin Li
386*9c5db199SXin Li
387*9c5db199SXin Lidef _parse_command(argv):
388*9c5db199SXin Li    """Parse the command line arguments.
389*9c5db199SXin Li
390*9c5db199SXin Li    Create an argument parser for this command's syntax, parse the
391*9c5db199SXin Li    command line, and return the result of the ArgumentParser
392*9c5db199SXin Li    parse_args() method.
393*9c5db199SXin Li
394*9c5db199SXin Li    @param argv Standard command line argument vector; argv[0] is
395*9c5db199SXin Li                assumed to be the command name.
396*9c5db199SXin Li    @return Result returned by ArgumentParser.parse_args().
397*9c5db199SXin Li
398*9c5db199SXin Li    """
399*9c5db199SXin Li    parser = argparse.ArgumentParser(
400*9c5db199SXin Li            prog=argv[0],
401*9c5db199SXin Li            description='Report DUT status and execution history',
402*9c5db199SXin Li            epilog='You can specify one or two of --since, --until, '
403*9c5db199SXin Li                   'and --duration, but not all three.')
404*9c5db199SXin Li    parser.add_argument('-s', '--since', type=status_history.parse_time,
405*9c5db199SXin Li                        metavar='DATE/TIME',
406*9c5db199SXin Li                        help=('Starting time for history display. '
407*9c5db199SXin Li                              'Format: "YYYY-MM-DD HH:MM:SS"'))
408*9c5db199SXin Li    parser.add_argument('-u', '--until', type=status_history.parse_time,
409*9c5db199SXin Li                        metavar='DATE/TIME',
410*9c5db199SXin Li                        help=('Ending time for history display. '
411*9c5db199SXin Li                              'Format: "YYYY-MM-DD HH:MM:SS" '
412*9c5db199SXin Li                              'Default: now'))
413*9c5db199SXin Li    parser.add_argument('-d', '--duration', type=int,
414*9c5db199SXin Li                        metavar='HOURS',
415*9c5db199SXin Li                        help='Number of hours of history to display'
416*9c5db199SXin Li                             ' (default: %d)' % _DEFAULT_DURATION)
417*9c5db199SXin Li
418*9c5db199SXin Li    format_group = parser.add_mutually_exclusive_group()
419*9c5db199SXin Li    format_group.add_argument('-f', '--full_history', action='store_true',
420*9c5db199SXin Li                              help='Display host history from most '
421*9c5db199SXin Li                                   'to least recent for each DUT')
422*9c5db199SXin Li    format_group.add_argument('-g', '--diagnosis', action='store_true',
423*9c5db199SXin Li                              help='Display host history for the '
424*9c5db199SXin Li                                   'most recent DUT status change')
425*9c5db199SXin Li    format_group.add_argument('-o', '--oneline', action='store_true',
426*9c5db199SXin Li                              help='Display host status summary')
427*9c5db199SXin Li
428*9c5db199SXin Li    parser.add_argument('-w', '--working', action='store_true',
429*9c5db199SXin Li                        help='List working devices by name only')
430*9c5db199SXin Li    parser.add_argument('-n', '--broken', action='store_true',
431*9c5db199SXin Li                        help='List non-working devices by name only')
432*9c5db199SXin Li
433*9c5db199SXin Li    parser.add_argument('-b', '--board',
434*9c5db199SXin Li                        help='Display history for all DUTs '
435*9c5db199SXin Li                             'of the given board')
436*9c5db199SXin Li    parser.add_argument('-m', '--model',
437*9c5db199SXin Li                        help='Display history for all DUTs of the given model.')
438*9c5db199SXin Li    parser.add_argument('-p', '--pool',
439*9c5db199SXin Li                        help='Display history for all DUTs '
440*9c5db199SXin Li                             'in the given pool. You might '
441*9c5db199SXin Li                             'be interested in the following pools: '
442*9c5db199SXin Li                             + ', '.join(constants.Pools.MANAGED_POOLS[:-1])
443*9c5db199SXin Li                             +', or '+ constants.Pools.MANAGED_POOLS[-1] +'.')
444*9c5db199SXin Li    parser.add_argument('hostnames',
445*9c5db199SXin Li                        nargs='*',
446*9c5db199SXin Li                        help='Host names of DUTs to report on')
447*9c5db199SXin Li    parser.add_argument('--web',
448*9c5db199SXin Li                        help='Autotest frontend hostname. If no value '
449*9c5db199SXin Li                             'is given, the one in global config will be used.',
450*9c5db199SXin Li                        default=None)
451*9c5db199SXin Li    arguments = parser.parse_args(argv[1:])
452*9c5db199SXin Li    return arguments
453*9c5db199SXin Li
454*9c5db199SXin Li
455*9c5db199SXin Lidef main(argv):
456*9c5db199SXin Li    """Standard main() for command line processing.
457*9c5db199SXin Li
458*9c5db199SXin Li    @param argv Command line arguments (normally sys.argv).
459*9c5db199SXin Li
460*9c5db199SXin Li    """
461*9c5db199SXin Li    arguments = _parse_command(argv)
462*9c5db199SXin Li    afe = frontend.AFE(server=arguments.web)
463*9c5db199SXin Li    history_list = _validate_command(afe, arguments)
464*9c5db199SXin Li    if arguments.oneline:
465*9c5db199SXin Li        _print_host_summaries(history_list, arguments)
466*9c5db199SXin Li    else:
467*9c5db199SXin Li        _print_hosts(history_list, arguments)
468*9c5db199SXin Li
469*9c5db199SXin Li
470*9c5db199SXin Liif __name__ == '__main__':
471*9c5db199SXin Li    main(sys.argv)
472