blob: e99bd86d64da65e3cf3f878519b9c529facb8c32 [file] [log] [blame]
Takuto Ikuta84e43fa2021-01-19 02:51:50 +00001#!/usr/bin/env python3
Takuto Ikuta9af233a2018-11-29 03:53:53 +00002# Copyright 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.
Takuto Ikuta9af233a2018-11-29 03:53:53 +00005"""
6This is script to upload ninja_log from googler.
7
8Server side implementation is in
9https://cs.chromium.org/chromium/infra/go/src/infra/appengine/chromium_build_stats/
10
11Uploaded ninjalog is stored in BigQuery table having following schema.
12https://cs.chromium.org/chromium/infra/go/src/infra/appengine/chromium_build_stats/ninjaproto/ninjalog.proto
13
14The log will be used to analyze user side build performance.
15"""
16
17import argparse
Takuto Ikuta9af233a2018-11-29 03:53:53 +000018import gzip
Takuto Ikuta84e43fa2021-01-19 02:51:50 +000019import io
Takuto Ikuta9af233a2018-11-29 03:53:53 +000020import json
21import logging
22import multiprocessing
23import os
24import platform
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000025import subprocess
Takuto Ikuta9af233a2018-11-29 03:53:53 +000026import sys
Takuto Ikuta36248fc2019-01-11 03:02:32 +000027import time
Takuto Ikuta9af233a2018-11-29 03:53:53 +000028
Takuto Ikuta84e43fa2021-01-19 02:51:50 +000029from third_party.six.moves.urllib import request
Takuto Ikuta9af233a2018-11-29 03:53:53 +000030
Victor Hugo Vianna Silva787f2f02021-11-11 03:27:28 +000031# These build configs affect build performance.
32ALLOWLISTED_CONFIGS = ('symbol_level', 'use_goma', 'is_debug',
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000033 'is_component_build', 'enable_nacl', 'host_os',
Victor Hugo Vianna Silva787f2f02021-11-11 03:27:28 +000034 'host_cpu', 'target_os', 'target_cpu',
35 'blink_symbol_level', 'is_java_debug',
36 'treat_warnings_as_errors', 'disable_android_lint',
Peter Wen4ee32ce2022-03-02 22:03:27 +000037 'use_errorprone_java_compiler', 'incremental_install',
38 'android_static_analysis')
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000039
Takuto Ikutac8069af2019-01-09 06:24:56 +000040
Takuto Ikutaa6573312022-01-20 00:24:57 +000041def IsGoogler():
Mike Frysinger124bb8e2023-09-06 05:48:55 +000042 """Check whether this user is Googler or not."""
43 p = subprocess.run('goma_auth info',
44 stdout=subprocess.PIPE,
45 stderr=subprocess.PIPE,
46 universal_newlines=True,
47 shell=True)
48 if p.returncode != 0:
49 return False
50 lines = p.stdout.splitlines()
51 if len(lines) == 0:
52 return False
53 l = lines[0]
54 # |l| will be like 'Login as <user>@google.com' for googler using goma.
55 return l.startswith('Login as ') and l.endswith('@google.com')
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000056
Takuto Ikuta9af233a2018-11-29 03:53:53 +000057
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000058def ParseGNArgs(gn_args):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000059 """Parse gn_args as json and return config dictionary."""
60 configs = json.loads(gn_args)
61 build_configs = {}
Takuto Ikutac8069af2019-01-09 06:24:56 +000062
Mike Frysinger124bb8e2023-09-06 05:48:55 +000063 for config in configs:
64 key = config["name"]
65 if key not in ALLOWLISTED_CONFIGS:
66 continue
67 if 'current' in config:
68 build_configs[key] = config['current']['value']
69 else:
70 build_configs[key] = config['default']['value']
Takuto Ikutac8069af2019-01-09 06:24:56 +000071
Mike Frysinger124bb8e2023-09-06 05:48:55 +000072 return build_configs
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000073
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000074
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000075def GetBuildTargetFromCommandLine(cmdline):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000076 """Get build targets from commandline."""
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000077
Mike Frysinger124bb8e2023-09-06 05:48:55 +000078 # Skip argv0, argv1: ['/path/to/python3', '/path/to/depot_tools/ninja.py']
79 idx = 2
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000080
Mike Frysinger124bb8e2023-09-06 05:48:55 +000081 # Skipping all args that involve these flags, and taking all remaining args
82 # as targets.
83 onearg_flags = ('-C', '-d', '-f', '-j', '-k', '-l', '-p', '-t', '-w')
84 zeroarg_flags = ('--version', '-n', '-v')
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000085
Mike Frysinger124bb8e2023-09-06 05:48:55 +000086 targets = []
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000087
Mike Frysinger124bb8e2023-09-06 05:48:55 +000088 while idx < len(cmdline):
89 arg = cmdline[idx]
90 if arg in onearg_flags:
91 idx += 2
92 continue
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000093
Mike Frysinger124bb8e2023-09-06 05:48:55 +000094 if (arg[:2] in onearg_flags or arg in zeroarg_flags):
95 idx += 1
96 continue
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000097
Mike Frysinger124bb8e2023-09-06 05:48:55 +000098 # A target doesn't start with '-'.
99 if arg.startswith('-'):
100 idx += 1
101 continue
Junji Watanabe8d6af632023-03-02 07:39:39 +0000102
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000103 # Avoid uploading absolute paths accidentally. e.g. b/270907050
104 if os.path.isabs(arg):
105 idx += 1
106 continue
Junji Watanabe8d6af632023-03-02 07:39:39 +0000107
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000108 targets.append(arg)
109 idx += 1
Takuto Ikutacf56a4b2018-12-18 05:47:26 +0000110
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000111 return targets
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000112
Takuto Ikutacf56a4b2018-12-18 05:47:26 +0000113
Takuto Ikutac8069af2019-01-09 06:24:56 +0000114def GetJflag(cmdline):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000115 """Parse cmdline to get flag value for -j"""
Takuto Ikutac8069af2019-01-09 06:24:56 +0000116
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000117 for i in range(len(cmdline)):
118 if (cmdline[i] == '-j' and i + 1 < len(cmdline)
119 and cmdline[i + 1].isdigit()):
120 return int(cmdline[i + 1])
Takuto Ikutac8069af2019-01-09 06:24:56 +0000121
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000122 if (cmdline[i].startswith('-j') and cmdline[i][len('-j'):].isdigit()):
123 return int(cmdline[i][len('-j'):])
Takuto Ikutac8069af2019-01-09 06:24:56 +0000124
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000125
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000126def GetMetadata(cmdline, ninjalog):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000127 """Get metadata for uploaded ninjalog.
Takuto Ikutac8069af2019-01-09 06:24:56 +0000128
129 Returned metadata has schema defined in
130 https://cs.chromium.org?q="type+Metadata+struct+%7B"+file:%5Einfra/go/src/infra/appengine/chromium_build_stats/ninjalog/
131
132 TODO(tikuta): Collect GOMA_* env var.
133 """
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000134
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000135 build_dir = os.path.dirname(ninjalog)
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000136
137 build_configs = {}
138
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000139 try:
140 args = ['gn', 'args', build_dir, '--list', '--short', '--json']
141 if sys.platform == 'win32':
142 # gn in PATH is bat file in windows environment (except cygwin).
143 args = ['cmd', '/c'] + args
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000144
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000145 gn_args = subprocess.check_output(args)
146 build_configs = ParseGNArgs(gn_args)
147 except subprocess.CalledProcessError as e:
148 logging.error("Failed to call gn %s", e)
149 build_configs = {}
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000150
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000151 # Stringify config.
152 for k in build_configs:
153 build_configs[k] = str(build_configs[k])
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000154
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000155 metadata = {
156 'platform': platform.system(),
157 'cpu_core': multiprocessing.cpu_count(),
158 'build_configs': build_configs,
159 'targets': GetBuildTargetFromCommandLine(cmdline),
160 }
161
162 jflag = GetJflag(cmdline)
163 if jflag is not None:
164 metadata['jobs'] = jflag
165
166 return metadata
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000167
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000168
169def GetNinjalog(cmdline):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000170 """GetNinjalog returns the path to ninjalog from cmdline."""
171 # ninjalog is in current working directory by default.
172 ninjalog_dir = '.'
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000173
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000174 i = 0
175 while i < len(cmdline):
176 cmd = cmdline[i]
177 i += 1
178 if cmd == '-C' and i < len(cmdline):
179 ninjalog_dir = cmdline[i]
180 i += 1
181 continue
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000182
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000183 if cmd.startswith('-C') and len(cmd) > len('-C'):
184 ninjalog_dir = cmd[len('-C'):]
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000185
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000186 return os.path.join(ninjalog_dir, '.ninja_log')
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000187
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000188
189def main():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000190 parser = argparse.ArgumentParser()
191 parser.add_argument('--server',
192 default='chromium-build-stats.appspot.com',
193 help='server to upload ninjalog file.')
194 parser.add_argument('--ninjalog', help='ninjalog file to upload.')
195 parser.add_argument('--verbose',
196 action='store_true',
197 help='Enable verbose logging.')
198 parser.add_argument('--cmdline',
199 required=True,
200 nargs=argparse.REMAINDER,
201 help='command line args passed to ninja.')
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000202
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000203 args = parser.parse_args()
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000204
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000205 if args.verbose:
206 logging.basicConfig(level=logging.INFO)
207 else:
208 # Disable logging.
209 logging.disable(logging.CRITICAL)
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000210
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000211 if not IsGoogler():
212 return 0
213
214 ninjalog = args.ninjalog or GetNinjalog(args.cmdline)
215 if not os.path.isfile(ninjalog):
216 logging.warning("ninjalog is not found in %s", ninjalog)
217 return 1
218
219 # We assume that each ninja invocation interval takes at least 2 seconds.
220 # This is not to have duplicate entry in server when current build is no-op.
221 if os.stat(ninjalog).st_mtime < time.time() - 2:
222 logging.info("ninjalog is not updated recently %s", ninjalog)
223 return 0
224
225 output = io.BytesIO()
226
227 with open(ninjalog) as f:
228 with gzip.GzipFile(fileobj=output, mode='wb') as g:
229 g.write(f.read().encode())
230 g.write(b'# end of ninja log\n')
231
232 metadata = GetMetadata(args.cmdline, ninjalog)
233 logging.info('send metadata: %s', json.dumps(metadata))
234 g.write(json.dumps(metadata).encode())
235
236 resp = request.urlopen(
237 request.Request('https://' + args.server + '/upload_ninja_log/',
238 data=output.getvalue(),
239 headers={'Content-Encoding': 'gzip'}))
240
241 if resp.status != 200:
242 logging.warning("unexpected status code for response: %s", resp.status)
243 return 1
244
245 logging.info('response header: %s', resp.headers)
246 logging.info('response content: %s', resp.read())
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000247 return 0
248
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000249
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000250if __name__ == '__main__':
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000251 sys.exit(main())