blob: 9fbb5637c0ccae62092aa7f31a31e020c2f6eba7 [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
Yunlian Jiang6b8bacb2016-12-15 14:53:39 -080027import re
Caroline Ticee02e9f82016-12-01 13:14:41 -080028import shutil
Caroline Tice48462062016-11-18 16:49:00 -080029import sys
30import time
31
32from cros_utils import command_executer
33
34# All the test suites whose data we might want for the reports.
Caroline Ticef6ef4392017-04-06 17:16:05 -070035TESTS = (('bvt-inline', 'HWTest'), ('bvt-cq', 'HWTest'),
36 ('toolchain-tests', 'HWTest'), ('security', 'HWTest'),
37 ('kernel_daily_regression', 'HWTest'), ('kernel_daily_benchmarks',
38 'HWTest'),)
Caroline Tice48462062016-11-18 16:49:00 -080039
40# The main waterfall builders, IN THE ORDER IN WHICH WE WANT THEM
41# LISTED IN THE REPORT.
42WATERFALL_BUILDERS = [
43 'amd64-gcc-toolchain', 'arm-gcc-toolchain', 'arm64-gcc-toolchain',
44 'x86-gcc-toolchain', 'amd64-llvm-toolchain', 'arm-llvm-toolchain',
45 'arm64-llvm-toolchain', 'x86-llvm-toolchain', 'amd64-llvm-next-toolchain',
46 'arm-llvm-next-toolchain', 'arm64-llvm-next-toolchain',
47 'x86-llvm-next-toolchain'
48]
49
Manoj Gupta9c0b33b2016-12-15 14:52:25 -080050DATA_DIR = '/google/data/rw/users/mo/mobiletc-prebuild/waterfall-report-data/'
51ARCHIVE_DIR = '/google/data/rw/users/mo/mobiletc-prebuild/waterfall-reports/'
Caroline Tice48462062016-11-18 16:49:00 -080052DOWNLOAD_DIR = '/tmp/waterfall-logs'
Caroline Ticee02e9f82016-12-01 13:14:41 -080053MAX_SAVE_RECORDS = 7
Caroline Tice48462062016-11-18 16:49:00 -080054BUILD_DATA_FILE = '%s/build-data.txt' % DATA_DIR
Caroline Ticefaa3c552016-12-13 11:29:59 -080055GCC_ROTATING_BUILDER = 'gcc_toolchain'
56LLVM_ROTATING_BUILDER = 'llvm_next_toolchain'
57ROTATING_BUILDERS = [GCC_ROTATING_BUILDER, LLVM_ROTATING_BUILDER]
Caroline Tice48462062016-11-18 16:49:00 -080058
59# For int-to-string date conversion. Note, the index of the month in this
60# list needs to correspond to the month's integer value. i.e. 'Sep' must
61# be as MONTHS[9].
62MONTHS = [
63 '', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct',
64 'Nov', 'Dec'
65]
66
67
68def format_date(int_date):
69 """Convert an integer date to a string date. YYYYMMDD -> YYYY-MMM-DD"""
70
71 if int_date == 0:
72 return 'today'
73
74 tmp_date = int_date
75 day = tmp_date % 100
76 tmp_date = tmp_date / 100
77 month = tmp_date % 100
78 year = tmp_date / 100
79
80 month_str = MONTHS[month]
81 date_str = '%d-%s-%d' % (year, month_str, day)
82 return date_str
83
84
Caroline Ticee02e9f82016-12-01 13:14:41 -080085def EmailReport(report_file, report_type, date):
86 subject = '%s Waterfall Summary report, %s' % (report_type, date)
87 email_to = getpass.getuser()
Rahul Chaudhry213f3c02016-12-06 10:47:05 -080088 sendgmr_path = '/google/data/ro/projects/gws-sre/sendgmr'
89 command = ('%s --to=%s@google.com --subject="%s" --body_file=%s' %
90 (sendgmr_path, email_to, subject, report_file))
Caroline Ticee02e9f82016-12-01 13:14:41 -080091 command_executer.GetCommandExecuter().RunCommand(command)
92
93
94def PruneOldFailures(failure_dict, int_date):
95 earliest_date = int_date - MAX_SAVE_RECORDS
96 for suite in failure_dict:
97 suite_dict = failure_dict[suite]
98 test_keys_to_remove = []
99 for test in suite_dict:
100 test_dict = suite_dict[test]
101 msg_keys_to_remove = []
102 for msg in test_dict:
103 fails = test_dict[msg]
104 i = 0
105 while i < len(fails) and fails[i][0] <= earliest_date:
106 i += 1
107 new_fails = fails[i:]
108 test_dict[msg] = new_fails
109 if len(new_fails) == 0:
110 msg_keys_to_remove.append(msg)
111
112 for k in msg_keys_to_remove:
113 del test_dict[k]
114
115 suite_dict[test] = test_dict
116 if len(test_dict) == 0:
117 test_keys_to_remove.append(test)
118
119 for k in test_keys_to_remove:
120 del suite_dict[k]
121
122 failure_dict[suite] = suite_dict
123
124
Yunlian Jiang6b8bacb2016-12-15 14:53:39 -0800125def GetBuildID(build_bot, date):
126 """Get the build id for a build_bot at a given date."""
Caroline Ticef6ef4392017-04-06 17:16:05 -0700127 day = '{day:02d}'.format(day=date % 100)
128 mon = MONTHS[date / 100 % 100]
Yunlian Jiang6b8bacb2016-12-15 14:53:39 -0800129 date_string = mon + ' ' + day
130 if build_bot in WATERFALL_BUILDERS:
131 url = 'https://uberchromegw.corp.google.com/i/chromeos/' + \
132 'builders/%s?numbuilds=200' % build_bot
133 if build_bot in ROTATING_BUILDERS:
134 url = 'https://uberchromegw.corp.google.com/i/chromiumos.tryserver/' + \
135 'builders/%s?numbuilds=200' % build_bot
Caroline Ticef6ef4392017-04-06 17:16:05 -0700136 command = 'sso_client %s' % url
Yunlian Jiang6b8bacb2016-12-15 14:53:39 -0800137 retval = 1
138 retry_time = 3
139 while retval and retry_time:
140 retval, output, _ = \
141 command_executer.GetCommandExecuter().RunCommandWOutput(command, \
142 print_to_console=False)
143 retry_time -= 1
144
145 if retval:
146 return []
147
148 out = output.split('\n')
149 line_num = 0
150 build_id = []
151 # Parse the output like this
152 # <td>Dec 14 10:55</td>
153 # <td class="revision">??</td>
154 # <td failure</td><td><a href="../builders/gcc_toolchain/builds/109">#109</a>
155 while line_num < len(out):
156 if date_string in out[line_num]:
157 if line_num + 2 < len(out):
158 build_num_line = out[line_num + 2]
159 raw_num = re.findall(r'builds/\d+', build_num_line)
160 # raw_num is ['builds/109'] in the example.
161 if raw_num:
162 build_id.append(int(raw_num[0].split('/')[1]))
163 line_num += 1
164 return build_id
165
166
Caroline Ticefaa3c552016-12-13 11:29:59 -0800167def GenerateFailuresReport(fail_dict, date):
168 filename = 'waterfall_report.failures.%s.txt' % date
169 date_string = format_date(date)
170 with open(filename, 'w') as out_file:
171 # Write failure report section.
172 out_file.write('\n\nSummary of Test Failures as of %s\n\n' % date_string)
173
174 # We want to sort the errors and output them in order of the ones that occur
175 # most often. So we have to collect the data about all of them, then sort
176 # it.
177 error_groups = []
178 for suite in fail_dict:
179 suite_dict = fail_dict[suite]
180 if suite_dict:
181 for test in suite_dict:
182 test_dict = suite_dict[test]
183 for err_msg in test_dict:
184 err_list = test_dict[err_msg]
185 sorted_list = sorted(err_list, key=lambda x: x[0], reverse=True)
186 err_group = [len(sorted_list), suite, test, err_msg, sorted_list]
187 error_groups.append(err_group)
188
189 # Sort the errors by the number of errors of each type. Then output them in
190 # order.
191 sorted_errors = sorted(error_groups, key=lambda x: x[0], reverse=True)
192 for i in range(0, len(sorted_errors)):
193 err_group = sorted_errors[i]
194 suite = err_group[1]
195 test = err_group[2]
196 err_msg = err_group[3]
197 err_list = err_group[4]
198 out_file.write('Suite: %s\n' % suite)
199 out_file.write(' %s (%d failures)\n' % (test, len(err_list)))
200 out_file.write(' (%s)\n' % err_msg)
201 for i in range(0, len(err_list)):
202 err = err_list[i]
203 out_file.write(' %s, %s, %s\n' % (format_date(err[0]), err[1],
204 err[2]))
205 out_file.write('\n')
206
207 print('Report generated in %s.' % filename)
208 return filename
209
210
211def GenerateWaterfallReport(report_dict, fail_dict, waterfall_type, date,
212 omit_failures):
Caroline Tice48462062016-11-18 16:49:00 -0800213 """Write out the actual formatted report."""
214
215 filename = 'waterfall_report.%s_waterfall.%s.txt' % (waterfall_type, date)
216
217 date_string = ''
218 date_list = report_dict['date']
219 num_dates = len(date_list)
220 i = 0
221 for d in date_list:
222 date_string += d
223 if i < num_dates - 1:
224 date_string += ', '
225 i += 1
226
227 if waterfall_type == 'main':
228 report_list = WATERFALL_BUILDERS
229 else:
230 report_list = report_dict.keys()
231
232 with open(filename, 'w') as out_file:
233 # Write Report Header
234 out_file.write('\nStatus of %s Waterfall Builds from %s\n\n' %
235 (waterfall_type, date_string))
236 out_file.write(' '
237 ' kernel kernel\n')
238 out_file.write(' Build bvt- bvt-cq '
239 'toolchain- security daily daily\n')
240 out_file.write(' status inline '
241 ' tests regression benchmarks\n')
242 out_file.write(' [P/ F/ DR]* [P/ F /DR]* '
243 '[P/ F/ DR]* [P/ F/ DR]* [P/ F/ DR]* [P/ F/ DR]*\n\n')
244
245 # Write daily waterfall status section.
246 for i in range(0, len(report_list)):
247 builder = report_list[i]
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800248 if builder == 'date':
249 continue
Caroline Tice48462062016-11-18 16:49:00 -0800250
251 if builder not in report_dict:
252 out_file.write('Unable to find information for %s.\n\n' % builder)
253 continue
254
255 build_dict = report_dict[builder]
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800256 status = build_dict.get('build_status', 'bad')
257 inline = build_dict.get('bvt-inline', '[??/ ?? /??]')
258 cq = build_dict.get('bvt-cq', '[??/ ?? /??]')
259 inline_color = build_dict.get('bvt-inline-color', '')
260 cq_color = build_dict.get('bvt-cq-color', '')
Caroline Tice48462062016-11-18 16:49:00 -0800261 if 'x86' not in builder:
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800262 toolchain = build_dict.get('toolchain-tests', '[??/ ?? /??]')
263 security = build_dict.get('security', '[??/ ?? /??]')
264 toolchain_color = build_dict.get('toolchain-tests-color', '')
265 security_color = build_dict.get('security-color', '')
Caroline Tice48462062016-11-18 16:49:00 -0800266 if 'gcc' in builder:
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800267 regression = build_dict.get('kernel_daily_regression', '[??/ ?? /??]')
268 bench = build_dict.get('kernel_daily_benchmarks', '[??/ ?? /??]')
269 regression_color = build_dict.get('kernel_daily_regression-color', '')
270 bench_color = build_dict.get('kernel_daily_benchmarks-color', '')
271 out_file.write(' %6s %6s'
272 ' %6s %6s %6s %6s\n' %
273 (inline_color, cq_color, toolchain_color,
274 security_color, regression_color, bench_color))
Caroline Ticef6ef4392017-04-06 17:16:05 -0700275 out_file.write('%25s %3s %s %s %s %s %s %s\n' %
276 (builder, status, inline, cq, toolchain, security,
277 regression, bench))
Caroline Tice48462062016-11-18 16:49:00 -0800278 else:
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800279 out_file.write(' %6s %6s'
Caroline Ticef6ef4392017-04-06 17:16:05 -0700280 ' %6s %6s\n' %
281 (inline_color, cq_color, toolchain_color,
282 security_color))
Caroline Tice48462062016-11-18 16:49:00 -0800283 out_file.write('%25s %3s %s %s %s %s\n' % (builder, status, inline,
284 cq, toolchain, security))
285 else:
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800286 out_file.write(' %6s %6s\n' %
287 (inline_color, cq_color))
Caroline Tice48462062016-11-18 16:49:00 -0800288 out_file.write('%25s %3s %s %s\n' % (builder, status, inline, cq))
289 if 'build_link' in build_dict:
290 out_file.write('%s\n\n' % build_dict['build_link'])
291
292 out_file.write('\n\n*P = Number of tests in suite that Passed; F = '
293 'Number of tests in suite that Failed; DR = Number of tests'
294 ' in suite that Didn\'t Run.\n')
295
Caroline Ticefaa3c552016-12-13 11:29:59 -0800296 if omit_failures:
297 print('Report generated in %s.' % filename)
298 return filename
299
Caroline Tice48462062016-11-18 16:49:00 -0800300 # Write failure report section.
301 out_file.write('\n\nSummary of Test Failures as of %s\n\n' % date_string)
302
303 # We want to sort the errors and output them in order of the ones that occur
304 # most often. So we have to collect the data about all of them, then sort
305 # it.
306 error_groups = []
307 for suite in fail_dict:
308 suite_dict = fail_dict[suite]
309 if suite_dict:
310 for test in suite_dict:
311 test_dict = suite_dict[test]
312 for err_msg in test_dict:
313 err_list = test_dict[err_msg]
314 sorted_list = sorted(err_list, key=lambda x: x[0], reverse=True)
315 err_group = [len(sorted_list), suite, test, err_msg, sorted_list]
316 error_groups.append(err_group)
317
318 # Sort the errors by the number of errors of each type. Then output them in
319 # order.
320 sorted_errors = sorted(error_groups, key=lambda x: x[0], reverse=True)
321 for i in range(0, len(sorted_errors)):
322 err_group = sorted_errors[i]
323 suite = err_group[1]
324 test = err_group[2]
325 err_msg = err_group[3]
326 err_list = err_group[4]
327 out_file.write('Suite: %s\n' % suite)
328 out_file.write(' %s (%d failures)\n' % (test, len(err_list)))
329 out_file.write(' (%s)\n' % err_msg)
330 for i in range(0, len(err_list)):
331 err = err_list[i]
332 out_file.write(' %s, %s, %s\n' % (format_date(err[0]), err[1],
333 err[2]))
334 out_file.write('\n')
335
336 print('Report generated in %s.' % filename)
Caroline Ticee02e9f82016-12-01 13:14:41 -0800337 return filename
Caroline Tice48462062016-11-18 16:49:00 -0800338
339
340def UpdateReport(report_dict, builder, test, report_date, build_link,
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800341 test_summary, board, color):
Caroline Tice48462062016-11-18 16:49:00 -0800342 """Update the data in our report dictionary with current test's data."""
343
344 if 'date' not in report_dict:
345 report_dict['date'] = [report_date]
346 elif report_date not in report_dict['date']:
347 # It is possible that some of the builders started/finished on different
348 # days, so we allow for multiple dates in the reports.
349 report_dict['date'].append(report_date)
350
351 build_key = ''
Caroline Ticefaa3c552016-12-13 11:29:59 -0800352 if builder == GCC_ROTATING_BUILDER:
Caroline Tice48462062016-11-18 16:49:00 -0800353 build_key = '%s-gcc-toolchain' % board
Caroline Ticefaa3c552016-12-13 11:29:59 -0800354 elif builder == LLVM_ROTATING_BUILDER:
355 build_key = '%s-llvm-next-toolchain' % board
Caroline Tice48462062016-11-18 16:49:00 -0800356 else:
357 build_key = builder
358
359 if build_key not in report_dict.keys():
360 build_dict = dict()
361 else:
362 build_dict = report_dict[build_key]
363
364 if 'build_link' not in build_dict:
365 build_dict['build_link'] = build_link
366
367 if 'date' not in build_dict:
368 build_dict['date'] = report_date
369
370 if 'board' in build_dict and build_dict['board'] != board:
Caroline Ticef6ef4392017-04-06 17:16:05 -0700371 raise RuntimeError(
372 'Error: Two different boards (%s,%s) in one build (%s)!' %
373 (board, build_dict['board'], build_link))
Caroline Tice48462062016-11-18 16:49:00 -0800374 build_dict['board'] = board
375
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800376 color_key = '%s-color' % test
377 build_dict[color_key] = color
378
Caroline Tice48462062016-11-18 16:49:00 -0800379 # Check to see if we already have a build status for this build_key
380 status = ''
381 if 'build_status' in build_dict.keys():
382 # Use current build_status, unless current test failed (see below).
383 status = build_dict['build_status']
384
385 if not test_summary:
386 # Current test data was not available, so something was bad with build.
387 build_dict['build_status'] = 'bad'
388 build_dict[test] = '[ no data ]'
389 else:
390 build_dict[test] = test_summary
391 if not status:
392 # Current test ok; no other data, so assume build was ok.
393 build_dict['build_status'] = 'ok'
394
395 report_dict[build_key] = build_dict
396
397
398def UpdateBuilds(builds):
399 """Update the data in our build-data.txt file."""
400
401 # The build data file records the last build number for which we
402 # generated a report. When we generate the next report, we read
403 # this data and increment it to get the new data; when we finish
404 # generating the reports, we write the updated values into this file.
405 # NOTE: One side effect of doing this at the end: If the script
406 # fails in the middle of generating a report, this data does not get
407 # updated.
408 with open(BUILD_DATA_FILE, 'w') as fp:
409 gcc_max = 0
410 llvm_max = 0
411 for b in builds:
Caroline Ticefaa3c552016-12-13 11:29:59 -0800412 if b[0] == GCC_ROTATING_BUILDER:
Caroline Tice48462062016-11-18 16:49:00 -0800413 gcc_max = max(gcc_max, b[1])
Caroline Ticefaa3c552016-12-13 11:29:59 -0800414 elif b[0] == LLVM_ROTATING_BUILDER:
Caroline Tice48462062016-11-18 16:49:00 -0800415 llvm_max = max(llvm_max, b[1])
416 else:
417 fp.write('%s,%d\n' % (b[0], b[1]))
418 if gcc_max > 0:
Caroline Ticefaa3c552016-12-13 11:29:59 -0800419 fp.write('%s,%d\n' % (GCC_ROTATING_BUILDER, gcc_max))
Caroline Tice48462062016-11-18 16:49:00 -0800420 if llvm_max > 0:
Caroline Ticefaa3c552016-12-13 11:29:59 -0800421 fp.write('%s,%d\n' % (LLVM_ROTATING_BUILDER, llvm_max))
Caroline Tice48462062016-11-18 16:49:00 -0800422
423
Yunlian Jiang6b8bacb2016-12-15 14:53:39 -0800424def GetBuilds(date=0):
425 """Get build id from builds."""
Caroline Tice48462062016-11-18 16:49:00 -0800426
Yunlian Jiang6b8bacb2016-12-15 14:53:39 -0800427 # If date is set, get the build id from waterfall.
428 builds = []
429
430 if date:
431 for builder in WATERFALL_BUILDERS + ROTATING_BUILDERS:
432 build_ids = GetBuildID(builder, date)
433 for build_id in build_ids:
434 builds.append((builder, build_id))
435 return builds
436
437 # If date is not set, we try to get the most recent builds.
Caroline Tice48462062016-11-18 16:49:00 -0800438 # Read the values of the last builds used to generate a report, and
439 # increment them appropriately, to get values for generating the
440 # current report. (See comments in UpdateBuilds).
441 with open(BUILD_DATA_FILE, 'r') as fp:
442 lines = fp.readlines()
443
Caroline Tice48462062016-11-18 16:49:00 -0800444 for l in lines:
445 l = l.rstrip()
446 words = l.split(',')
447 builder = words[0]
448 build = int(words[1])
449 builds.append((builder, build + 1))
450 # NOTE: We are assuming here that there are always 2 daily builds in
451 # each of the rotating builders. I am not convinced this is a valid
452 # assumption.
Caroline Ticefaa3c552016-12-13 11:29:59 -0800453 if builder in ROTATING_BUILDERS:
Caroline Tice48462062016-11-18 16:49:00 -0800454 builds.append((builder, build + 2))
455
456 return builds
457
458
459def RecordFailures(failure_dict, platform, suite, builder, int_date, log_file,
460 build_num, failed):
461 """Read and update the stored data about test failures."""
462
463 # Get the dictionary for this particular test suite from the failures
464 # dictionary.
465 suite_dict = failure_dict[suite]
466
467 # Read in the entire log file for this test/build.
468 with open(log_file, 'r') as in_file:
469 lines = in_file.readlines()
470
471 # Update the entries in the failure dictionary for each test within this suite
472 # that failed.
473 for test in failed:
474 # Check to see if there is already an entry in the suite dictionary for this
475 # test; if so use that, otherwise create a new entry.
476 if test in suite_dict:
477 test_dict = suite_dict[test]
478 else:
479 test_dict = dict()
480 # Parse the lines from the log file, looking for lines that indicate this
481 # test failed.
482 msg = ''
483 for l in lines:
484 words = l.split()
485 if len(words) < 3:
486 continue
487 if ((words[0] == test and words[1] == 'ERROR:') or
488 (words[0] == 'provision' and words[1] == 'FAIL:')):
489 words = words[2:]
490 # Get the error message for the failure.
491 msg = ' '.join(words)
492 if not msg:
493 msg = 'Unknown_Error'
494
495 # Look for an existing entry for this error message in the test dictionary.
496 # If found use that, otherwise create a new entry for this error message.
497 if msg in test_dict:
498 error_list = test_dict[msg]
499 else:
500 error_list = list()
501 # Create an entry for this new failure
502 new_item = [int_date, platform, builder, build_num]
503 # Add this failure to the error list if it's not already there.
504 if new_item not in error_list:
505 error_list.append([int_date, platform, builder, build_num])
506 # Sort the error list by date.
507 error_list.sort(key=lambda x: x[0])
508 # Calculate the earliest date to save; delete records for older failures.
509 earliest_date = int_date - MAX_SAVE_RECORDS
510 i = 0
Caroline Ticee02e9f82016-12-01 13:14:41 -0800511 while i < len(error_list) and error_list[i][0] <= earliest_date:
Caroline Tice48462062016-11-18 16:49:00 -0800512 i += 1
513 if i > 0:
514 error_list = error_list[i:]
515 # Save the error list in the test's dictionary, keyed on error_msg.
516 test_dict[msg] = error_list
517
518 # Save the updated test dictionary in the test_suite dictionary.
519 suite_dict[test] = test_dict
520
521 # Save the updated test_suite dictionary in the failure dictionary.
522 failure_dict[suite] = suite_dict
523
524
525def ParseLogFile(log_file, test_data_dict, failure_dict, test, builder,
526 build_num, build_link):
527 """Parse the log file from the given builder, build_num and test.
528
529 Also adds the results for this test to our test results dictionary,
530 and calls RecordFailures, to update our test failure data.
531 """
532
533 lines = []
534 with open(log_file, 'r') as infile:
535 lines = infile.readlines()
536
537 passed = {}
538 failed = {}
539 not_run = {}
540 date = ''
541 status = ''
542 board = ''
543 num_provision_errors = 0
544 build_ok = True
545 afe_line = ''
546
547 for line in lines:
548 if line.rstrip() == '<title>404 Not Found</title>':
549 print('Warning: File for %s (build number %d), %s was not found.' %
550 (builder, build_num, test))
551 build_ok = False
552 break
553 if '[ PASSED ]' in line:
554 test_name = line.split()[0]
555 if test_name != 'Suite':
556 passed[test_name] = True
557 elif '[ FAILED ]' in line:
558 test_name = line.split()[0]
559 if test_name == 'provision':
560 num_provision_errors += 1
561 not_run[test_name] = True
562 elif test_name != 'Suite':
563 failed[test_name] = True
564 elif line.startswith('started: '):
565 date = line.rstrip()
566 date = date[9:]
567 date_obj = time.strptime(date, '%a %b %d %H:%M:%S %Y')
568 int_date = (
569 date_obj.tm_year * 10000 + date_obj.tm_mon * 100 + date_obj.tm_mday)
570 date = time.strftime('%a %b %d %Y', date_obj)
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800571 elif not status and line.startswith('status: '):
Caroline Tice48462062016-11-18 16:49:00 -0800572 status = line.rstrip()
573 words = status.split(':')
574 status = words[-1]
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800575 elif line.find('Suite passed with a warning') != -1:
576 status = 'WARNING'
Caroline Tice48462062016-11-18 16:49:00 -0800577 elif line.startswith('@@@STEP_LINK@Link to suite@'):
578 afe_line = line.rstrip()
579 words = afe_line.split('@')
580 for w in words:
581 if w.startswith('http'):
582 afe_line = w
583 afe_line = afe_line.replace('&amp;', '&')
584 elif 'INFO: RunCommand:' in line:
585 words = line.split()
586 for i in range(0, len(words) - 1):
587 if words[i] == '--board':
588 board = words[i + 1]
589
590 test_dict = test_data_dict[test]
591 test_list = test_dict['tests']
592
593 if build_ok:
594 for t in test_list:
595 if not t in passed and not t in failed:
596 not_run[t] = True
597
598 total_pass = len(passed)
599 total_fail = len(failed)
600 total_notrun = len(not_run)
601
602 else:
603 total_pass = 0
604 total_fail = 0
605 total_notrun = 0
606 status = 'Not found.'
607 if not build_ok:
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800608 return [], date, board, 0, ' '
Caroline Tice48462062016-11-18 16:49:00 -0800609
610 build_dict = dict()
611 build_dict['id'] = build_num
612 build_dict['builder'] = builder
613 build_dict['date'] = date
614 build_dict['build_link'] = build_link
615 build_dict['total_pass'] = total_pass
616 build_dict['total_fail'] = total_fail
617 build_dict['total_not_run'] = total_notrun
618 build_dict['afe_job_link'] = afe_line
619 build_dict['provision_errors'] = num_provision_errors
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800620 if status.strip() == 'SUCCESS':
621 build_dict['color'] = 'green '
622 elif status.strip() == 'FAILURE':
623 build_dict['color'] = ' red '
624 elif status.strip() == 'WARNING':
625 build_dict['color'] = 'orange'
626 else:
627 build_dict['color'] = ' '
Caroline Tice48462062016-11-18 16:49:00 -0800628
629 # Use YYYYMMDD (integer) as the build record key
630 if build_ok:
631 if board in test_dict:
632 board_dict = test_dict[board]
633 else:
634 board_dict = dict()
635 board_dict[int_date] = build_dict
636
637 # Only keep the last 5 records (based on date)
638 keys_list = board_dict.keys()
639 if len(keys_list) > MAX_SAVE_RECORDS:
640 min_key = min(keys_list)
641 del board_dict[min_key]
642
643 # Make sure changes get back into the main dictionary
644 test_dict[board] = board_dict
645 test_data_dict[test] = test_dict
646
647 if len(failed) > 0:
648 RecordFailures(failure_dict, board, test, builder, int_date, log_file,
649 build_num, failed)
650
651 summary_result = '[%2d/ %2d/ %2d]' % (total_pass, total_fail, total_notrun)
652
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800653 return summary_result, date, board, int_date, build_dict['color']
Caroline Tice48462062016-11-18 16:49:00 -0800654
655
656def DownloadLogFile(builder, buildnum, test, test_family):
657
658 ce = command_executer.GetCommandExecuter()
659 os.system('mkdir -p %s/%s/%s' % (DOWNLOAD_DIR, builder, test))
Caroline Ticefaa3c552016-12-13 11:29:59 -0800660 if builder in ROTATING_BUILDERS:
Caroline Tice48462062016-11-18 16:49:00 -0800661 source = ('https://uberchromegw.corp.google.com/i/chromiumos.tryserver'
662 '/builders/%s/builds/%d/steps/%s%%20%%5B%s%%5D/logs/stdio' %
663 (builder, buildnum, test_family, test))
664 build_link = ('https://uberchromegw.corp.google.com/i/chromiumos.tryserver'
665 '/builders/%s/builds/%d' % (builder, buildnum))
666 else:
667 source = ('https://uberchromegw.corp.google.com/i/chromeos/builders/%s/'
668 'builds/%d/steps/%s%%20%%5B%s%%5D/logs/stdio' %
669 (builder, buildnum, test_family, test))
670 build_link = ('https://uberchromegw.corp.google.com/i/chromeos/builders/%s'
671 '/builds/%d' % (builder, buildnum))
672
673 target = '%s/%s/%s/%d' % (DOWNLOAD_DIR, builder, test, buildnum)
674 if not os.path.isfile(target) or os.path.getsize(target) == 0:
675 cmd = 'sso_client %s > %s' % (source, target)
676 status = ce.RunCommand(cmd)
677 if status != 0:
678 return '', ''
679
680 return target, build_link
681
682
Manoj Gupta63824522016-12-14 11:05:18 -0800683# Check for prodaccess.
684def CheckProdAccess():
685 status, output, _ = command_executer.GetCommandExecuter().RunCommandWOutput(
686 'prodcertstatus')
687 if status != 0:
688 return False
689 # Verify that status is not expired
690 if 'expires' in output:
691 return True
692 return False
693
694
Caroline Ticefaa3c552016-12-13 11:29:59 -0800695def ValidOptions(parser, options):
696 too_many_options = False
697 if options.main:
698 if options.rotating or options.failures_report:
699 too_many_options = True
700 elif options.rotating and options.failures_report:
701 too_many_options = True
702
703 if too_many_options:
704 parser.error('Can only specify one of --main, --rotating or'
705 ' --failures_report.')
706
707 conflicting_failure_options = False
708 if options.failures_report and options.omit_failures:
709 conflicting_failure_options = True
710 parser.error('Cannot specify both --failures_report and --omit_failures.')
711
712 return not too_many_options and not conflicting_failure_options
713
714
715def Main(argv):
Caroline Tice48462062016-11-18 16:49:00 -0800716 """Main function for this script."""
Caroline Ticefaa3c552016-12-13 11:29:59 -0800717 parser = argparse.ArgumentParser()
718 parser.add_argument(
719 '--main',
720 dest='main',
721 default=False,
722 action='store_true',
723 help='Generate report only for main waterfall '
724 'builders.')
725 parser.add_argument(
726 '--rotating',
727 dest='rotating',
728 default=False,
729 action='store_true',
730 help='Generate report only for rotating builders.')
731 parser.add_argument(
732 '--failures_report',
733 dest='failures_report',
734 default=False,
735 action='store_true',
736 help='Only generate the failures section of the report.')
737 parser.add_argument(
738 '--omit_failures',
739 dest='omit_failures',
740 default=False,
741 action='store_true',
742 help='Do not generate the failures section of the report.')
743 parser.add_argument(
744 '--no_update',
745 dest='no_update',
746 default=False,
747 action='store_true',
748 help='Run reports, but do not update the data files.')
Yunlian Jiang6b8bacb2016-12-15 14:53:39 -0800749 parser.add_argument(
750 '--date',
751 dest='date',
752 default=0,
753 type=int,
754 help='The date YYYYMMDD of waterfall report.')
Caroline Ticefaa3c552016-12-13 11:29:59 -0800755
756 options = parser.parse_args(argv)
757
758 if not ValidOptions(parser, options):
759 return 1
760
761 main_only = options.main
762 rotating_only = options.rotating
763 failures_report = options.failures_report
764 omit_failures = options.omit_failures
Yunlian Jiang6b8bacb2016-12-15 14:53:39 -0800765 date = options.date
Caroline Tice48462062016-11-18 16:49:00 -0800766
767 test_data_dict = dict()
768 failure_dict = dict()
Manoj Gupta63824522016-12-14 11:05:18 -0800769
770 prod_access = CheckProdAccess()
771 if not prod_access:
772 print('ERROR: Please run prodaccess first.')
773 return
774
Caroline Tice48462062016-11-18 16:49:00 -0800775 with open('%s/waterfall-test-data.json' % DATA_DIR, 'r') as input_file:
776 test_data_dict = json.load(input_file)
777
778 with open('%s/test-failure-data.json' % DATA_DIR, 'r') as fp:
779 failure_dict = json.load(fp)
780
Yunlian Jiang6b8bacb2016-12-15 14:53:39 -0800781 builds = GetBuilds(date)
Caroline Tice48462062016-11-18 16:49:00 -0800782
783 waterfall_report_dict = dict()
784 rotating_report_dict = dict()
785 int_date = 0
786 for test_desc in TESTS:
787 test, test_family = test_desc
788 for build in builds:
789 (builder, buildnum) = build
790 if test.startswith('kernel') and 'llvm' in builder:
791 continue
792 if 'x86' in builder and not test.startswith('bvt'):
793 continue
794 target, build_link = DownloadLogFile(builder, buildnum, test, test_family)
795
796 if os.path.exists(target):
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800797 test_summary, report_date, board, tmp_date, color = ParseLogFile(
Caroline Tice48462062016-11-18 16:49:00 -0800798 target, test_data_dict, failure_dict, test, builder, buildnum,
799 build_link)
800
801 if tmp_date != 0:
802 int_date = tmp_date
803
804 if builder in ROTATING_BUILDERS:
805 UpdateReport(rotating_report_dict, builder, test, report_date,
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800806 build_link, test_summary, board, color)
Caroline Tice48462062016-11-18 16:49:00 -0800807 else:
808 UpdateReport(waterfall_report_dict, builder, test, report_date,
Caroline Ticef0ad65c2016-11-29 10:40:23 -0800809 build_link, test_summary, board, color)
Caroline Tice48462062016-11-18 16:49:00 -0800810
Caroline Ticee02e9f82016-12-01 13:14:41 -0800811 PruneOldFailures(failure_dict, int_date)
812
Caroline Ticefaa3c552016-12-13 11:29:59 -0800813 if waterfall_report_dict and not rotating_only and not failures_report:
Caroline Ticee02e9f82016-12-01 13:14:41 -0800814 main_report = GenerateWaterfallReport(waterfall_report_dict, failure_dict,
Caroline Ticefaa3c552016-12-13 11:29:59 -0800815 'main', int_date, omit_failures)
Caroline Ticee02e9f82016-12-01 13:14:41 -0800816 EmailReport(main_report, 'Main', format_date(int_date))
817 shutil.copy(main_report, ARCHIVE_DIR)
Caroline Ticefaa3c552016-12-13 11:29:59 -0800818 if rotating_report_dict and not main_only and not failures_report:
Caroline Ticef6ef4392017-04-06 17:16:05 -0700819 rotating_report = GenerateWaterfallReport(
820 rotating_report_dict, failure_dict, 'rotating', int_date, omit_failures)
Caroline Ticee02e9f82016-12-01 13:14:41 -0800821 EmailReport(rotating_report, 'Rotating', format_date(int_date))
822 shutil.copy(rotating_report, ARCHIVE_DIR)
Caroline Tice48462062016-11-18 16:49:00 -0800823
Caroline Ticefaa3c552016-12-13 11:29:59 -0800824 if failures_report:
825 failures_report = GenerateFailuresReport(failure_dict, int_date)
826 EmailReport(failures_report, 'Failures', format_date(int_date))
827 shutil.copy(failures_report, ARCHIVE_DIR)
Caroline Tice48462062016-11-18 16:49:00 -0800828
Caroline Ticefaa3c552016-12-13 11:29:59 -0800829 if not options.no_update:
830 with open('%s/waterfall-test-data.json' % DATA_DIR, 'w') as out_file:
831 json.dump(test_data_dict, out_file, indent=2)
Caroline Tice48462062016-11-18 16:49:00 -0800832
Caroline Ticefaa3c552016-12-13 11:29:59 -0800833 with open('%s/test-failure-data.json' % DATA_DIR, 'w') as out_file:
834 json.dump(failure_dict, out_file, indent=2)
835
836 UpdateBuilds(builds)
Caroline Tice48462062016-11-18 16:49:00 -0800837
838
839if __name__ == '__main__':
Caroline Ticefaa3c552016-12-13 11:29:59 -0800840 Main(sys.argv[1:])
Caroline Tice48462062016-11-18 16:49:00 -0800841 sys.exit(0)