blob: dd11a45e789b3d97a25c9b988e19bfdb4136bb25 [file] [log] [blame]
Siddharth Shukla8e64d902017-03-12 19:50:18 +01001#!/usr/bin/env python
Adele Zhoubf3b7692016-08-11 18:45:18 -07002# Copyright 2016, Google Inc.
3# All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""Tool to get build statistics from Jenkins and upload to BigQuery."""
32
Siddharth Shuklad194f592017-03-11 19:12:43 +010033from __future__ import print_function
34
Adele Zhoubf3b7692016-08-11 18:45:18 -070035import argparse
36import jenkinsapi
37from jenkinsapi.custom_exceptions import JenkinsAPIException
38from jenkinsapi.jenkins import Jenkins
39import json
40import os
41import re
42import sys
43import urllib
44
45
46gcp_utils_dir = os.path.abspath(os.path.join(
47 os.path.dirname(__file__), '../gcp/utils'))
48sys.path.append(gcp_utils_dir)
49import big_query_utils
50
51
Adele Zhoubf3b7692016-08-11 18:45:18 -070052_PROJECT_ID = 'grpc-testing'
53_HAS_MATRIX = True
Matt Kwong983c2da2016-11-10 15:07:59 -080054_BUILDS = {'gRPC_interop_master': not _HAS_MATRIX,
55 'gRPC_master_linux': not _HAS_MATRIX,
56 'gRPC_master_macos': not _HAS_MATRIX,
57 'gRPC_master_windows': not _HAS_MATRIX,
58 'gRPC_performance_master': not _HAS_MATRIX,
59 'gRPC_portability_master_linux': not _HAS_MATRIX,
60 'gRPC_portability_master_windows': not _HAS_MATRIX,
61 'gRPC_master_asanitizer_c': not _HAS_MATRIX,
62 'gRPC_master_asanitizer_cpp': not _HAS_MATRIX,
63 'gRPC_master_msan_c': not _HAS_MATRIX,
64 'gRPC_master_tsanitizer_c': not _HAS_MATRIX,
65 'gRPC_master_tsan_cpp': not _HAS_MATRIX,
Adele Zhoubf3b7692016-08-11 18:45:18 -070066 'gRPC_interop_pull_requests': not _HAS_MATRIX,
Matt Kwong983c2da2016-11-10 15:07:59 -080067 'gRPC_performance_pull_requests': not _HAS_MATRIX,
68 'gRPC_portability_pull_requests_linux': not _HAS_MATRIX,
69 'gRPC_portability_pr_win': not _HAS_MATRIX,
70 'gRPC_pull_requests_linux': not _HAS_MATRIX,
71 'gRPC_pull_requests_macos': not _HAS_MATRIX,
72 'gRPC_pr_win': not _HAS_MATRIX,
73 'gRPC_pull_requests_asan_c': not _HAS_MATRIX,
74 'gRPC_pull_requests_asan_cpp': not _HAS_MATRIX,
75 'gRPC_pull_requests_msan_c': not _HAS_MATRIX,
76 'gRPC_pull_requests_tsan_c': not _HAS_MATRIX,
77 'gRPC_pull_requests_tsan_cpp': not _HAS_MATRIX,
Adele Zhoubf3b7692016-08-11 18:45:18 -070078}
79_URL_BASE = 'https://grpc-testing.appspot.com/job'
Adele Zhou445534e2016-09-06 17:57:11 -070080
81# This is a dynamic list where known and active issues should be added.
82# Fixed ones should be removed.
83# Also try not to add multiple messages from the same failure.
Adele Zhoubf3b7692016-08-11 18:45:18 -070084_KNOWN_ERRORS = [
85 'Failed to build workspace Tests with scheme AllTests',
86 'Build timed out',
Adele Zhou859d2a42016-09-16 15:15:44 -070087 'TIMEOUT: tools/run_tests/pre_build_node.sh',
88 'TIMEOUT: tools/run_tests/pre_build_ruby.sh',
Adele Zhoubf3b7692016-08-11 18:45:18 -070089 'FATAL: Unable to produce a script file',
Adele Zhou107e51e2016-09-08 11:37:30 -070090 'FAILED: build_docker_c\+\+',
Adele Zhou445534e2016-09-06 17:57:11 -070091 'cannot find package \"cloud.google.com/go/compute/metadata\"',
Adele Zhoubf3b7692016-08-11 18:45:18 -070092 'LLVM ERROR: IO failure on output stream.',
93 'MSBUILD : error MSB1009: Project file does not exist.',
Adele Zhou445534e2016-09-06 17:57:11 -070094 'fatal: git fetch_pack: expected ACK/NAK',
95 'Failed to fetch from http://github.com/grpc/grpc.git',
96 ('hudson.remoting.RemotingSystemException: java.io.IOException: '
97 'Backing channel is disconnected.'),
Adele Zhou107e51e2016-09-08 11:37:30 -070098 'hudson.remoting.ChannelClosedException',
Adele Zhou59af64b2016-09-22 15:23:54 -070099 'Could not initialize class hudson.Util',
100 'Too many open files in system',
Adele Zhou445534e2016-09-06 17:57:11 -0700101 'FAILED: bins/tsan/qps_openloop_test GRPC_POLL_STRATEGY=epoll',
102 'FAILED: bins/tsan/qps_openloop_test GRPC_POLL_STRATEGY=legacy',
103 'FAILED: bins/tsan/qps_openloop_test GRPC_POLL_STRATEGY=poll',
104 ('tests.bins/asan/h2_proxy_test streaming_error_response '
105 'GRPC_POLL_STRATEGY=legacy'),
Adele Zhoubf3b7692016-08-11 18:45:18 -0700106]
Matt Kwong983c2da2016-11-10 15:07:59 -0800107_NO_REPORT_FILES_FOUND_ERROR = 'No test report files were found. Configuration error?'
Adele Zhoubf3b7692016-08-11 18:45:18 -0700108_UNKNOWN_ERROR = 'Unknown error'
109_DATASET_ID = 'build_statistics'
110
111
112def _scrape_for_known_errors(html):
113 error_list = []
114 known_error_count = 0
115 for known_error in _KNOWN_ERRORS:
116 errors = re.findall(known_error, html)
117 this_error_count = len(errors)
118 if this_error_count > 0:
119 known_error_count += this_error_count
120 error_list.append({'description': known_error,
121 'count': this_error_count})
122 print('====> %d failures due to %s' % (this_error_count, known_error))
123 return error_list, known_error_count
124
125
Matt Kwong983c2da2016-11-10 15:07:59 -0800126def _no_report_files_found(html):
127 return _NO_REPORT_FILES_FOUND_ERROR in html
128
129
Adele Zhoubf3b7692016-08-11 18:45:18 -0700130def _get_last_processed_buildnumber(build_name):
131 query = 'SELECT max(build_number) FROM [%s:%s.%s];' % (
132 _PROJECT_ID, _DATASET_ID, build_name)
133 query_job = big_query_utils.sync_query_job(bq, _PROJECT_ID, query)
134 page = bq.jobs().getQueryResults(
135 pageToken=None,
136 **query_job['jobReference']).execute(num_retries=3)
137 if page['rows'][0]['f'][0]['v']:
138 return int(page['rows'][0]['f'][0]['v'])
139 return 0
140
141
142def _process_matrix(build, url_base):
143 matrix_list = []
144 for matrix in build.get_matrix_runs():
145 matrix_str = re.match('.*\\xc2\\xbb ((?:[^,]+,?)+) #.*',
146 matrix.name).groups()[0]
147 matrix_tuple = matrix_str.split(',')
148 json_url = '%s/config=%s,language=%s,platform=%s/testReport/api/json' % (
149 url_base, matrix_tuple[0], matrix_tuple[1], matrix_tuple[2])
150 console_url = '%s/config=%s,language=%s,platform=%s/consoleFull' % (
151 url_base, matrix_tuple[0], matrix_tuple[1], matrix_tuple[2])
152 matrix_dict = {'name': matrix_str,
153 'duration': matrix.get_duration().total_seconds()}
154 matrix_dict.update(_process_build(json_url, console_url))
155 matrix_list.append(matrix_dict)
156
157 return matrix_list
158
159
160def _process_build(json_url, console_url):
161 build_result = {}
162 error_list = []
163 try:
164 html = urllib.urlopen(json_url).read()
165 test_result = json.loads(html)
166 print('====> Parsing result from %s' % json_url)
167 failure_count = test_result['failCount']
168 build_result['pass_count'] = test_result['passCount']
169 build_result['failure_count'] = failure_count
Matt Kwong983c2da2016-11-10 15:07:59 -0800170 build_result['no_report_files_found'] = _no_report_files_found(html)
Adele Zhoubf3b7692016-08-11 18:45:18 -0700171 if failure_count > 0:
172 error_list, known_error_count = _scrape_for_known_errors(html)
173 unknown_error_count = failure_count - known_error_count
174 # This can happen if the same error occurs multiple times in one test.
175 if failure_count < known_error_count:
176 print('====> Some errors are duplicates.')
177 unknown_error_count = 0
178 error_list.append({'description': _UNKNOWN_ERROR,
179 'count': unknown_error_count})
180 except Exception as e:
181 print('====> Got exception for %s: %s.' % (json_url, str(e)))
182 print('====> Parsing errors from %s.' % console_url)
183 html = urllib.urlopen(console_url).read()
184 build_result['pass_count'] = 0
185 build_result['failure_count'] = 1
186 error_list, _ = _scrape_for_known_errors(html)
187 if error_list:
188 error_list.append({'description': _UNKNOWN_ERROR, 'count': 0})
189 else:
190 error_list.append({'description': _UNKNOWN_ERROR, 'count': 1})
191
192 if error_list:
193 build_result['error'] = error_list
194
195 return build_result
196
197
198# parse command line
199argp = argparse.ArgumentParser(description='Get build statistics.')
200argp.add_argument('-u', '--username', default='jenkins')
201argp.add_argument('-b', '--builds',
202 choices=['all'] + sorted(_BUILDS.keys()),
203 nargs='+',
204 default=['all'])
205args = argp.parse_args()
206
207J = Jenkins('https://grpc-testing.appspot.com', args.username, 'apiToken')
208bq = big_query_utils.create_big_query()
209
210for build_name in _BUILDS.keys() if 'all' in args.builds else args.builds:
211 print('====> Build: %s' % build_name)
212 # Since get_last_completed_build() always fails due to malformatted string
213 # error, we use get_build_metadata() instead.
214 job = None
215 try:
216 job = J[build_name]
217 except Exception as e:
218 print('====> Failed to get build %s: %s.' % (build_name, str(e)))
219 continue
220 last_processed_build_number = _get_last_processed_buildnumber(build_name)
221 last_complete_build_number = job.get_last_completed_buildnumber()
222 # To avoid processing all builds for a project never looked at. In this case,
223 # only examine 10 latest builds.
224 starting_build_number = max(last_processed_build_number+1,
225 last_complete_build_number-9)
226 for build_number in xrange(starting_build_number,
227 last_complete_build_number+1):
228 print('====> Processing %s build %d.' % (build_name, build_number))
229 build = None
230 try:
231 build = job.get_build_metadata(build_number)
232 except KeyError:
233 print('====> Build %s is missing. Skip.' % build_number)
234 continue
235 build_result = {'build_number': build_number,
236 'timestamp': str(build.get_timestamp())}
237 url_base = json_url = '%s/%s/%d' % (_URL_BASE, build_name, build_number)
238 if _BUILDS[build_name]: # The build has matrix, such as gRPC_master.
239 build_result['matrix'] = _process_matrix(build, url_base)
240 else:
241 json_url = '%s/testReport/api/json' % url_base
242 console_url = '%s/consoleFull' % url_base
243 build_result['duration'] = build.get_duration().total_seconds()
244 build_result.update(_process_build(json_url, console_url))
245 rows = [big_query_utils.make_row(build_number, build_result)]
246 if not big_query_utils.insert_rows(bq, _PROJECT_ID, _DATASET_ID, build_name,
247 rows):
Siddharth Shuklad194f592017-03-11 19:12:43 +0100248 print('====> Error uploading result to bigquery.')
Adele Zhoubf3b7692016-08-11 18:45:18 -0700249 sys.exit(1)
250