blob: 935ad60eb36c3fd31c5c2a07562f7d8c229a7036 [file] [log] [blame]
J. Richard Barnettec9b79332014-09-22 13:14:33 -07001#!/usr/bin/env python
2# Copyright 2014 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
J. Richard Barnette40ab60a2014-11-18 15:44:09 -08006"""Report whether DUTs are working or broken.
J. Richard Barnettee62fa2c2014-11-06 17:44:04 -08007
J. Richard Barnette048a6282014-12-12 11:30:23 -08008usage: dut_status [ <options> ] [hostname ...]
J. Richard Barnettee62fa2c2014-11-06 17:44:04 -08009
J. Richard Barnette048a6282014-12-12 11:30:23 -080010Reports on the history and status of selected DUT hosts, to
11determine whether they're "working" or "broken". For purposes of
12the script, "broken" means "the DUT requires manual intervention
13before it can be used for further testing", and "working" means "not
14broken". The status determination is based on the history of
15completed jobs for the DUT in a given time interval; still-running
16jobs are not considered.
J. Richard Barnette40ab60a2014-11-18 15:44:09 -080017
J. Richard Barnette048a6282014-12-12 11:30:23 -080018Time Interval Selection
19~~~~~~~~~~~~~~~~~~~~~~~
20A DUT's reported status is based on the DUT's job history in a time
21interval determined by command line options. The interval is
22specified with up to two of three options:
J. Richard Barnettee62fa2c2014-11-06 17:44:04 -080023 --until/-u DATE/TIME - Specifies an end time for the search
24 range. (default: now)
25 --since/-s DATE/TIME - Specifies a start time for the search
26 range. (no default)
27 --duration/-d HOURS - Specifies the length of the search interval
Dan Shid2437362014-12-29 09:42:10 -080028 in hours. (default: 24 hours)
J. Richard Barnettee62fa2c2014-11-06 17:44:04 -080029
J. Richard Barnette784d1ba2014-11-18 14:52:43 -080030Any two time options completely specify the time interval. If only
31one option is provided, these defaults are used:
J. Richard Barnettee62fa2c2014-11-06 17:44:04 -080032 --until - Use the given end time with the default duration.
33 --since - Use the given start time with the default end time.
34 --duration - Use the given duration with the default end time.
35
36If no time options are given, use the default end time and duration.
37
38DATE/TIME values are of the form '2014-11-06 17:21:34'.
39
J. Richard Barnette048a6282014-12-12 11:30:23 -080040DUT Selection
41~~~~~~~~~~~~~
42By default, information is reported for DUTs named as command-line
43arguments. Options are also available for selecting groups of
44hosts:
45 --board/-b BOARD - Only include hosts with the given board.
46 --pool/-p POOL - Only include hosts in the given pool.
47
48The selected hosts may also be filtered based on status:
49 -w/--working - Only include hosts in a working state.
50 -n/--broken - Only include hosts in a non-working state. Hosts
51 with no job history are considered non-working.
52
53Output Formats
54~~~~~~~~~~~~~~
J. Richard Barnette8abbfd62015-06-23 12:46:54 -070055There are four available output formats:
J. Richard Barnette048a6282014-12-12 11:30:23 -080056 * A simple list of host names.
57 * A status summary showing one line per host.
58 * A detailed job history for all selected DUTs, sorted by
59 time of execution.
J. Richard Barnette8abbfd62015-06-23 12:46:54 -070060 * A job history for all selected DUTs showing only the history
61 surrounding the DUT's last change from working to broken,
62 or vice versa.
J. Richard Barnette048a6282014-12-12 11:30:23 -080063
64The default format depends on whether hosts are filtered by
65status:
66 * With the --working or --broken options, the list of host names
67 is the default format.
68 * Without those options, the default format is the one-line status
69 summary.
70
71These options override the default formats:
72 -o/--oneline - Use the one-line summary with the --working or
73 --broken options.
74 -f/--full_history - Print detailed per-host job history.
J. Richard Barnette8abbfd62015-06-23 12:46:54 -070075 -g/--diagnosis - Print the job history surrounding a status
76 change.
J. Richard Barnette048a6282014-12-12 11:30:23 -080077
78Examples
79~~~~~~~~
J. Richard Barnettee62fa2c2014-11-06 17:44:04 -080080 $ dut_status chromeos2-row4-rack2-host12
81 hostname S last checked URL
82 chromeos2-row4-rack2-host12 NO 2014-11-06 15:25:29 http://...
83
J. Richard Barnette784d1ba2014-11-18 14:52:43 -080084'NO' means the DUT is broken. That diagnosis is based on a job that
85failed: 'last checked' is the time of the failed job, and the URL
J. Richard Barnettee62fa2c2014-11-06 17:44:04 -080086points to the job's logs.
87
88 $ dut_status.py -u '2014-11-06 15:30:00' -d 1 -f chromeos2-row4-rack2-host12
89 chromeos2-row4-rack2-host12
90 2014-11-06 15:25:29 NO http://...
91 2014-11-06 14:44:07 -- http://...
92 2014-11-06 14:42:56 OK http://...
93
J. Richard Barnette784d1ba2014-11-18 14:52:43 -080094The times are the start times of the jobs; the URL points to the
95job's logs. The status indicates the working or broken status after
96the job:
J. Richard Barnette40ab60a2014-11-18 15:44:09 -080097 'NO' Indicates that the DUT was believed broken after the job.
98 'OK' Indicates that the DUT was believed working after the job.
J. Richard Barnettee62fa2c2014-11-06 17:44:04 -080099 '--' Indicates that the job probably didn't change the DUT's
100 status.
J. Richard Barnette784d1ba2014-11-18 14:52:43 -0800101Typically, logs of the actual failure will be found at the last job
102to report 'OK', or the first job to report '--'.
J. Richard Barnettee62fa2c2014-11-06 17:44:04 -0800103
104"""
105
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700106import argparse
107import sys
108import time
109
110import common
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700111from autotest_lib.client.common_lib import time_utils
MK Ryu1b2d7f92015-02-24 17:45:02 -0800112from autotest_lib.server import frontend
Aviv Keshet7ee95862016-08-30 15:18:27 -0700113from autotest_lib.server.lib import status_history
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700114
115
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700116# The fully qualified name makes for lines that are too long, so
117# shorten it locally.
118HostJobHistory = status_history.HostJobHistory
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700119
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700120# _DIAGNOSIS_IDS -
121# Dictionary to map the known diagnosis codes to string values.
122
123_DIAGNOSIS_IDS = {
124 status_history.UNUSED: '??',
125 status_history.UNKNOWN: '--',
126 status_history.WORKING: 'OK',
127 status_history.BROKEN: 'NO'
128}
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700129
130
131# Default time interval for the --duration option when a value isn't
132# specified on the command line.
Dan Shid2437362014-12-29 09:42:10 -0800133_DEFAULT_DURATION = 24
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700134
135
J. Richard Barnette048a6282014-12-12 11:30:23 -0800136def _include_status(status, arguments):
137 """Determine whether the given status should be filtered.
138
139 Checks the given `status` against the command line options in
140 `arguments`. Return whether a host with that status should be
141 printed based on the options.
142
143 @param status Status of a host to be printed or skipped.
144 @param arguments Parsed arguments object as returned by
145 ArgumentParser.parse_args().
146
147 @return Returns `True` if the command-line options call for
148 printing hosts with the status, or `False` otherwise.
149
150 """
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700151 if status == status_history.WORKING:
J. Richard Barnette048a6282014-12-12 11:30:23 -0800152 return arguments.working
153 else:
154 return arguments.broken
155
156
157def _print_host_summaries(history_list, arguments):
158 """Print one-line summaries of host history.
159
160 This function handles the output format of the --oneline option.
161
162 @param history_list A list of HostHistory objects to be printed.
163 @param arguments Parsed arguments object as returned by
164 ArgumentParser.parse_args().
165
166 """
J. Richard Barnette390e8652015-06-24 11:59:10 -0700167 fmt = '%-30s %-2s %-19s %s'
J. Richard Barnette36282cc2014-10-14 13:58:30 -0700168 print fmt % ('hostname', 'S', 'last checked', 'URL')
J. Richard Barnette784d1ba2014-11-18 14:52:43 -0800169 for history in history_list:
170 status, event = history.last_diagnosis()
J. Richard Barnette048a6282014-12-12 11:30:23 -0800171 if not _include_status(status, arguments):
172 continue
MK Ryu1b2d7f92015-02-24 17:45:02 -0800173 datestr = '---'
174 url = '---'
J. Richard Barnette784d1ba2014-11-18 14:52:43 -0800175 if event is not None:
176 datestr = time_utils.epoch_time_to_date_string(
177 event.start_time)
178 url = event.job_url
MK Ryu1b2d7f92015-02-24 17:45:02 -0800179
J. Richard Barnette36282cc2014-10-14 13:58:30 -0700180 print fmt % (history.hostname,
181 _DIAGNOSIS_IDS[status],
182 datestr,
183 url)
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700184
185
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700186def _print_event_summary(event):
187 """Print a one-line summary of a job or special task."""
188 start_time = time_utils.epoch_time_to_date_string(
189 event.start_time)
190 print ' %s %s %s' % (
191 start_time,
192 _DIAGNOSIS_IDS[event.diagnosis],
193 event.job_url)
194
195
J. Richard Barnette048a6282014-12-12 11:30:23 -0800196def _print_hosts(history_list, arguments):
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700197 """Print hosts, optionally with a job history.
J. Richard Barnette048a6282014-12-12 11:30:23 -0800198
199 This function handles both the default format for --working
200 and --broken options, as well as the output for the
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700201 --full_history and --diagnosis options. The `arguments`
202 parameter determines the format to use.
J. Richard Barnette048a6282014-12-12 11:30:23 -0800203
204 @param history_list A list of HostHistory objects to be printed.
205 @param arguments Parsed arguments object as returned by
206 ArgumentParser.parse_args().
207
208 """
J. Richard Barnette784d1ba2014-11-18 14:52:43 -0800209 for history in history_list:
J. Richard Barnette048a6282014-12-12 11:30:23 -0800210 status, _ = history.last_diagnosis()
211 if not _include_status(status, arguments):
212 continue
J. Richard Barnette784d1ba2014-11-18 14:52:43 -0800213 print history.hostname
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700214 if arguments.full_history:
215 for event in history:
216 _print_event_summary(event)
217 elif arguments.diagnosis:
218 for event in history.diagnosis_interval():
219 _print_event_summary(event)
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700220
221
J. Richard Barnette784d1ba2014-11-18 14:52:43 -0800222def _validate_time_range(arguments):
223 """Validate the time range requested on the command line.
224
225 Enforces the rules for the --until, --since, and --duration
226 options are followed, and calculates defaults:
227 * It isn't allowed to supply all three options.
228 * If only two options are supplied, they completely determine
229 the time interval.
230 * If only one option is supplied, or no options, then apply
231 specified defaults to the arguments object.
232
233 @param arguments Parsed arguments object as returned by
234 ArgumentParser.parse_args().
235
236 """
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700237 if (arguments.duration is not None and
238 arguments.since is not None and arguments.until is not None):
J. Richard Barnette784d1ba2014-11-18 14:52:43 -0800239 print >>sys.stderr, ('FATAL: Can specify at most two of '
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700240 '--since, --until, and --duration')
241 sys.exit(1)
242 if (arguments.until is None and (arguments.since is None or
243 arguments.duration is None)):
244 arguments.until = int(time.time())
245 if arguments.since is None:
246 if arguments.duration is None:
247 arguments.duration = _DEFAULT_DURATION
248 arguments.since = (arguments.until -
249 arguments.duration * 60 * 60)
250 elif arguments.until is None:
251 arguments.until = (arguments.since +
252 arguments.duration * 60 * 60)
253
254
MK Ryu1b2d7f92015-02-24 17:45:02 -0800255def _get_host_histories(afe, arguments):
J. Richard Barnette40ab60a2014-11-18 15:44:09 -0800256 """Return HostJobHistory objects for the requested hosts.
J. Richard Barnette784d1ba2014-11-18 14:52:43 -0800257
J. Richard Barnette40ab60a2014-11-18 15:44:09 -0800258 Checks that individual hosts specified on the command line are
259 valid. Invalid hosts generate a warning message, and are
260 omitted from futher processing.
J. Richard Barnette784d1ba2014-11-18 14:52:43 -0800261
J. Richard Barnette40ab60a2014-11-18 15:44:09 -0800262 The return value is a list of HostJobHistory objects for the
263 valid requested hostnames, using the time range supplied on the
J. Richard Barnette784d1ba2014-11-18 14:52:43 -0800264 command line.
265
MK Ryu1b2d7f92015-02-24 17:45:02 -0800266 @param afe Autotest frontend
J. Richard Barnette784d1ba2014-11-18 14:52:43 -0800267 @param arguments Parsed arguments object as returned by
268 ArgumentParser.parse_args().
269 @return List of HostJobHistory objects for the hosts requested
270 on the command line.
271
272 """
273 histories = []
274 saw_error = False
275 for hostname in arguments.hostnames:
276 try:
J. Richard Barnette40ab60a2014-11-18 15:44:09 -0800277 h = HostJobHistory.get_host_history(
MK Ryu1b2d7f92015-02-24 17:45:02 -0800278 afe, hostname, arguments.since, arguments.until)
J. Richard Barnette784d1ba2014-11-18 14:52:43 -0800279 histories.append(h)
280 except:
281 print >>sys.stderr, ('WARNING: Ignoring unknown host %s' %
282 hostname)
283 saw_error = True
284 if saw_error:
285 # Create separation from the output that follows
286 print >>sys.stderr
287 return histories
288
289
MK Ryu1b2d7f92015-02-24 17:45:02 -0800290def _validate_host_list(afe, arguments):
J. Richard Barnette40ab60a2014-11-18 15:44:09 -0800291 """Validate the user-specified list of hosts.
292
293 Hosts may be specified implicitly with --board or --pool, or
294 explictly as command line arguments. This enforces these
295 rules:
296 * If --board or --pool, or both are specified, individual
297 hosts may not be specified.
298 * However specified, there must be at least one host.
299
300 The return value is a list of HostJobHistory objects for the
301 requested hosts, using the time range supplied on the command
302 line.
303
MK Ryu1b2d7f92015-02-24 17:45:02 -0800304 @param afe Autotest frontend
J. Richard Barnette40ab60a2014-11-18 15:44:09 -0800305 @param arguments Parsed arguments object as returned by
306 ArgumentParser.parse_args().
307 @return List of HostJobHistory objects for the hosts requested
308 on the command line.
309
310 """
311 if arguments.board or arguments.pool:
312 if arguments.hostnames:
313 print >>sys.stderr, ('FATAL: Hostname arguments provided '
314 'with --board or --pool')
315 sys.exit(1)
316 histories = HostJobHistory.get_multiple_histories(
MK Ryu1b2d7f92015-02-24 17:45:02 -0800317 afe, arguments.since, arguments.until,
J. Richard Barnette40ab60a2014-11-18 15:44:09 -0800318 board=arguments.board, pool=arguments.pool)
319 else:
MK Ryu1b2d7f92015-02-24 17:45:02 -0800320 histories = _get_host_histories(afe, arguments)
J. Richard Barnette40ab60a2014-11-18 15:44:09 -0800321 if not histories:
322 print >>sys.stderr, 'FATAL: no valid hosts found'
323 sys.exit(1)
324 return histories
325
326
J. Richard Barnette048a6282014-12-12 11:30:23 -0800327def _validate_format_options(arguments):
328 """Check the options for what output format to use.
329
330 Enforce these rules:
331 * If neither --broken nor --working was used, then --oneline
332 becomes the selected format.
333 * If neither --broken nor --working was used, included both
334 working and broken DUTs.
335
336 @param arguments Parsed arguments object as returned by
337 ArgumentParser.parse_args().
338
339 """
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700340 if (not arguments.oneline and not arguments.diagnosis and
341 not arguments.full_history):
J. Richard Barnette048a6282014-12-12 11:30:23 -0800342 arguments.oneline = (not arguments.working and
343 not arguments.broken)
344 if not arguments.working and not arguments.broken:
345 arguments.working = True
346 arguments.broken = True
347
348
MK Ryu1b2d7f92015-02-24 17:45:02 -0800349def _validate_command(afe, arguments):
J. Richard Barnette784d1ba2014-11-18 14:52:43 -0800350 """Check that the command's arguments are valid.
351
352 This performs command line checking to enforce command line
353 rules that ArgumentParser can't handle. Additionally, this
354 handles calculation of default arguments/options when a simple
355 constant default won't do.
356
357 Areas checked:
358 * Check that a valid time range was provided, supplying
359 defaults as necessary.
360 * Identify invalid host names.
361
MK Ryu1b2d7f92015-02-24 17:45:02 -0800362 @param afe Autotest frontend
J. Richard Barnette784d1ba2014-11-18 14:52:43 -0800363 @param arguments Parsed arguments object as returned by
364 ArgumentParser.parse_args().
365 @return List of HostJobHistory objects for the hosts requested
366 on the command line.
367
368 """
369 _validate_time_range(arguments)
J. Richard Barnette048a6282014-12-12 11:30:23 -0800370 _validate_format_options(arguments)
MK Ryu1b2d7f92015-02-24 17:45:02 -0800371 return _validate_host_list(afe, arguments)
J. Richard Barnette784d1ba2014-11-18 14:52:43 -0800372
373
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700374def _parse_command(argv):
J. Richard Barnette784d1ba2014-11-18 14:52:43 -0800375 """Parse the command line arguments.
376
377 Create an argument parser for this command's syntax, parse the
378 command line, and return the result of the ArgumentParser
379 parse_args() method.
380
381 @param argv Standard command line argument vector; argv[0] is
382 assumed to be the command name.
383 @return Result returned by ArgumentParser.parse_args().
384
385 """
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700386 parser = argparse.ArgumentParser(
387 prog=argv[0],
J. Richard Barnettee62fa2c2014-11-06 17:44:04 -0800388 description='Report DUT status and execution history',
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700389 epilog='You can specify one or two of --since, --until, '
390 'and --duration, but not all three.\n'
391 'The date/time format is "YYYY-MM-DD HH:MM:SS".')
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700392 parser.add_argument('-s', '--since', type=status_history.parse_time,
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700393 metavar='DATE/TIME',
394 help='starting time for history display')
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700395 parser.add_argument('-u', '--until', type=status_history.parse_time,
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700396 metavar='DATE/TIME',
397 help='ending time for history display'
398 ' (default: now)')
399 parser.add_argument('-d', '--duration', type=int,
400 metavar='HOURS',
401 help='number of hours of history to display'
402 ' (default: %d)' % _DEFAULT_DURATION)
J. Richard Barnette048a6282014-12-12 11:30:23 -0800403
404 format_group = parser.add_mutually_exclusive_group()
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700405 format_group.add_argument('-f', '--full_history', action='store_true',
J. Richard Barnette048a6282014-12-12 11:30:23 -0800406 help='Display host history from most '
407 'to least recent for each DUT')
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700408 format_group.add_argument('-g', '--diagnosis', action='store_true',
409 help='Display host history for the '
410 'most recent DUT status change')
J. Richard Barnette048a6282014-12-12 11:30:23 -0800411 format_group.add_argument('-o', '--oneline', action='store_true',
J. Richard Barnette048a6282014-12-12 11:30:23 -0800412 help='Display host status summary')
413
414 parser.add_argument('-w', '--working', action='store_true',
J. Richard Barnette048a6282014-12-12 11:30:23 -0800415 help='List working devices by name only')
416 parser.add_argument('-n', '--broken', action='store_true',
J. Richard Barnette048a6282014-12-12 11:30:23 -0800417 help='List non-working devices by name only')
418
J. Richard Barnette40ab60a2014-11-18 15:44:09 -0800419 parser.add_argument('-b', '--board',
420 help='Display history for all DUTs '
421 'of the given board')
422 parser.add_argument('-p', '--pool',
423 help='Display history for all DUTs '
424 'in the given pool')
J. Richard Barnette048a6282014-12-12 11:30:23 -0800425 parser.add_argument('hostnames',
426 nargs='*',
427 help='host names of DUTs to report on')
MK Ryu1b2d7f92015-02-24 17:45:02 -0800428 parser.add_argument('--web',
429 help='Master autotest frontend hostname. If no value '
430 'is given, the one in global config will be used.',
431 default=None)
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700432 arguments = parser.parse_args(argv[1:])
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700433 return arguments
434
435
436def main(argv):
437 """Standard main() for command line processing.
438
439 @param argv Command line arguments (normally sys.argv).
440
441 """
442 arguments = _parse_command(argv)
MK Ryu1b2d7f92015-02-24 17:45:02 -0800443 afe = frontend.AFE(server=arguments.web)
444 history_list = _validate_command(afe, arguments)
J. Richard Barnette048a6282014-12-12 11:30:23 -0800445 if arguments.oneline:
446 _print_host_summaries(history_list, arguments)
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700447 else:
J. Richard Barnette048a6282014-12-12 11:30:23 -0800448 _print_hosts(history_list, arguments)
J. Richard Barnettec9b79332014-09-22 13:14:33 -0700449
450
451if __name__ == '__main__':
452 main(sys.argv)