blob: 6b89826fe8d75936743681f3ae980ade5498ab94 [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(
Takuto Ikutab5393e52023-11-30 07:15:55 +000054 "cipd auth-info",
Takuto Ikutadf3e5772023-11-16 07:14:49 +000055 stdout=subprocess.PIPE,
56 stderr=subprocess.PIPE,
Takuto Ikutab5393e52023-11-30 07:15:55 +000057 text=True,
Takuto Ikutadf3e5772023-11-16 07:14:49 +000058 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]
Takuto Ikutab5393e52023-11-30 07:15:55 +000066 # |l| will be like 'Logged in as <user>@google.com.' for googler using
67 # reclient.
68 return l.startswith("Logged in as ") and l.endswith("@google.com.")
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000069
Takuto Ikuta9af233a2018-11-29 03:53:53 +000070
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000071def ParseGNArgs(gn_args):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000072 """Parse gn_args as json and return config dictionary."""
73 configs = json.loads(gn_args)
74 build_configs = {}
Takuto Ikutac8069af2019-01-09 06:24:56 +000075
Mike Frysinger124bb8e2023-09-06 05:48:55 +000076 for config in configs:
77 key = config["name"]
78 if key not in ALLOWLISTED_CONFIGS:
79 continue
Takuto Ikutadf3e5772023-11-16 07:14:49 +000080 if "current" in config:
81 build_configs[key] = config["current"]["value"]
Mike Frysinger124bb8e2023-09-06 05:48:55 +000082 else:
Takuto Ikutadf3e5772023-11-16 07:14:49 +000083 build_configs[key] = config["default"]["value"]
Takuto Ikutac8069af2019-01-09 06:24:56 +000084
Mike Frysinger124bb8e2023-09-06 05:48:55 +000085 return build_configs
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000086
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000087
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000088def GetBuildTargetFromCommandLine(cmdline):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000089 """Get build targets from commandline."""
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000090
Mike Frysinger124bb8e2023-09-06 05:48:55 +000091 # Skip argv0, argv1: ['/path/to/python3', '/path/to/depot_tools/ninja.py']
92 idx = 2
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000093
Mike Frysinger124bb8e2023-09-06 05:48:55 +000094 # Skipping all args that involve these flags, and taking all remaining args
95 # as targets.
Takuto Ikutadf3e5772023-11-16 07:14:49 +000096 onearg_flags = ("-C", "-d", "-f", "-j", "-k", "-l", "-p", "-t", "-w")
97 zeroarg_flags = ("--version", "-n", "-v")
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000098
Mike Frysinger124bb8e2023-09-06 05:48:55 +000099 targets = []
Takuto Ikutacf56a4b2018-12-18 05:47:26 +0000100
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000101 while idx < len(cmdline):
102 arg = cmdline[idx]
103 if arg in onearg_flags:
104 idx += 2
105 continue
Takuto Ikutacf56a4b2018-12-18 05:47:26 +0000106
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000107 if arg[:2] in onearg_flags or arg in zeroarg_flags:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000108 idx += 1
109 continue
Takuto Ikutacf56a4b2018-12-18 05:47:26 +0000110
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000111 # A target doesn't start with '-'.
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000112 if arg.startswith("-"):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000113 idx += 1
114 continue
Junji Watanabe8d6af632023-03-02 07:39:39 +0000115
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000116 # Avoid uploading absolute paths accidentally. e.g. b/270907050
117 if os.path.isabs(arg):
118 idx += 1
119 continue
Junji Watanabe8d6af632023-03-02 07:39:39 +0000120
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000121 targets.append(arg)
122 idx += 1
Takuto Ikutacf56a4b2018-12-18 05:47:26 +0000123
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000124 return targets
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000125
Takuto Ikutacf56a4b2018-12-18 05:47:26 +0000126
Takuto Ikutac8069af2019-01-09 06:24:56 +0000127def GetJflag(cmdline):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000128 """Parse cmdline to get flag value for -j"""
Takuto Ikutac8069af2019-01-09 06:24:56 +0000129
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000130 for i in range(len(cmdline)):
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000131 if (cmdline[i] == "-j" and i + 1 < len(cmdline)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000132 and cmdline[i + 1].isdigit()):
133 return int(cmdline[i + 1])
Takuto Ikutac8069af2019-01-09 06:24:56 +0000134
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000135 if cmdline[i].startswith("-j") and cmdline[i][len("-j"):].isdigit():
136 return int(cmdline[i][len("-j"):])
Takuto Ikutac8069af2019-01-09 06:24:56 +0000137
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000138
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000139def GetMetadata(cmdline, ninjalog):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000140 """Get metadata for uploaded ninjalog.
Takuto Ikutac8069af2019-01-09 06:24:56 +0000141
142 Returned metadata has schema defined in
143 https://cs.chromium.org?q="type+Metadata+struct+%7B"+file:%5Einfra/go/src/infra/appengine/chromium_build_stats/ninjalog/
144
145 TODO(tikuta): Collect GOMA_* env var.
146 """
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000147
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000148 build_dir = os.path.dirname(ninjalog)
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000149
150 build_configs = {}
151
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000152 try:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000153 args = ["gn", "args", build_dir, "--list", "--short", "--json"]
154 if sys.platform == "win32":
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000155 # gn in PATH is bat file in windows environment (except cygwin).
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000156 args = ["cmd", "/c"] + args
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000157
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000158 gn_args = subprocess.check_output(args)
159 build_configs = ParseGNArgs(gn_args)
160 except subprocess.CalledProcessError as e:
161 logging.error("Failed to call gn %s", e)
162 build_configs = {}
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000163
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000164 # Stringify config.
165 for k in build_configs:
166 build_configs[k] = str(build_configs[k])
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000167
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000168 metadata = {
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000169 "platform": platform.system(),
170 "cpu_core": multiprocessing.cpu_count(),
171 "build_configs": build_configs,
172 "targets": GetBuildTargetFromCommandLine(cmdline),
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000173 }
174
175 jflag = GetJflag(cmdline)
176 if jflag is not None:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000177 metadata["jobs"] = jflag
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000178
179 return metadata
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000180
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000181
182def GetNinjalog(cmdline):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000183 """GetNinjalog returns the path to ninjalog from cmdline."""
184 # ninjalog is in current working directory by default.
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000185 ninjalog_dir = "."
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000186
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000187 i = 0
188 while i < len(cmdline):
189 cmd = cmdline[i]
190 i += 1
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000191 if cmd == "-C" and i < len(cmdline):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000192 ninjalog_dir = cmdline[i]
193 i += 1
194 continue
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000195
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000196 if cmd.startswith("-C") and len(cmd) > len("-C"):
197 ninjalog_dir = cmd[len("-C"):]
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000198
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000199 return os.path.join(ninjalog_dir, ".ninja_log")
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000200
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000201
202def main():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000203 parser = argparse.ArgumentParser()
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000204 parser.add_argument(
205 "--server",
206 default="chromium-build-stats.appspot.com",
207 help="server to upload ninjalog file.",
208 )
209 parser.add_argument("--ninjalog", help="ninjalog file to upload.")
210 parser.add_argument("--verbose",
211 action="store_true",
212 help="Enable verbose logging.")
213 parser.add_argument(
214 "--cmdline",
215 required=True,
216 nargs=argparse.REMAINDER,
217 help="command line args passed to ninja.",
218 )
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000219
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000220 args = parser.parse_args()
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000221
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000222 if args.verbose:
223 logging.basicConfig(level=logging.INFO)
224 else:
225 # Disable logging.
226 logging.disable(logging.CRITICAL)
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000227
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000228 if not IsGoogler():
229 return 0
230
231 ninjalog = args.ninjalog or GetNinjalog(args.cmdline)
232 if not os.path.isfile(ninjalog):
233 logging.warning("ninjalog is not found in %s", ninjalog)
234 return 1
235
236 # We assume that each ninja invocation interval takes at least 2 seconds.
237 # This is not to have duplicate entry in server when current build is no-op.
238 if os.stat(ninjalog).st_mtime < time.time() - 2:
239 logging.info("ninjalog is not updated recently %s", ninjalog)
240 return 0
241
242 output = io.BytesIO()
243
244 with open(ninjalog) as f:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000245 with gzip.GzipFile(fileobj=output, mode="wb") as g:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000246 g.write(f.read().encode())
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000247 g.write(b"# end of ninja log\n")
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000248
249 metadata = GetMetadata(args.cmdline, ninjalog)
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000250 logging.info("send metadata: %s", json.dumps(metadata))
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000251 g.write(json.dumps(metadata).encode())
252
Gavin Mak7f5b53f2023-09-07 18:13:01 +0000253 resp = urllib.request.urlopen(
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000254 urllib.request.Request(
255 "https://" + args.server + "/upload_ninja_log/",
256 data=output.getvalue(),
257 headers={"Content-Encoding": "gzip"},
258 ))
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000259
260 if resp.status != 200:
261 logging.warning("unexpected status code for response: %s", resp.status)
262 return 1
263
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000264 logging.info("response header: %s", resp.headers)
265 logging.info("response content: %s", resp.read())
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000266 return 0
267
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000268
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000269if __name__ == "__main__":
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000270 sys.exit(main())