blob: 2ac4ea1d05126c52c279aead064373d6a69a8381 [file] [log] [blame]
Caroline Tice48462062016-11-18 16:49:00 -08001#!/usr/bin/env python2
2"""Generate summary report for ChromeOS toolchain waterfalls."""
3
4# Desired future features (to be added):
5# - arguments to allow generating only the main waterfall report,
6# or only the rotating builder reports, or only the failures
7# report; or the waterfall reports without the failures report.
8# - Better way of figuring out which dates/builds to generate
9# reports for: probably an argument specifying a date or a date
10# range, then use something like the new buildbot utils to
11# query the build logs to find the right build numbers for the
12# builders for the specified dates.
13# - Store/get the json/data files in mobiletc-prebuild's x20 area.
14# - Update data in json file to reflect, for each testsuite, which
15# tests are not expected to run on which boards; update this
16# script to use that data appropriately.
17# - Make sure user's prodaccess is up-to-date before trying to use
18# this script.
19# - Add some nice formatting/highlighting to reports.
20
21from __future__ import print_function
22
Caroline Ticefaa3c552016-12-13 11:29:59 -080023import argparse
Caroline Ticee02e9f82016-12-01 13:14:41 -080024import getpass
Caroline Tice48462062016-11-18 16:49:00 -080025import json
26import os
Caroline Ticee02e9f82016-12-01 13:14:41 -080027import shutil
Caroline Tice48462062016-11-18 16:49:00 -080028import sys
29import time
30
31from cros_utils import command_executer
32
33# All the test suites whose data we might want for the reports.
34TESTS = (
35 ('bvt-inline', 'HWTest'),
36 ('bvt-cq', 'HWTest'),
37 ('toolchain-tests', 'HWTest'),
38 ('security', 'HWTest'),
39 ('kernel_daily_regression', 'HWTest'),
40 ('kernel_daily_benchmarks', 'HWTest'),)
41
42# The main waterfall builders, IN THE ORDER IN WHICH WE WANT THEM
43# LISTED IN THE REPORT.
44WATERFALL_BUILDERS = [
45 'amd64-gcc-toolchain', 'arm-gcc-toolchain', 'arm64-gcc-toolchain',
46 'x86-gcc-toolchain', 'amd64-llvm-toolchain', 'arm-llvm-toolchain',
47 'arm64-llvm-toolchain', 'x86-llvm-toolchain', 'amd64-llvm-next-toolchain',
48 'arm-llvm-next-toolchain', 'arm64-llvm-next-toolchain',
49 'x86-llvm-next-toolchain'
50]
51
Caroline Ticefaa3c552016-12-13 11:29:59 -080052DATA_DIR = '/usr/local/google2/cmtice/toolchain-utils/waterfall-report-data/'
53ARCHIVE_DIR = '/usr/local/google2/cmtice/toolchain-utils/waterfall-reports/'
54#DATA_DIR = '/google/data/rw/users/mo/mobiletc-prebuild/waterfall-report-data/'
55#ARCHIVE_DIR = '/google/data/rw/users/mo/mobiletc-prebuild/waterfall-reports/'
Caroline Tice48462062016-11-18 16:49:00 -080056DOWNLOAD_DIR = '/tmp/waterfall-logs'
Caroline Ticee02e9f82016-12-01 13:14:41 -080057MAX_SAVE_RECORDS = 7
Caroline Tice48462062016-11-18 16:49:00 -080058BUILD_DATA_FILE = '%s/build-data.txt' % DATA_DIR
Caroline Ticefaa3c552016-12-13 11:29:59 -080059GCC_ROTATING_BUILDER = 'gcc_toolchain'
60LLVM_ROTATING_BUILDER = 'llvm_next_toolchain'
61ROTATING_BUILDERS = [GCC_ROTATING_BUILDER, LLVM_ROTATING_BUILDER]
Caroline Tice48462062016-11-18 16:49:00 -080062
63# For int-to-string date conversion. Note, the index of the month in this
64# list needs to correspond to the month's integer value. i.e. 'Sep' must
65# be as MONTHS[9].
66MONTHS = [
67 '', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct',
68 'Nov', 'Dec'
69]
70
71
72def format_date(int_date):
73 """Convert an integer date to a string date. YYYYMMDD -> YYYY-MMM-DD"""
74
75 if int_date == 0:
76 return 'today'
77
78 tmp_date = int_date
79 day = tmp_date % 100
80 tmp_date = tmp_date / 100
81 month = tmp_date % 100
82 year = tmp_date / 100
83
84 month_str = MONTHS[month]
85 date_str = '%d-%s-%d' % (year, month_str, day)
86 return date_str
87
88
Caroline Ticee02e9f82016-12-01 13:14:41 -080089def EmailReport(report_file, report_type, date):
90 subject = '%s Waterfall Summary report, %s' % (report_type, date)
91 email_to = getpass.getuser()
Rahul Chaudhry213f3c02016-12-06 10:47:05 -080092 sendgmr_path = '/google/data/ro/projects/gws-sre/sendgmr'
93 command = ('%s --to=%s@google.com --subject="%s" --body_file=%s' %
94 (sendgmr_path, email_to, subject, report_file))
Caroline Ticee02e9f82016-12-01 13:14:41 -080095 command_executer.GetCommandExecuter().RunCommand(command)
96
97
98def PruneOldFailures(failure_dict, int_date):
99 earliest_date = int_date - MAX_SAVE_RECORDS
100 for suite in failure_dict:
101 suite_dict = failure_dict[suite]
102 test_keys_to_remove = []
103 for test in suite_dict:
104 test_dict = suite_dict[test]
105 msg_keys_to_remove = []
106 for msg in test_dict:
107 fails = test_dict[msg]
108 i = 0
109 while i < len(fails) and fails[i][0] <= earliest_date:
110 i += 1
111 new_fails = fails[i:]
112 test_dict[msg] = new_fails
113 if len(new_fails) == 0:
114 msg_keys_to_remove.append(msg)
115
116 for k in msg_keys_to_remove:
117 del test_dict[k]
118
119 suite_dict[test] = test_dict
120 if len(test_dict) == 0:
121 test_keys_to_remove.append(test)
122
123 for k in test_keys_to_remove:
124 del suite_dict[k]
125
126 failure_dict[suite] = suite_dict
127
128
Caroline Ticefaa3c552016-12-13 11:29:59 -0800129def GenerateFailuresReport(fail_dict, date):
130 filename = 'waterfall_report.failures.%s.txt' % date
131 date_string = format_date(date)
132 with open(filename, 'w') as out_file:
133 # Write failure report section.
134 out_file.write('\n\nSummary of Test Failures as of %s\n\n' % date_string)
135
136 # We want to sort the errors and output them in order of the ones that occur
137 # most often. So we have to collect the data about all of them, then sort
138 # it.
139 error_groups = []
140 for suite in fail_dict:
141 suite_dict = fail_dict[suite]
142 if suite_dict:
143 for test in suite_dict:
144 test_dict = suite_dict[test]
145 for err_msg in test_dict:
146 err_list = test_dict[err_msg]
147 sorted_list = sorted(err_list, key=lambda x: x[0], reverse=True)
148 err_group = [len(sorted_list), suite, test, err_msg, sorted_list]
149 error_groups.append(err_group)
150
151 # Sort the errors by the number of errors of each type. Then output them in
152 # order.
153 sorted_errors = sorted(error_groups, key=lambda x: x[0], reverse=True)
154 for i in range(0, len(sorted_errors)):
155 err_group = sorted_errors[i]
156 suite = err_group[1]
157 test = err_group[2]
158 err_msg = err_group[3]
159 err_list = err_group[4]
160 out_file.write('Suite: %s\n' % suite)
161 out_file.write(' %s (%d failures)\n' % (test, len(err_list)))
162 out_file.write(' (%s)\n' % err_msg)
163 for i in range(0, len(err_list)):
164 err = err_list[i]
165 out_file.write(' %s, %s, %s\n' % (format_date(err[0]), err[1],
166 err[2]))
167 out_file.write('\n')
168
169 print('Report generated in %s.' % filename)
170 return filename
171
172
173def GenerateWaterfallReport(report_dict, fail_dict, waterfall_type, date,
174 omit_failures):
Caroline Tice48462062016-11-18 16:49:00 -0800175 """Write out the actual formatted report."""
176
177 filename = 'waterfall_report.%s_waterfall.%s.txt' % (waterfall_type, date)
178
179 date_string = ''
180 date_list = report_dict['date']
181 num_dates = len(date_list)
182 i = 0
183 for d in date_list:
184 date_string += d
185 if i < num_dates - 1:
186 date_string += ', '
187 i += 1
188
189 if waterfall_type == 'main':
190 report_list = WATERFALL_BUILDERS
191 else:
192 report_list = report_dict.keys()
193
194 with open(filename, 'w') as out_file:
195 # Write Report Header
196 out_file.write('\nStatus of %s Waterfall Builds from %s\n\n' %
197 (waterfall_type, date_string))
198 out_file.write(' '
199 ' kernel kernel\n')
200 out_file.write(' Build bvt- bvt-cq '
201 'toolchain- security daily daily\n')
202 out_file.write(' status inline '
203 ' tests regression benchmarks\n')
204 out_file.write(' [P/ F/ DR]* [P/ F /DR]* '
205 '[P/ F/ DR]* [P/ F/ DR]* [P/ F/ DR]* [P/ F/ DR]*\n\n')
206
207 # Write daily waterfall status section.
208 for i in range(0, len(report_list)):
209 builder = report_list[i]
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800210 if builder == 'date':
211 continue
Caroline Tice48462062016-11-18 16:49:00 -0800212
213 if builder not in report_dict:
214 out_file.write('Unable to find information for %s.\n\n' % builder)
215 continue
216
217 build_dict = report_dict[builder]
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800218 status = build_dict.get('build_status', 'bad')
219 inline = build_dict.get('bvt-inline', '[??/ ?? /??]')
220 cq = build_dict.get('bvt-cq', '[??/ ?? /??]')
221 inline_color = build_dict.get('bvt-inline-color', '')
222 cq_color = build_dict.get('bvt-cq-color', '')
Caroline Tice48462062016-11-18 16:49:00 -0800223 if 'x86' not in builder:
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800224 toolchain = build_dict.get('toolchain-tests', '[??/ ?? /??]')
225 security = build_dict.get('security', '[??/ ?? /??]')
226 toolchain_color = build_dict.get('toolchain-tests-color', '')
227 security_color = build_dict.get('security-color', '')
Caroline Tice48462062016-11-18 16:49:00 -0800228 if 'gcc' in builder:
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800229 regression = build_dict.get('kernel_daily_regression', '[??/ ?? /??]')
230 bench = build_dict.get('kernel_daily_benchmarks', '[??/ ?? /??]')
231 regression_color = build_dict.get('kernel_daily_regression-color', '')
232 bench_color = build_dict.get('kernel_daily_benchmarks-color', '')
233 out_file.write(' %6s %6s'
234 ' %6s %6s %6s %6s\n' %
235 (inline_color, cq_color, toolchain_color,
236 security_color, regression_color, bench_color))
Caroline Tice48462062016-11-18 16:49:00 -0800237 out_file.write('%25s %3s %s %s %s %s %s %s\n' % (builder, status,
238 inline, cq,
239 toolchain, security,
240 regression, bench))
241 else:
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800242 out_file.write(' %6s %6s'
243 ' %6s %6s\n' % (inline_color, cq_color,
244 toolchain_color,
245 security_color))
Caroline Tice48462062016-11-18 16:49:00 -0800246 out_file.write('%25s %3s %s %s %s %s\n' % (builder, status, inline,
247 cq, toolchain, security))
248 else:
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800249 out_file.write(' %6s %6s\n' %
250 (inline_color, cq_color))
Caroline Tice48462062016-11-18 16:49:00 -0800251 out_file.write('%25s %3s %s %s\n' % (builder, status, inline, cq))
252 if 'build_link' in build_dict:
253 out_file.write('%s\n\n' % build_dict['build_link'])
254
255 out_file.write('\n\n*P = Number of tests in suite that Passed; F = '
256 'Number of tests in suite that Failed; DR = Number of tests'
257 ' in suite that Didn\'t Run.\n')
258
Caroline Ticefaa3c552016-12-13 11:29:59 -0800259 if omit_failures:
260 print('Report generated in %s.' % filename)
261 return filename
262
Caroline Tice48462062016-11-18 16:49:00 -0800263 # Write failure report section.
264 out_file.write('\n\nSummary of Test Failures as of %s\n\n' % date_string)
265
266 # We want to sort the errors and output them in order of the ones that occur
267 # most often. So we have to collect the data about all of them, then sort
268 # it.
269 error_groups = []
270 for suite in fail_dict:
271 suite_dict = fail_dict[suite]
272 if suite_dict:
273 for test in suite_dict:
274 test_dict = suite_dict[test]
275 for err_msg in test_dict:
276 err_list = test_dict[err_msg]
277 sorted_list = sorted(err_list, key=lambda x: x[0], reverse=True)
278 err_group = [len(sorted_list), suite, test, err_msg, sorted_list]
279 error_groups.append(err_group)
280
281 # Sort the errors by the number of errors of each type. Then output them in
282 # order.
283 sorted_errors = sorted(error_groups, key=lambda x: x[0], reverse=True)
284 for i in range(0, len(sorted_errors)):
285 err_group = sorted_errors[i]
286 suite = err_group[1]
287 test = err_group[2]
288 err_msg = err_group[3]
289 err_list = err_group[4]
290 out_file.write('Suite: %s\n' % suite)
291 out_file.write(' %s (%d failures)\n' % (test, len(err_list)))
292 out_file.write(' (%s)\n' % err_msg)
293 for i in range(0, len(err_list)):
294 err = err_list[i]
295 out_file.write(' %s, %s, %s\n' % (format_date(err[0]), err[1],
296 err[2]))
297 out_file.write('\n')
298
299 print('Report generated in %s.' % filename)
Caroline Ticee02e9f82016-12-01 13:14:41 -0800300 return filename
Caroline Tice48462062016-11-18 16:49:00 -0800301
302
303def UpdateReport(report_dict, builder, test, report_date, build_link,
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800304 test_summary, board, color):
Caroline Tice48462062016-11-18 16:49:00 -0800305 """Update the data in our report dictionary with current test's data."""
306
307 if 'date' not in report_dict:
308 report_dict['date'] = [report_date]
309 elif report_date not in report_dict['date']:
310 # It is possible that some of the builders started/finished on different
311 # days, so we allow for multiple dates in the reports.
312 report_dict['date'].append(report_date)
313
314 build_key = ''
Caroline Ticefaa3c552016-12-13 11:29:59 -0800315 if builder == GCC_ROTATING_BUILDER:
Caroline Tice48462062016-11-18 16:49:00 -0800316 build_key = '%s-gcc-toolchain' % board
Caroline Ticefaa3c552016-12-13 11:29:59 -0800317 elif builder == LLVM_ROTATING_BUILDER:
318 build_key = '%s-llvm-next-toolchain' % board
Caroline Tice48462062016-11-18 16:49:00 -0800319 else:
320 build_key = builder
321
322 if build_key not in report_dict.keys():
323 build_dict = dict()
324 else:
325 build_dict = report_dict[build_key]
326
327 if 'build_link' not in build_dict:
328 build_dict['build_link'] = build_link
329
330 if 'date' not in build_dict:
331 build_dict['date'] = report_date
332
333 if 'board' in build_dict and build_dict['board'] != board:
334 raise RuntimeError('Error: Two different boards (%s,%s) in one build (%s)!'
335 % (board, build_dict['board'], build_link))
336 build_dict['board'] = board
337
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800338 color_key = '%s-color' % test
339 build_dict[color_key] = color
340
Caroline Tice48462062016-11-18 16:49:00 -0800341 # Check to see if we already have a build status for this build_key
342 status = ''
343 if 'build_status' in build_dict.keys():
344 # Use current build_status, unless current test failed (see below).
345 status = build_dict['build_status']
346
347 if not test_summary:
348 # Current test data was not available, so something was bad with build.
349 build_dict['build_status'] = 'bad'
350 build_dict[test] = '[ no data ]'
351 else:
352 build_dict[test] = test_summary
353 if not status:
354 # Current test ok; no other data, so assume build was ok.
355 build_dict['build_status'] = 'ok'
356
357 report_dict[build_key] = build_dict
358
359
360def UpdateBuilds(builds):
361 """Update the data in our build-data.txt file."""
362
363 # The build data file records the last build number for which we
364 # generated a report. When we generate the next report, we read
365 # this data and increment it to get the new data; when we finish
366 # generating the reports, we write the updated values into this file.
367 # NOTE: One side effect of doing this at the end: If the script
368 # fails in the middle of generating a report, this data does not get
369 # updated.
370 with open(BUILD_DATA_FILE, 'w') as fp:
371 gcc_max = 0
372 llvm_max = 0
373 for b in builds:
Caroline Ticefaa3c552016-12-13 11:29:59 -0800374 if b[0] == GCC_ROTATING_BUILDER:
Caroline Tice48462062016-11-18 16:49:00 -0800375 gcc_max = max(gcc_max, b[1])
Caroline Ticefaa3c552016-12-13 11:29:59 -0800376 elif b[0] == LLVM_ROTATING_BUILDER:
Caroline Tice48462062016-11-18 16:49:00 -0800377 llvm_max = max(llvm_max, b[1])
378 else:
379 fp.write('%s,%d\n' % (b[0], b[1]))
380 if gcc_max > 0:
Caroline Ticefaa3c552016-12-13 11:29:59 -0800381 fp.write('%s,%d\n' % (GCC_ROTATING_BUILDER, gcc_max))
Caroline Tice48462062016-11-18 16:49:00 -0800382 if llvm_max > 0:
Caroline Ticefaa3c552016-12-13 11:29:59 -0800383 fp.write('%s,%d\n' % (LLVM_ROTATING_BUILDER, llvm_max))
Caroline Tice48462062016-11-18 16:49:00 -0800384
385
386def GetBuilds():
387 """Read build-data.txt to determine values for current report."""
388
389 # Read the values of the last builds used to generate a report, and
390 # increment them appropriately, to get values for generating the
391 # current report. (See comments in UpdateBuilds).
392 with open(BUILD_DATA_FILE, 'r') as fp:
393 lines = fp.readlines()
394
395 builds = []
396 for l in lines:
397 l = l.rstrip()
398 words = l.split(',')
399 builder = words[0]
400 build = int(words[1])
401 builds.append((builder, build + 1))
402 # NOTE: We are assuming here that there are always 2 daily builds in
403 # each of the rotating builders. I am not convinced this is a valid
404 # assumption.
Caroline Ticefaa3c552016-12-13 11:29:59 -0800405 if builder in ROTATING_BUILDERS:
Caroline Tice48462062016-11-18 16:49:00 -0800406 builds.append((builder, build + 2))
407
408 return builds
409
410
411def RecordFailures(failure_dict, platform, suite, builder, int_date, log_file,
412 build_num, failed):
413 """Read and update the stored data about test failures."""
414
415 # Get the dictionary for this particular test suite from the failures
416 # dictionary.
417 suite_dict = failure_dict[suite]
418
419 # Read in the entire log file for this test/build.
420 with open(log_file, 'r') as in_file:
421 lines = in_file.readlines()
422
423 # Update the entries in the failure dictionary for each test within this suite
424 # that failed.
425 for test in failed:
426 # Check to see if there is already an entry in the suite dictionary for this
427 # test; if so use that, otherwise create a new entry.
428 if test in suite_dict:
429 test_dict = suite_dict[test]
430 else:
431 test_dict = dict()
432 # Parse the lines from the log file, looking for lines that indicate this
433 # test failed.
434 msg = ''
435 for l in lines:
436 words = l.split()
437 if len(words) < 3:
438 continue
439 if ((words[0] == test and words[1] == 'ERROR:') or
440 (words[0] == 'provision' and words[1] == 'FAIL:')):
441 words = words[2:]
442 # Get the error message for the failure.
443 msg = ' '.join(words)
444 if not msg:
445 msg = 'Unknown_Error'
446
447 # Look for an existing entry for this error message in the test dictionary.
448 # If found use that, otherwise create a new entry for this error message.
449 if msg in test_dict:
450 error_list = test_dict[msg]
451 else:
452 error_list = list()
453 # Create an entry for this new failure
454 new_item = [int_date, platform, builder, build_num]
455 # Add this failure to the error list if it's not already there.
456 if new_item not in error_list:
457 error_list.append([int_date, platform, builder, build_num])
458 # Sort the error list by date.
459 error_list.sort(key=lambda x: x[0])
460 # Calculate the earliest date to save; delete records for older failures.
461 earliest_date = int_date - MAX_SAVE_RECORDS
462 i = 0
Caroline Ticee02e9f82016-12-01 13:14:41 -0800463 while i < len(error_list) and error_list[i][0] <= earliest_date:
Caroline Tice48462062016-11-18 16:49:00 -0800464 i += 1
465 if i > 0:
466 error_list = error_list[i:]
467 # Save the error list in the test's dictionary, keyed on error_msg.
468 test_dict[msg] = error_list
469
470 # Save the updated test dictionary in the test_suite dictionary.
471 suite_dict[test] = test_dict
472
473 # Save the updated test_suite dictionary in the failure dictionary.
474 failure_dict[suite] = suite_dict
475
476
477def ParseLogFile(log_file, test_data_dict, failure_dict, test, builder,
478 build_num, build_link):
479 """Parse the log file from the given builder, build_num and test.
480
481 Also adds the results for this test to our test results dictionary,
482 and calls RecordFailures, to update our test failure data.
483 """
484
485 lines = []
486 with open(log_file, 'r') as infile:
487 lines = infile.readlines()
488
489 passed = {}
490 failed = {}
491 not_run = {}
492 date = ''
493 status = ''
494 board = ''
495 num_provision_errors = 0
496 build_ok = True
497 afe_line = ''
498
499 for line in lines:
500 if line.rstrip() == '<title>404 Not Found</title>':
501 print('Warning: File for %s (build number %d), %s was not found.' %
502 (builder, build_num, test))
503 build_ok = False
504 break
505 if '[ PASSED ]' in line:
506 test_name = line.split()[0]
507 if test_name != 'Suite':
508 passed[test_name] = True
509 elif '[ FAILED ]' in line:
510 test_name = line.split()[0]
511 if test_name == 'provision':
512 num_provision_errors += 1
513 not_run[test_name] = True
514 elif test_name != 'Suite':
515 failed[test_name] = True
516 elif line.startswith('started: '):
517 date = line.rstrip()
518 date = date[9:]
519 date_obj = time.strptime(date, '%a %b %d %H:%M:%S %Y')
520 int_date = (
521 date_obj.tm_year * 10000 + date_obj.tm_mon * 100 + date_obj.tm_mday)
522 date = time.strftime('%a %b %d %Y', date_obj)
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800523 elif not status and line.startswith('status: '):
Caroline Tice48462062016-11-18 16:49:00 -0800524 status = line.rstrip()
525 words = status.split(':')
526 status = words[-1]
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800527 elif line.find('Suite passed with a warning') != -1:
528 status = 'WARNING'
Caroline Tice48462062016-11-18 16:49:00 -0800529 elif line.startswith('@@@STEP_LINK@Link to suite@'):
530 afe_line = line.rstrip()
531 words = afe_line.split('@')
532 for w in words:
533 if w.startswith('http'):
534 afe_line = w
535 afe_line = afe_line.replace('&amp;', '&')
536 elif 'INFO: RunCommand:' in line:
537 words = line.split()
538 for i in range(0, len(words) - 1):
539 if words[i] == '--board':
540 board = words[i + 1]
541
542 test_dict = test_data_dict[test]
543 test_list = test_dict['tests']
544
545 if build_ok:
546 for t in test_list:
547 if not t in passed and not t in failed:
548 not_run[t] = True
549
550 total_pass = len(passed)
551 total_fail = len(failed)
552 total_notrun = len(not_run)
553
554 else:
555 total_pass = 0
556 total_fail = 0
557 total_notrun = 0
558 status = 'Not found.'
559 if not build_ok:
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800560 return [], date, board, 0, ' '
Caroline Tice48462062016-11-18 16:49:00 -0800561
562 build_dict = dict()
563 build_dict['id'] = build_num
564 build_dict['builder'] = builder
565 build_dict['date'] = date
566 build_dict['build_link'] = build_link
567 build_dict['total_pass'] = total_pass
568 build_dict['total_fail'] = total_fail
569 build_dict['total_not_run'] = total_notrun
570 build_dict['afe_job_link'] = afe_line
571 build_dict['provision_errors'] = num_provision_errors
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800572 if status.strip() == 'SUCCESS':
573 build_dict['color'] = 'green '
574 elif status.strip() == 'FAILURE':
575 build_dict['color'] = ' red '
576 elif status.strip() == 'WARNING':
577 build_dict['color'] = 'orange'
578 else:
579 build_dict['color'] = ' '
Caroline Tice48462062016-11-18 16:49:00 -0800580
581 # Use YYYYMMDD (integer) as the build record key
582 if build_ok:
583 if board in test_dict:
584 board_dict = test_dict[board]
585 else:
586 board_dict = dict()
587 board_dict[int_date] = build_dict
588
589 # Only keep the last 5 records (based on date)
590 keys_list = board_dict.keys()
591 if len(keys_list) > MAX_SAVE_RECORDS:
592 min_key = min(keys_list)
593 del board_dict[min_key]
594
595 # Make sure changes get back into the main dictionary
596 test_dict[board] = board_dict
597 test_data_dict[test] = test_dict
598
599 if len(failed) > 0:
600 RecordFailures(failure_dict, board, test, builder, int_date, log_file,
601 build_num, failed)
602
603 summary_result = '[%2d/ %2d/ %2d]' % (total_pass, total_fail, total_notrun)
604
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800605 return summary_result, date, board, int_date, build_dict['color']
Caroline Tice48462062016-11-18 16:49:00 -0800606
607
608def DownloadLogFile(builder, buildnum, test, test_family):
609
610 ce = command_executer.GetCommandExecuter()
611 os.system('mkdir -p %s/%s/%s' % (DOWNLOAD_DIR, builder, test))
Caroline Ticefaa3c552016-12-13 11:29:59 -0800612 if builder in ROTATING_BUILDERS:
Caroline Tice48462062016-11-18 16:49:00 -0800613 source = ('https://uberchromegw.corp.google.com/i/chromiumos.tryserver'
614 '/builders/%s/builds/%d/steps/%s%%20%%5B%s%%5D/logs/stdio' %
615 (builder, buildnum, test_family, test))
616 build_link = ('https://uberchromegw.corp.google.com/i/chromiumos.tryserver'
617 '/builders/%s/builds/%d' % (builder, buildnum))
618 else:
619 source = ('https://uberchromegw.corp.google.com/i/chromeos/builders/%s/'
620 'builds/%d/steps/%s%%20%%5B%s%%5D/logs/stdio' %
621 (builder, buildnum, test_family, test))
622 build_link = ('https://uberchromegw.corp.google.com/i/chromeos/builders/%s'
623 '/builds/%d' % (builder, buildnum))
624
625 target = '%s/%s/%s/%d' % (DOWNLOAD_DIR, builder, test, buildnum)
626 if not os.path.isfile(target) or os.path.getsize(target) == 0:
627 cmd = 'sso_client %s > %s' % (source, target)
628 status = ce.RunCommand(cmd)
629 if status != 0:
630 return '', ''
631
632 return target, build_link
633
634
Manoj Gupta63824522016-12-14 11:05:18 -0800635# Check for prodaccess.
636def CheckProdAccess():
637 status, output, _ = command_executer.GetCommandExecuter().RunCommandWOutput(
638 'prodcertstatus')
639 if status != 0:
640 return False
641 # Verify that status is not expired
642 if 'expires' in output:
643 return True
644 return False
645
646
Caroline Ticefaa3c552016-12-13 11:29:59 -0800647def ValidOptions(parser, options):
648 too_many_options = False
649 if options.main:
650 if options.rotating or options.failures_report:
651 too_many_options = True
652 elif options.rotating and options.failures_report:
653 too_many_options = True
654
655 if too_many_options:
656 parser.error('Can only specify one of --main, --rotating or'
657 ' --failures_report.')
658
659 conflicting_failure_options = False
660 if options.failures_report and options.omit_failures:
661 conflicting_failure_options = True
662 parser.error('Cannot specify both --failures_report and --omit_failures.')
663
664 return not too_many_options and not conflicting_failure_options
665
666
667def Main(argv):
Caroline Tice48462062016-11-18 16:49:00 -0800668 """Main function for this script."""
Caroline Ticefaa3c552016-12-13 11:29:59 -0800669 parser = argparse.ArgumentParser()
670 parser.add_argument(
671 '--main',
672 dest='main',
673 default=False,
674 action='store_true',
675 help='Generate report only for main waterfall '
676 'builders.')
677 parser.add_argument(
678 '--rotating',
679 dest='rotating',
680 default=False,
681 action='store_true',
682 help='Generate report only for rotating builders.')
683 parser.add_argument(
684 '--failures_report',
685 dest='failures_report',
686 default=False,
687 action='store_true',
688 help='Only generate the failures section of the report.')
689 parser.add_argument(
690 '--omit_failures',
691 dest='omit_failures',
692 default=False,
693 action='store_true',
694 help='Do not generate the failures section of the report.')
695 parser.add_argument(
696 '--no_update',
697 dest='no_update',
698 default=False,
699 action='store_true',
700 help='Run reports, but do not update the data files.')
701
702 options = parser.parse_args(argv)
703
704 if not ValidOptions(parser, options):
705 return 1
706
707 main_only = options.main
708 rotating_only = options.rotating
709 failures_report = options.failures_report
710 omit_failures = options.omit_failures
Caroline Tice48462062016-11-18 16:49:00 -0800711
712 test_data_dict = dict()
713 failure_dict = dict()
Manoj Gupta63824522016-12-14 11:05:18 -0800714
715 prod_access = CheckProdAccess()
716 if not prod_access:
717 print('ERROR: Please run prodaccess first.')
718 return
719
Caroline Tice48462062016-11-18 16:49:00 -0800720 with open('%s/waterfall-test-data.json' % DATA_DIR, 'r') as input_file:
721 test_data_dict = json.load(input_file)
722
723 with open('%s/test-failure-data.json' % DATA_DIR, 'r') as fp:
724 failure_dict = json.load(fp)
725
726 builds = GetBuilds()
727
728 waterfall_report_dict = dict()
729 rotating_report_dict = dict()
730 int_date = 0
731 for test_desc in TESTS:
732 test, test_family = test_desc
733 for build in builds:
734 (builder, buildnum) = build
735 if test.startswith('kernel') and 'llvm' in builder:
736 continue
737 if 'x86' in builder and not test.startswith('bvt'):
738 continue
739 target, build_link = DownloadLogFile(builder, buildnum, test, test_family)
740
741 if os.path.exists(target):
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800742 test_summary, report_date, board, tmp_date, color = ParseLogFile(
Caroline Tice48462062016-11-18 16:49:00 -0800743 target, test_data_dict, failure_dict, test, builder, buildnum,
744 build_link)
745
746 if tmp_date != 0:
747 int_date = tmp_date
748
749 if builder in ROTATING_BUILDERS:
750 UpdateReport(rotating_report_dict, builder, test, report_date,
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800751 build_link, test_summary, board, color)
Caroline Tice48462062016-11-18 16:49:00 -0800752 else:
753 UpdateReport(waterfall_report_dict, builder, test, report_date,
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800754 build_link, test_summary, board, color)
Caroline Tice48462062016-11-18 16:49:00 -0800755
Caroline Ticee02e9f82016-12-01 13:14:41 -0800756 PruneOldFailures(failure_dict, int_date)
757
Caroline Ticefaa3c552016-12-13 11:29:59 -0800758 if waterfall_report_dict and not rotating_only and not failures_report:
Caroline Ticee02e9f82016-12-01 13:14:41 -0800759 main_report = GenerateWaterfallReport(waterfall_report_dict, failure_dict,
Caroline Ticefaa3c552016-12-13 11:29:59 -0800760 'main', int_date, omit_failures)
Caroline Ticee02e9f82016-12-01 13:14:41 -0800761 EmailReport(main_report, 'Main', format_date(int_date))
762 shutil.copy(main_report, ARCHIVE_DIR)
Caroline Ticefaa3c552016-12-13 11:29:59 -0800763 if rotating_report_dict and not main_only and not failures_report:
Caroline Ticee02e9f82016-12-01 13:14:41 -0800764 rotating_report = GenerateWaterfallReport(rotating_report_dict,
765 failure_dict, 'rotating',
Caroline Ticefaa3c552016-12-13 11:29:59 -0800766 int_date, omit_failures)
Caroline Ticee02e9f82016-12-01 13:14:41 -0800767 EmailReport(rotating_report, 'Rotating', format_date(int_date))
768 shutil.copy(rotating_report, ARCHIVE_DIR)
Caroline Tice48462062016-11-18 16:49:00 -0800769
Caroline Ticefaa3c552016-12-13 11:29:59 -0800770 if failures_report:
771 failures_report = GenerateFailuresReport(failure_dict, int_date)
772 EmailReport(failures_report, 'Failures', format_date(int_date))
773 shutil.copy(failures_report, ARCHIVE_DIR)
Caroline Tice48462062016-11-18 16:49:00 -0800774
Caroline Ticefaa3c552016-12-13 11:29:59 -0800775 if not options.no_update:
776 with open('%s/waterfall-test-data.json' % DATA_DIR, 'w') as out_file:
777 json.dump(test_data_dict, out_file, indent=2)
Caroline Tice48462062016-11-18 16:49:00 -0800778
Caroline Ticefaa3c552016-12-13 11:29:59 -0800779 with open('%s/test-failure-data.json' % DATA_DIR, 'w') as out_file:
780 json.dump(failure_dict, out_file, indent=2)
781
782 UpdateBuilds(builds)
Caroline Tice48462062016-11-18 16:49:00 -0800783
784
785if __name__ == '__main__':
Caroline Ticefaa3c552016-12-13 11:29:59 -0800786 Main(sys.argv[1:])
Caroline Tice48462062016-11-18 16:49:00 -0800787 sys.exit(0)