blob: ee7bb3212a142731b900181d1315780dc0ada3c3 [file] [log] [blame]
Josip Sokcevic4de5dea2022-03-23 21:15:14 +00001#!/usr/bin/env python3
Edward Lemur32e3d1e2018-07-12 00:54:05 +00002# Copyright (c) 2018 The Chromium 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
Edward Lemur03d6d112018-10-23 15:17:36 +00006import re
Edward Lesmes1e59a242021-04-30 18:38:25 +00007import os
Josip Sokcevic7958e302023-03-01 23:02:21 +00008import scm
Edward Lemur32e3d1e2018-07-12 00:54:05 +00009import subprocess2
10import sys
Gavin Mak5f955df2023-08-30 15:39:13 +000011import urllib.parse
Edward Lemur32e3d1e2018-07-12 00:54:05 +000012
Edward Lemur48836262018-10-18 02:08:06 +000013# Current version of metrics recording.
14# When we add new metrics, the version number will be increased, we display the
15# user what has changed, and ask the user to agree again.
Edward Lesmes1e59a242021-04-30 18:38:25 +000016CURRENT_VERSION = 2
Edward Lemur48836262018-10-18 02:08:06 +000017
Edward Lemur5ba1e9c2018-07-23 18:19:02 +000018APP_URL = 'https://cit-cli-metrics.appspot.com'
19
Edward Lesmes9c349062021-05-06 20:02:39 +000020REPORT_BUILD = os.getenv('DEPOT_TOOLS_REPORT_BUILD')
Mike Frysinger124bb8e2023-09-06 05:48:55 +000021COLLECT_METRICS = (os.getenv('DEPOT_TOOLS_COLLECT_METRICS') != '0'
22 and os.getenv('DEPOT_TOOLS_METRICS') != '0')
Edward Lesmes1e59a242021-04-30 18:38:25 +000023
Edward Lesmesc8f63d32021-06-02 23:51:53 +000024SYNC_STATUS_SUCCESS = 'SYNC_STATUS_SUCCESS'
25SYNC_STATUS_FAILURE = 'SYNC_STATUS_FAILURE'
26
Edward Lesmes1e59a242021-04-30 18:38:25 +000027
Samuel Huang98a7e802019-02-12 15:32:22 +000028def get_notice_countdown_header(countdown):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000029 if countdown == 0:
30 yield ' METRICS COLLECTION IS TAKING PLACE'
31 else:
32 yield ' METRICS COLLECTION WILL START IN %d EXECUTIONS' % countdown
33
Edward Lemur32e3d1e2018-07-12 00:54:05 +000034
Samuel Huang98a7e802019-02-12 15:32:22 +000035def get_notice_version_change_header():
Mike Frysinger124bb8e2023-09-06 05:48:55 +000036 yield ' WE ARE COLLECTING ADDITIONAL METRICS'
37 yield ''
38 yield ' Please review the changes and opt-in again.'
39
Samuel Huang98a7e802019-02-12 15:32:22 +000040
41def get_notice_footer():
Mike Frysinger124bb8e2023-09-06 05:48:55 +000042 yield 'To suppress this message opt in or out using:'
43 yield '$ gclient metrics [--opt-in] [--opt-out]'
44 yield 'For more information please see metrics.README.md'
45 yield 'in your depot_tools checkout or visit'
46 yield 'https://bit.ly/3MpLAYM.'
47
Samuel Huang98a7e802019-02-12 15:32:22 +000048
49def get_change_notice(version):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000050 if version == 0:
51 return [] # No changes for version 0
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +000052
Mike Frysinger124bb8e2023-09-06 05:48:55 +000053 if version == 1:
54 return [
55 'We want to collect the Git version.',
56 'We want to collect information about the HTTP',
57 'requests that depot_tools makes, and the git and',
58 'cipd commands it executes.',
59 '',
60 'We only collect known strings to make sure we',
61 'don\'t record PII.',
62 ]
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +000063
Mike Frysinger124bb8e2023-09-06 05:48:55 +000064 if version == 2:
65 return [
66 'We will start collecting metrics from bots.',
67 'There are no changes for developers.',
68 'If the DEPOT_TOOLS_REPORT_BUILD environment variable is set,',
69 'we will report information about the current build',
70 '(e.g. buildbucket project, bucket, builder and build id),',
71 'and authenticate to the metrics collection server.',
72 'This information will only be recorded for requests',
73 'authenticated as bot service accounts.',
74 ]
Edward Lemur48836262018-10-18 02:08:06 +000075
76
Edward Lemur40764b02018-07-20 18:50:29 +000077KNOWN_PROJECT_URLS = {
Mike Frysinger124bb8e2023-09-06 05:48:55 +000078 'https://chrome-internal.googlesource.com/chrome/ios_internal',
79 'https://chrome-internal.googlesource.com/infra/infra_internal',
80 'https://chromium.googlesource.com/breakpad/breakpad',
81 'https://chromium.googlesource.com/chromium/src',
82 'https://chromium.googlesource.com/chromium/tools/depot_tools',
83 'https://chromium.googlesource.com/crashpad/crashpad',
84 'https://chromium.googlesource.com/external/gyp',
85 'https://chromium.googlesource.com/external/naclports',
86 'https://chromium.googlesource.com/infra/goma/client',
87 'https://chromium.googlesource.com/infra/infra',
88 'https://chromium.googlesource.com/native_client/',
89 'https://chromium.googlesource.com/syzygy',
90 'https://chromium.googlesource.com/v8/v8',
91 'https://dart.googlesource.com/sdk',
92 'https://pdfium.googlesource.com/pdfium',
93 'https://skia.googlesource.com/buildbot',
94 'https://skia.googlesource.com/skia',
95 'https://webrtc.googlesource.com/src',
Edward Lemur40764b02018-07-20 18:50:29 +000096}
97
Edward Lemur03d6d112018-10-23 15:17:36 +000098KNOWN_HTTP_HOSTS = {
Mike Frysinger124bb8e2023-09-06 05:48:55 +000099 'chrome-internal-review.googlesource.com',
100 'chromium-review.googlesource.com',
101 'dart-review.googlesource.com',
102 'eu1-mirror-chromium-review.googlesource.com',
103 'pdfium-review.googlesource.com',
104 'skia-review.googlesource.com',
105 'us1-mirror-chromium-review.googlesource.com',
106 'us2-mirror-chromium-review.googlesource.com',
107 'us3-mirror-chromium-review.googlesource.com',
108 'webrtc-review.googlesource.com',
Edward Lemur03d6d112018-10-23 15:17:36 +0000109}
110
111KNOWN_HTTP_METHODS = {
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000112 'DELETE',
113 'GET',
114 'PATCH',
115 'POST',
116 'PUT',
Edward Lemur03d6d112018-10-23 15:17:36 +0000117}
118
119KNOWN_HTTP_PATHS = {
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000120 'accounts': re.compile(r'(/a)?/accounts/.*'),
121 'changes': re.compile(r'(/a)?/changes/([^/]+)?$'),
122 'changes/abandon': re.compile(r'(/a)?/changes/.*/abandon'),
123 'changes/comments': re.compile(r'(/a)?/changes/.*/comments'),
124 'changes/detail': re.compile(r'(/a)?/changes/.*/detail'),
125 'changes/edit': re.compile(r'(/a)?/changes/.*/edit'),
126 'changes/message': re.compile(r'(/a)?/changes/.*/message'),
127 'changes/restore': re.compile(r'(/a)?/changes/.*/restore'),
128 'changes/reviewers': re.compile(r'(/a)?/changes/.*/reviewers/.*'),
129 'changes/revisions/commit':
130 re.compile(r'(/a)?/changes/.*/revisions/.*/commit'),
131 'changes/revisions/review':
132 re.compile(r'(/a)?/changes/.*/revisions/.*/review'),
133 'changes/submit': re.compile(r'(/a)?/changes/.*/submit'),
134 'projects/branches': re.compile(r'(/a)?/projects/.*/branches/.*'),
Edward Lemur03d6d112018-10-23 15:17:36 +0000135}
136
137KNOWN_HTTP_ARGS = {
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000138 'ALL_REVISIONS',
139 'CURRENT_COMMIT',
140 'CURRENT_REVISION',
141 'DETAILED_ACCOUNTS',
142 'LABELS',
Edward Lemur03d6d112018-10-23 15:17:36 +0000143}
144
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000145GIT_VERSION_RE = re.compile(r'git version (\d)\.(\d{0,2})\.(\d{0,2})')
Edward Lemur861640f2018-10-31 19:45:31 +0000146
Edward Lemurfec80c42018-11-01 23:14:14 +0000147KNOWN_SUBCOMMAND_ARGS = {
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000148 'cc', 'hashtag', 'l=Auto-Submit+1', 'l=Code-Review+1', 'l=Code-Review+2',
149 'l=Commit-Queue+1', 'l=Commit-Queue+2', 'label', 'm', 'notify=ALL',
150 'notify=NONE', 'private', 'r', 'ready', 'topic', 'wip'
Edward Lemurfec80c42018-11-01 23:14:14 +0000151}
152
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000153
154def get_python_version():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000155 """Return the python version in the major.minor.micro format."""
156 return '{v.major}.{v.minor}.{v.micro}'.format(v=sys.version_info)
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000157
158
Edward Lemur861640f2018-10-31 19:45:31 +0000159def get_git_version():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000160 """Return the Git version in the major.minor.micro format."""
161 p = subprocess2.Popen(['git', '--version'],
162 stdout=subprocess2.PIPE,
163 stderr=subprocess2.PIPE)
164 stdout, _ = p.communicate()
165 match = GIT_VERSION_RE.match(stdout.decode('utf-8'))
166 if not match:
167 return None
168 return '%s.%s.%s' % match.groups()
Edward Lemur861640f2018-10-31 19:45:31 +0000169
170
Edward Lesmes1e59a242021-04-30 18:38:25 +0000171def get_bot_metrics():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000172 try:
173 project, bucket, builder, build = REPORT_BUILD.split('/')
174 return {
175 'build_id': int(build),
176 'builder': {
177 'project': project,
178 'bucket': bucket,
179 'builder': builder,
180 },
181 }
182 except (AttributeError, ValueError):
183 return None
Edward Lesmes1e59a242021-04-30 18:38:25 +0000184
185
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000186def return_code_from_exception(exception):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000187 """Returns the exit code that would result of raising the exception."""
188 if exception is None:
189 return 0
190 e = exception[1]
191 if isinstance(e, KeyboardInterrupt):
192 return 130
193 if isinstance(e, SystemExit):
194 return e.code
195 return 1
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000196
197
Edward Lemurfec80c42018-11-01 23:14:14 +0000198def extract_known_subcommand_args(args):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000199 """Extract the known arguments from the passed list of args."""
200 known_args = []
201 for arg in args:
202 if arg in KNOWN_SUBCOMMAND_ARGS:
203 known_args.append(arg)
204 else:
205 arg = arg.split('=')[0]
206 if arg in KNOWN_SUBCOMMAND_ARGS:
207 known_args.append(arg)
208 return sorted(known_args)
Edward Lemurfec80c42018-11-01 23:14:14 +0000209
210
Edward Lemur03d6d112018-10-23 15:17:36 +0000211def extract_http_metrics(request_uri, method, status, response_time):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000212 """Extract metrics from the request URI.
Edward Lemur03d6d112018-10-23 15:17:36 +0000213
214 Extracts the host, path, and arguments from the request URI, and returns them
215 along with the method, status and response time.
216
217 The host, method, path and arguments must be in the KNOWN_HTTP_* constants
218 defined above.
219
220 Arguments are the values of the o= url parameter. In Gerrit, additional fields
221 can be obtained by adding o parameters, each option requires more database
222 lookups and slows down the query response time to the client, so we make an
223 effort to collect them.
224
225 The regex defined in KNOWN_HTTP_PATH_RES are checked against the path, and
226 those that match will be returned.
227 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000228 http_metrics = {
229 'status': status,
230 'response_time': response_time,
231 }
Edward Lemur03d6d112018-10-23 15:17:36 +0000232
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000233 if method in KNOWN_HTTP_METHODS:
234 http_metrics['method'] = method
Edward Lemur03d6d112018-10-23 15:17:36 +0000235
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000236 parsed_url = urllib.parse.urlparse(request_uri)
Edward Lemur03d6d112018-10-23 15:17:36 +0000237
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000238 if parsed_url.netloc in KNOWN_HTTP_HOSTS:
239 http_metrics['host'] = parsed_url.netloc
Edward Lemur03d6d112018-10-23 15:17:36 +0000240
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000241 for name, path_re in KNOWN_HTTP_PATHS.items():
242 if path_re.match(parsed_url.path):
243 http_metrics['path'] = name
244 break
Edward Lemur03d6d112018-10-23 15:17:36 +0000245
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000246 parsed_query = urllib.parse.parse_qs(parsed_url.query)
Edward Lemur03d6d112018-10-23 15:17:36 +0000247
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000248 # Collect o-parameters from the request.
249 args = [arg for arg in parsed_query.get('o', []) if arg in KNOWN_HTTP_ARGS]
250 if args:
251 http_metrics['arguments'] = args
Edward Lemur03d6d112018-10-23 15:17:36 +0000252
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000253 return http_metrics
Edward Lemur03d6d112018-10-23 15:17:36 +0000254
255
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000256def get_repo_timestamp(path_to_repo):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000257 """Get an approximate timestamp for the upstream of |path_to_repo|.
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000258
259 Returns the top two bits of the timestamp of the HEAD for the upstream of the
260 branch path_to_repo is checked out at.
261 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000262 # Get the upstream for the current branch. If we're not in a branch,
263 # fallback to HEAD.
264 try:
265 upstream = scm.GIT.GetUpstreamBranch(path_to_repo) or 'HEAD'
266 except subprocess2.CalledProcessError:
267 upstream = 'HEAD'
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000268
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000269 # Get the timestamp of the HEAD for the upstream of the current branch.
270 p = subprocess2.Popen(
271 ['git', '-C', path_to_repo, 'log', '-n1', upstream, '--format=%at'],
272 stdout=subprocess2.PIPE,
273 stderr=subprocess2.PIPE)
274 stdout, _ = p.communicate()
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000275
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000276 # If there was an error, give up.
277 if p.returncode != 0:
278 return None
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000279
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000280 return stdout.strip()
281
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000282
Hans Wennborg24b5f902019-03-15 18:51:27 +0000283def print_boxed_text(out, min_width, lines):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000284 [EW, NS, SE, SW, NE, NW] = list('=|++++')
285 width = max(min_width, max(len(line) for line in lines))
286 out(SE + EW * (width + 2) + SW + '\n')
287 for line in lines:
288 out('%s %-*s %s\n' % (NS, width, line, NS))
289 out(NE + EW * (width + 2) + NW + '\n')
290
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000291
292def print_notice(countdown):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000293 """Print a notice to let the user know the status of metrics collection."""
294 lines = list(get_notice_countdown_header(countdown))
295 lines.append('')
296 lines += list(get_notice_footer())
297 print_boxed_text(sys.stderr.write, 49, lines)
298
Edward Lemur48836262018-10-18 02:08:06 +0000299
300def print_version_change(config_version):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000301 """Print a notice to let the user know we are collecting more metrics."""
302 lines = list(get_notice_version_change_header())
303 for version in range(config_version + 1, CURRENT_VERSION + 1):
304 lines.append('')
305 lines += get_change_notice(version)
306 print_boxed_text(sys.stderr.write, 49, lines)