blob: 0664e39698cfc9d0501f8781aaa9be07c048c2e5 [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 Lemurc87d45b2018-07-26 17:43:11 +00006from __future__ import print_function
7
Edward Lemur03d6d112018-10-23 15:17:36 +00008import re
Edward Lesmes1e59a242021-04-30 18:38:25 +00009import os
Josip Sokcevic7958e302023-03-01 23:02:21 +000010import scm
Edward Lemur32e3d1e2018-07-12 00:54:05 +000011import subprocess2
12import sys
Gavin Mak5f955df2023-08-30 15:39:13 +000013import urllib.parse
Edward Lemur32e3d1e2018-07-12 00:54:05 +000014
Edward Lemur48836262018-10-18 02:08:06 +000015# Current version of metrics recording.
16# When we add new metrics, the version number will be increased, we display the
17# user what has changed, and ask the user to agree again.
Edward Lesmes1e59a242021-04-30 18:38:25 +000018CURRENT_VERSION = 2
Edward Lemur48836262018-10-18 02:08:06 +000019
Edward Lemur5ba1e9c2018-07-23 18:19:02 +000020APP_URL = 'https://cit-cli-metrics.appspot.com'
21
Edward Lesmes9c349062021-05-06 20:02:39 +000022REPORT_BUILD = os.getenv('DEPOT_TOOLS_REPORT_BUILD')
Mike Frysinger124bb8e2023-09-06 05:48:55 +000023COLLECT_METRICS = (os.getenv('DEPOT_TOOLS_COLLECT_METRICS') != '0'
24 and os.getenv('DEPOT_TOOLS_METRICS') != '0')
Edward Lesmes1e59a242021-04-30 18:38:25 +000025
Edward Lesmesc8f63d32021-06-02 23:51:53 +000026SYNC_STATUS_SUCCESS = 'SYNC_STATUS_SUCCESS'
27SYNC_STATUS_FAILURE = 'SYNC_STATUS_FAILURE'
28
Edward Lesmes1e59a242021-04-30 18:38:25 +000029
Samuel Huang98a7e802019-02-12 15:32:22 +000030def get_notice_countdown_header(countdown):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000031 if countdown == 0:
32 yield ' METRICS COLLECTION IS TAKING PLACE'
33 else:
34 yield ' METRICS COLLECTION WILL START IN %d EXECUTIONS' % countdown
35
Edward Lemur32e3d1e2018-07-12 00:54:05 +000036
Samuel Huang98a7e802019-02-12 15:32:22 +000037def get_notice_version_change_header():
Mike Frysinger124bb8e2023-09-06 05:48:55 +000038 yield ' WE ARE COLLECTING ADDITIONAL METRICS'
39 yield ''
40 yield ' Please review the changes and opt-in again.'
41
Samuel Huang98a7e802019-02-12 15:32:22 +000042
43def get_notice_footer():
Mike Frysinger124bb8e2023-09-06 05:48:55 +000044 yield 'To suppress this message opt in or out using:'
45 yield '$ gclient metrics [--opt-in] [--opt-out]'
46 yield 'For more information please see metrics.README.md'
47 yield 'in your depot_tools checkout or visit'
48 yield 'https://bit.ly/3MpLAYM.'
49
Samuel Huang98a7e802019-02-12 15:32:22 +000050
51def get_change_notice(version):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000052 if version == 0:
53 return [] # No changes for version 0
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +000054
Mike Frysinger124bb8e2023-09-06 05:48:55 +000055 if version == 1:
56 return [
57 'We want to collect the Git version.',
58 'We want to collect information about the HTTP',
59 'requests that depot_tools makes, and the git and',
60 'cipd commands it executes.',
61 '',
62 'We only collect known strings to make sure we',
63 'don\'t record PII.',
64 ]
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +000065
Mike Frysinger124bb8e2023-09-06 05:48:55 +000066 if version == 2:
67 return [
68 'We will start collecting metrics from bots.',
69 'There are no changes for developers.',
70 'If the DEPOT_TOOLS_REPORT_BUILD environment variable is set,',
71 'we will report information about the current build',
72 '(e.g. buildbucket project, bucket, builder and build id),',
73 'and authenticate to the metrics collection server.',
74 'This information will only be recorded for requests',
75 'authenticated as bot service accounts.',
76 ]
Edward Lemur48836262018-10-18 02:08:06 +000077
78
Edward Lemur40764b02018-07-20 18:50:29 +000079KNOWN_PROJECT_URLS = {
Mike Frysinger124bb8e2023-09-06 05:48:55 +000080 'https://chrome-internal.googlesource.com/chrome/ios_internal',
81 'https://chrome-internal.googlesource.com/infra/infra_internal',
82 'https://chromium.googlesource.com/breakpad/breakpad',
83 'https://chromium.googlesource.com/chromium/src',
84 'https://chromium.googlesource.com/chromium/tools/depot_tools',
85 'https://chromium.googlesource.com/crashpad/crashpad',
86 'https://chromium.googlesource.com/external/gyp',
87 'https://chromium.googlesource.com/external/naclports',
88 'https://chromium.googlesource.com/infra/goma/client',
89 'https://chromium.googlesource.com/infra/infra',
90 'https://chromium.googlesource.com/native_client/',
91 'https://chromium.googlesource.com/syzygy',
92 'https://chromium.googlesource.com/v8/v8',
93 'https://dart.googlesource.com/sdk',
94 'https://pdfium.googlesource.com/pdfium',
95 'https://skia.googlesource.com/buildbot',
96 'https://skia.googlesource.com/skia',
97 'https://webrtc.googlesource.com/src',
Edward Lemur40764b02018-07-20 18:50:29 +000098}
99
Edward Lemur03d6d112018-10-23 15:17:36 +0000100KNOWN_HTTP_HOSTS = {
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000101 'chrome-internal-review.googlesource.com',
102 'chromium-review.googlesource.com',
103 'dart-review.googlesource.com',
104 'eu1-mirror-chromium-review.googlesource.com',
105 'pdfium-review.googlesource.com',
106 'skia-review.googlesource.com',
107 'us1-mirror-chromium-review.googlesource.com',
108 'us2-mirror-chromium-review.googlesource.com',
109 'us3-mirror-chromium-review.googlesource.com',
110 'webrtc-review.googlesource.com',
Edward Lemur03d6d112018-10-23 15:17:36 +0000111}
112
113KNOWN_HTTP_METHODS = {
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000114 'DELETE',
115 'GET',
116 'PATCH',
117 'POST',
118 'PUT',
Edward Lemur03d6d112018-10-23 15:17:36 +0000119}
120
121KNOWN_HTTP_PATHS = {
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000122 'accounts': re.compile(r'(/a)?/accounts/.*'),
123 'changes': re.compile(r'(/a)?/changes/([^/]+)?$'),
124 'changes/abandon': re.compile(r'(/a)?/changes/.*/abandon'),
125 'changes/comments': re.compile(r'(/a)?/changes/.*/comments'),
126 'changes/detail': re.compile(r'(/a)?/changes/.*/detail'),
127 'changes/edit': re.compile(r'(/a)?/changes/.*/edit'),
128 'changes/message': re.compile(r'(/a)?/changes/.*/message'),
129 'changes/restore': re.compile(r'(/a)?/changes/.*/restore'),
130 'changes/reviewers': re.compile(r'(/a)?/changes/.*/reviewers/.*'),
131 'changes/revisions/commit':
132 re.compile(r'(/a)?/changes/.*/revisions/.*/commit'),
133 'changes/revisions/review':
134 re.compile(r'(/a)?/changes/.*/revisions/.*/review'),
135 'changes/submit': re.compile(r'(/a)?/changes/.*/submit'),
136 'projects/branches': re.compile(r'(/a)?/projects/.*/branches/.*'),
Edward Lemur03d6d112018-10-23 15:17:36 +0000137}
138
139KNOWN_HTTP_ARGS = {
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000140 'ALL_REVISIONS',
141 'CURRENT_COMMIT',
142 'CURRENT_REVISION',
143 'DETAILED_ACCOUNTS',
144 'LABELS',
Edward Lemur03d6d112018-10-23 15:17:36 +0000145}
146
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000147GIT_VERSION_RE = re.compile(r'git version (\d)\.(\d{0,2})\.(\d{0,2})')
Edward Lemur861640f2018-10-31 19:45:31 +0000148
Edward Lemurfec80c42018-11-01 23:14:14 +0000149KNOWN_SUBCOMMAND_ARGS = {
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000150 'cc', 'hashtag', 'l=Auto-Submit+1', 'l=Code-Review+1', 'l=Code-Review+2',
151 'l=Commit-Queue+1', 'l=Commit-Queue+2', 'label', 'm', 'notify=ALL',
152 'notify=NONE', 'private', 'r', 'ready', 'topic', 'wip'
Edward Lemurfec80c42018-11-01 23:14:14 +0000153}
154
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000155
156def get_python_version():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000157 """Return the python version in the major.minor.micro format."""
158 return '{v.major}.{v.minor}.{v.micro}'.format(v=sys.version_info)
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000159
160
Edward Lemur861640f2018-10-31 19:45:31 +0000161def get_git_version():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000162 """Return the Git version in the major.minor.micro format."""
163 p = subprocess2.Popen(['git', '--version'],
164 stdout=subprocess2.PIPE,
165 stderr=subprocess2.PIPE)
166 stdout, _ = p.communicate()
167 match = GIT_VERSION_RE.match(stdout.decode('utf-8'))
168 if not match:
169 return None
170 return '%s.%s.%s' % match.groups()
Edward Lemur861640f2018-10-31 19:45:31 +0000171
172
Edward Lesmes1e59a242021-04-30 18:38:25 +0000173def get_bot_metrics():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000174 try:
175 project, bucket, builder, build = REPORT_BUILD.split('/')
176 return {
177 'build_id': int(build),
178 'builder': {
179 'project': project,
180 'bucket': bucket,
181 'builder': builder,
182 },
183 }
184 except (AttributeError, ValueError):
185 return None
Edward Lesmes1e59a242021-04-30 18:38:25 +0000186
187
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000188def return_code_from_exception(exception):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000189 """Returns the exit code that would result of raising the exception."""
190 if exception is None:
191 return 0
192 e = exception[1]
193 if isinstance(e, KeyboardInterrupt):
194 return 130
195 if isinstance(e, SystemExit):
196 return e.code
197 return 1
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000198
199
Edward Lemurfec80c42018-11-01 23:14:14 +0000200def extract_known_subcommand_args(args):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000201 """Extract the known arguments from the passed list of args."""
202 known_args = []
203 for arg in args:
204 if arg in KNOWN_SUBCOMMAND_ARGS:
205 known_args.append(arg)
206 else:
207 arg = arg.split('=')[0]
208 if arg in KNOWN_SUBCOMMAND_ARGS:
209 known_args.append(arg)
210 return sorted(known_args)
Edward Lemurfec80c42018-11-01 23:14:14 +0000211
212
Edward Lemur03d6d112018-10-23 15:17:36 +0000213def extract_http_metrics(request_uri, method, status, response_time):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000214 """Extract metrics from the request URI.
Edward Lemur03d6d112018-10-23 15:17:36 +0000215
216 Extracts the host, path, and arguments from the request URI, and returns them
217 along with the method, status and response time.
218
219 The host, method, path and arguments must be in the KNOWN_HTTP_* constants
220 defined above.
221
222 Arguments are the values of the o= url parameter. In Gerrit, additional fields
223 can be obtained by adding o parameters, each option requires more database
224 lookups and slows down the query response time to the client, so we make an
225 effort to collect them.
226
227 The regex defined in KNOWN_HTTP_PATH_RES are checked against the path, and
228 those that match will be returned.
229 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000230 http_metrics = {
231 'status': status,
232 'response_time': response_time,
233 }
Edward Lemur03d6d112018-10-23 15:17:36 +0000234
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000235 if method in KNOWN_HTTP_METHODS:
236 http_metrics['method'] = method
Edward Lemur03d6d112018-10-23 15:17:36 +0000237
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000238 parsed_url = urllib.parse.urlparse(request_uri)
Edward Lemur03d6d112018-10-23 15:17:36 +0000239
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000240 if parsed_url.netloc in KNOWN_HTTP_HOSTS:
241 http_metrics['host'] = parsed_url.netloc
Edward Lemur03d6d112018-10-23 15:17:36 +0000242
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000243 for name, path_re in KNOWN_HTTP_PATHS.items():
244 if path_re.match(parsed_url.path):
245 http_metrics['path'] = name
246 break
Edward Lemur03d6d112018-10-23 15:17:36 +0000247
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000248 parsed_query = urllib.parse.parse_qs(parsed_url.query)
Edward Lemur03d6d112018-10-23 15:17:36 +0000249
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000250 # Collect o-parameters from the request.
251 args = [arg for arg in parsed_query.get('o', []) if arg in KNOWN_HTTP_ARGS]
252 if args:
253 http_metrics['arguments'] = args
Edward Lemur03d6d112018-10-23 15:17:36 +0000254
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000255 return http_metrics
Edward Lemur03d6d112018-10-23 15:17:36 +0000256
257
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000258def get_repo_timestamp(path_to_repo):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000259 """Get an approximate timestamp for the upstream of |path_to_repo|.
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000260
261 Returns the top two bits of the timestamp of the HEAD for the upstream of the
262 branch path_to_repo is checked out at.
263 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000264 # Get the upstream for the current branch. If we're not in a branch,
265 # fallback to HEAD.
266 try:
267 upstream = scm.GIT.GetUpstreamBranch(path_to_repo) or 'HEAD'
268 except subprocess2.CalledProcessError:
269 upstream = 'HEAD'
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000270
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000271 # Get the timestamp of the HEAD for the upstream of the current branch.
272 p = subprocess2.Popen(
273 ['git', '-C', path_to_repo, 'log', '-n1', upstream, '--format=%at'],
274 stdout=subprocess2.PIPE,
275 stderr=subprocess2.PIPE)
276 stdout, _ = p.communicate()
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000277
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000278 # If there was an error, give up.
279 if p.returncode != 0:
280 return None
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000281
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000282 return stdout.strip()
283
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000284
Hans Wennborg24b5f902019-03-15 18:51:27 +0000285def print_boxed_text(out, min_width, lines):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000286 [EW, NS, SE, SW, NE, NW] = list('=|++++')
287 width = max(min_width, max(len(line) for line in lines))
288 out(SE + EW * (width + 2) + SW + '\n')
289 for line in lines:
290 out('%s %-*s %s\n' % (NS, width, line, NS))
291 out(NE + EW * (width + 2) + NW + '\n')
292
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000293
294def print_notice(countdown):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000295 """Print a notice to let the user know the status of metrics collection."""
296 lines = list(get_notice_countdown_header(countdown))
297 lines.append('')
298 lines += list(get_notice_footer())
299 print_boxed_text(sys.stderr.write, 49, lines)
300
Edward Lemur48836262018-10-18 02:08:06 +0000301
302def print_version_change(config_version):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000303 """Print a notice to let the user know we are collecting more metrics."""
304 lines = list(get_notice_version_change_header())
305 for version in range(config_version + 1, CURRENT_VERSION + 1):
306 lines.append('')
307 lines += get_change_notice(version)
308 print_boxed_text(sys.stderr.write, 49, lines)