blob: 80f72698b6deb91ecccf3fc74dfbaca493ee42c4 [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
Gavin Mak7f5b53f2023-09-07 18:13:01 +000028import urllib.request
Takuto Ikuta9af233a2018-11-29 03:53:53 +000029
Victor Hugo Vianna Silva787f2f02021-11-11 03:27:28 +000030# These build configs affect build performance.
Takuto Ikutadf3e5772023-11-16 07:14:49 +000031ALLOWLISTED_CONFIGS = (
32 "symbol_level",
33 "use_goma",
34 "is_debug",
35 "is_component_build",
36 "enable_nacl",
37 "host_os",
38 "host_cpu",
39 "target_os",
40 "target_cpu",
41 "blink_symbol_level",
42 "is_java_debug",
43 "treat_warnings_as_errors",
44 "disable_android_lint",
45 "use_errorprone_java_compiler",
46 "incremental_install",
47 "android_static_analysis",
48)
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000049
Takuto Ikutac8069af2019-01-09 06:24:56 +000050
Takuto Ikutaa6573312022-01-20 00:24:57 +000051def IsGoogler():
Mike Frysinger124bb8e2023-09-06 05:48:55 +000052 """Check whether this user is Googler or not."""
Takuto Ikutadf3e5772023-11-16 07:14:49 +000053 p = subprocess.run(
54 "goma_auth info",
55 stdout=subprocess.PIPE,
56 stderr=subprocess.PIPE,
57 universal_newlines=True,
58 shell=True,
59 )
Mike Frysinger124bb8e2023-09-06 05:48:55 +000060 if p.returncode != 0:
61 return False
62 lines = p.stdout.splitlines()
63 if len(lines) == 0:
64 return False
65 l = lines[0]
66 # |l| will be like 'Login as <user>@google.com' for googler using goma.
Takuto Ikutadf3e5772023-11-16 07:14:49 +000067 return l.startswith("Login as ") and l.endswith("@google.com")
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000068
Takuto Ikuta9af233a2018-11-29 03:53:53 +000069
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000070def ParseGNArgs(gn_args):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000071 """Parse gn_args as json and return config dictionary."""
72 configs = json.loads(gn_args)
73 build_configs = {}
Takuto Ikutac8069af2019-01-09 06:24:56 +000074
Mike Frysinger124bb8e2023-09-06 05:48:55 +000075 for config in configs:
76 key = config["name"]
77 if key not in ALLOWLISTED_CONFIGS:
78 continue
Takuto Ikutadf3e5772023-11-16 07:14:49 +000079 if "current" in config:
80 build_configs[key] = config["current"]["value"]
Mike Frysinger124bb8e2023-09-06 05:48:55 +000081 else:
Takuto Ikutadf3e5772023-11-16 07:14:49 +000082 build_configs[key] = config["default"]["value"]
Takuto Ikutac8069af2019-01-09 06:24:56 +000083
Mike Frysinger124bb8e2023-09-06 05:48:55 +000084 return build_configs
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000085
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000086
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000087def GetBuildTargetFromCommandLine(cmdline):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000088 """Get build targets from commandline."""
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000089
Mike Frysinger124bb8e2023-09-06 05:48:55 +000090 # Skip argv0, argv1: ['/path/to/python3', '/path/to/depot_tools/ninja.py']
91 idx = 2
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000092
Mike Frysinger124bb8e2023-09-06 05:48:55 +000093 # Skipping all args that involve these flags, and taking all remaining args
94 # as targets.
Takuto Ikutadf3e5772023-11-16 07:14:49 +000095 onearg_flags = ("-C", "-d", "-f", "-j", "-k", "-l", "-p", "-t", "-w")
96 zeroarg_flags = ("--version", "-n", "-v")
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000097
Mike Frysinger124bb8e2023-09-06 05:48:55 +000098 targets = []
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000099
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000100 while idx < len(cmdline):
101 arg = cmdline[idx]
102 if arg in onearg_flags:
103 idx += 2
104 continue
Takuto Ikutacf56a4b2018-12-18 05:47:26 +0000105
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000106 if arg[:2] in onearg_flags or arg in zeroarg_flags:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000107 idx += 1
108 continue
Takuto Ikutacf56a4b2018-12-18 05:47:26 +0000109
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000110 # A target doesn't start with '-'.
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000111 if arg.startswith("-"):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000112 idx += 1
113 continue
Junji Watanabe8d6af632023-03-02 07:39:39 +0000114
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000115 # Avoid uploading absolute paths accidentally. e.g. b/270907050
116 if os.path.isabs(arg):
117 idx += 1
118 continue
Junji Watanabe8d6af632023-03-02 07:39:39 +0000119
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000120 targets.append(arg)
121 idx += 1
Takuto Ikutacf56a4b2018-12-18 05:47:26 +0000122
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000123 return targets
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000124
Takuto Ikutacf56a4b2018-12-18 05:47:26 +0000125
Takuto Ikutac8069af2019-01-09 06:24:56 +0000126def GetJflag(cmdline):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000127 """Parse cmdline to get flag value for -j"""
Takuto Ikutac8069af2019-01-09 06:24:56 +0000128
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000129 for i in range(len(cmdline)):
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000130 if (cmdline[i] == "-j" and i + 1 < len(cmdline)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000131 and cmdline[i + 1].isdigit()):
132 return int(cmdline[i + 1])
Takuto Ikutac8069af2019-01-09 06:24:56 +0000133
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000134 if cmdline[i].startswith("-j") and cmdline[i][len("-j"):].isdigit():
135 return int(cmdline[i][len("-j"):])
Takuto Ikutac8069af2019-01-09 06:24:56 +0000136
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000137
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000138def GetMetadata(cmdline, ninjalog):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000139 """Get metadata for uploaded ninjalog.
Takuto Ikutac8069af2019-01-09 06:24:56 +0000140
141 Returned metadata has schema defined in
142 https://cs.chromium.org?q="type+Metadata+struct+%7B"+file:%5Einfra/go/src/infra/appengine/chromium_build_stats/ninjalog/
143
144 TODO(tikuta): Collect GOMA_* env var.
145 """
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000146
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000147 build_dir = os.path.dirname(ninjalog)
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000148
149 build_configs = {}
150
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000151 try:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000152 args = ["gn", "args", build_dir, "--list", "--short", "--json"]
153 if sys.platform == "win32":
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000154 # gn in PATH is bat file in windows environment (except cygwin).
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000155 args = ["cmd", "/c"] + args
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000156
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000157 gn_args = subprocess.check_output(args)
158 build_configs = ParseGNArgs(gn_args)
159 except subprocess.CalledProcessError as e:
160 logging.error("Failed to call gn %s", e)
161 build_configs = {}
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000162
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000163 # Stringify config.
164 for k in build_configs:
165 build_configs[k] = str(build_configs[k])
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000166
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000167 metadata = {
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000168 "platform": platform.system(),
169 "cpu_core": multiprocessing.cpu_count(),
170 "build_configs": build_configs,
171 "targets": GetBuildTargetFromCommandLine(cmdline),
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000172 }
173
174 jflag = GetJflag(cmdline)
175 if jflag is not None:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000176 metadata["jobs"] = jflag
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000177
178 return metadata
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000179
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000180
181def GetNinjalog(cmdline):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000182 """GetNinjalog returns the path to ninjalog from cmdline."""
183 # ninjalog is in current working directory by default.
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000184 ninjalog_dir = "."
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000185
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000186 i = 0
187 while i < len(cmdline):
188 cmd = cmdline[i]
189 i += 1
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000190 if cmd == "-C" and i < len(cmdline):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000191 ninjalog_dir = cmdline[i]
192 i += 1
193 continue
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000194
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000195 if cmd.startswith("-C") and len(cmd) > len("-C"):
196 ninjalog_dir = cmd[len("-C"):]
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000197
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000198 return os.path.join(ninjalog_dir, ".ninja_log")
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000199
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000200
201def main():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000202 parser = argparse.ArgumentParser()
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000203 parser.add_argument(
204 "--server",
205 default="chromium-build-stats.appspot.com",
206 help="server to upload ninjalog file.",
207 )
208 parser.add_argument("--ninjalog", help="ninjalog file to upload.")
209 parser.add_argument("--verbose",
210 action="store_true",
211 help="Enable verbose logging.")
212 parser.add_argument(
213 "--cmdline",
214 required=True,
215 nargs=argparse.REMAINDER,
216 help="command line args passed to ninja.",
217 )
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000218
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000219 args = parser.parse_args()
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000220
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000221 if args.verbose:
222 logging.basicConfig(level=logging.INFO)
223 else:
224 # Disable logging.
225 logging.disable(logging.CRITICAL)
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000226
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000227 if not IsGoogler():
228 return 0
229
230 ninjalog = args.ninjalog or GetNinjalog(args.cmdline)
231 if not os.path.isfile(ninjalog):
232 logging.warning("ninjalog is not found in %s", ninjalog)
233 return 1
234
235 # We assume that each ninja invocation interval takes at least 2 seconds.
236 # This is not to have duplicate entry in server when current build is no-op.
237 if os.stat(ninjalog).st_mtime < time.time() - 2:
238 logging.info("ninjalog is not updated recently %s", ninjalog)
239 return 0
240
241 output = io.BytesIO()
242
243 with open(ninjalog) as f:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000244 with gzip.GzipFile(fileobj=output, mode="wb") as g:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000245 g.write(f.read().encode())
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000246 g.write(b"# end of ninja log\n")
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000247
248 metadata = GetMetadata(args.cmdline, ninjalog)
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000249 logging.info("send metadata: %s", json.dumps(metadata))
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000250 g.write(json.dumps(metadata).encode())
251
Gavin Mak7f5b53f2023-09-07 18:13:01 +0000252 resp = urllib.request.urlopen(
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000253 urllib.request.Request(
254 "https://" + args.server + "/upload_ninja_log/",
255 data=output.getvalue(),
256 headers={"Content-Encoding": "gzip"},
257 ))
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000258
259 if resp.status != 200:
260 logging.warning("unexpected status code for response: %s", resp.status)
261 return 1
262
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000263 logging.info("response header: %s", resp.headers)
264 logging.info("response content: %s", resp.read())
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000265 return 0
266
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000267
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000268if __name__ == "__main__":
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000269 sys.exit(main())