blob: 1f1d98dc0efc14b3ead472b1c46f8d78cf29a362 [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 Ikuta4bb3a7d2021-06-09 03:58:27 +000029from third_party.six.moves import http_client
Takuto Ikuta84e43fa2021-01-19 02:51:50 +000030from third_party.six.moves.urllib import error
31from third_party.six.moves.urllib import request
Takuto Ikuta9af233a2018-11-29 03:53:53 +000032
Takuto Ikutac8069af2019-01-09 06:24:56 +000033# These build configs affect build performance a lot.
Bruce Dawson9e633032020-08-04 21:50:29 +000034# TODO(https://crbug.com/900161): Add 'blink_symbol_level' and
35# 'enable_js_type_check'.
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000036WHITELISTED_CONFIGS = ('symbol_level', 'use_goma', 'is_debug',
37 'is_component_build', 'enable_nacl', 'host_os',
38 'host_cpu', 'target_os', 'target_cpu')
39
Takuto Ikutac8069af2019-01-09 06:24:56 +000040
Takuto Ikuta9af233a2018-11-29 03:53:53 +000041def IsGoogler(server):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000042 """Check whether this script run inside corp network."""
43 try:
Takuto Ikuta84e43fa2021-01-19 02:51:50 +000044 resp = request.urlopen('https://' + server + '/should-upload')
45 return resp.read() == b'Success'
Takuto Ikuta4bb3a7d2021-06-09 03:58:27 +000046 except (error.URLError, http_client.RemoteDisconnected):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000047 return False
48
Takuto Ikuta9af233a2018-11-29 03:53:53 +000049
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000050def ParseGNArgs(gn_args):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000051 """Parse gn_args as json and return config dictionary."""
52 configs = json.loads(gn_args)
53 build_configs = {}
Takuto Ikutac8069af2019-01-09 06:24:56 +000054
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000055 for config in configs:
56 key = config["name"]
57 if key not in WHITELISTED_CONFIGS:
58 continue
59 if 'current' in config:
60 build_configs[key] = config['current']['value']
61 else:
62 build_configs[key] = config['default']['value']
Takuto Ikutac8069af2019-01-09 06:24:56 +000063
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000064 return build_configs
65
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000066
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000067def GetBuildTargetFromCommandLine(cmdline):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000068 """Get build targets from commandline."""
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000069
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000070 # Skip argv0.
71 idx = 1
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000072
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000073 # Skipping all args that involve these flags, and taking all remaining args
74 # as targets.
75 onearg_flags = ('-C', '-f', '-j', '-k', '-l', '-d', '-t', '-w')
76 zeroarg_flags = ('--version', '-n', '-v')
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000077
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000078 targets = []
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000079
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000080 while idx < len(cmdline):
81 if cmdline[idx] in onearg_flags:
82 idx += 2
83 continue
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000084
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000085 if (cmdline[idx][:2] in onearg_flags or cmdline[idx] in zeroarg_flags):
86 idx += 1
87 continue
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000088
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000089 targets.append(cmdline[idx])
90 idx += 1
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000091
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000092 return targets
93
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000094
Takuto Ikutac8069af2019-01-09 06:24:56 +000095def GetJflag(cmdline):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000096 """Parse cmdline to get flag value for -j"""
Takuto Ikutac8069af2019-01-09 06:24:56 +000097
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000098 for i in range(len(cmdline)):
99 if (cmdline[i] == '-j' and i + 1 < len(cmdline)
100 and cmdline[i + 1].isdigit()):
101 return int(cmdline[i + 1])
Takuto Ikutac8069af2019-01-09 06:24:56 +0000102
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000103 if (cmdline[i].startswith('-j') and cmdline[i][len('-j'):].isdigit()):
104 return int(cmdline[i][len('-j'):])
Takuto Ikutac8069af2019-01-09 06:24:56 +0000105
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000106
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000107def GetMetadata(cmdline, ninjalog):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000108 """Get metadata for uploaded ninjalog.
Takuto Ikutac8069af2019-01-09 06:24:56 +0000109
110 Returned metadata has schema defined in
111 https://cs.chromium.org?q="type+Metadata+struct+%7B"+file:%5Einfra/go/src/infra/appengine/chromium_build_stats/ninjalog/
112
113 TODO(tikuta): Collect GOMA_* env var.
114 """
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000115
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000116 build_dir = os.path.dirname(ninjalog)
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000117
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000118 build_configs = {}
119
120 try:
121 args = ['gn', 'args', build_dir, '--list', '--short', '--json']
122 if sys.platform == 'win32':
123 # gn in PATH is bat file in windows environment (except cygwin).
124 args = ['cmd', '/c'] + args
125
126 gn_args = subprocess.check_output(args)
127 build_configs = ParseGNArgs(gn_args)
128 except subprocess.CalledProcessError as e:
129 logging.error("Failed to call gn %s", e)
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000130 build_configs = {}
131
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000132 # Stringify config.
133 for k in build_configs:
134 build_configs[k] = str(build_configs[k])
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000135
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000136 metadata = {
137 'platform': platform.system(),
138 'cpu_core': multiprocessing.cpu_count(),
139 'build_configs': build_configs,
140 'targets': GetBuildTargetFromCommandLine(cmdline),
141 }
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000142
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000143 jflag = GetJflag(cmdline)
144 if jflag is not None:
145 metadata['jobs'] = jflag
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000146
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000147 return metadata
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000148
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000149
150def GetNinjalog(cmdline):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000151 """GetNinjalog returns the path to ninjalog from cmdline."""
152 # ninjalog is in current working directory by default.
153 ninjalog_dir = '.'
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000154
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000155 i = 0
156 while i < len(cmdline):
157 cmd = cmdline[i]
158 i += 1
159 if cmd == '-C' and i < len(cmdline):
160 ninjalog_dir = cmdline[i]
161 i += 1
162 continue
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000163
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000164 if cmd.startswith('-C') and len(cmd) > len('-C'):
165 ninjalog_dir = cmd[len('-C'):]
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000166
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000167 return os.path.join(ninjalog_dir, '.ninja_log')
168
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000169
170def main():
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000171 parser = argparse.ArgumentParser()
172 parser.add_argument('--server',
173 default='chromium-build-stats.appspot.com',
174 help='server to upload ninjalog file.')
175 parser.add_argument('--ninjalog', help='ninjalog file to upload.')
176 parser.add_argument('--verbose',
177 action='store_true',
178 help='Enable verbose logging.')
179 parser.add_argument('--cmdline',
180 required=True,
181 nargs=argparse.REMAINDER,
182 help='command line args passed to ninja.')
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000183
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000184 args = parser.parse_args()
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000185
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000186 if args.verbose:
187 logging.basicConfig(level=logging.INFO)
188 else:
189 # Disable logging.
190 logging.disable(logging.CRITICAL)
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000191
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000192 if not IsGoogler(args.server):
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000193 return 0
194
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000195 ninjalog = args.ninjalog or GetNinjalog(args.cmdline)
196 if not os.path.isfile(ninjalog):
Gavin Make6a62332020-12-04 21:57:10 +0000197 logging.warning("ninjalog is not found in %s", ninjalog)
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000198 return 1
199
200 # We assume that each ninja invocation interval takes at least 2 seconds.
201 # This is not to have duplicate entry in server when current build is no-op.
202 if os.stat(ninjalog).st_mtime < time.time() - 2:
203 logging.info("ninjalog is not updated recently %s", ninjalog)
204 return 0
205
Takuto Ikuta84e43fa2021-01-19 02:51:50 +0000206 output = io.BytesIO()
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000207
208 with open(ninjalog) as f:
209 with gzip.GzipFile(fileobj=output, mode='wb') as g:
Takuto Ikuta84e43fa2021-01-19 02:51:50 +0000210 g.write(f.read().encode())
211 g.write(b'# end of ninja log\n')
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000212
213 metadata = GetMetadata(args.cmdline, ninjalog)
214 logging.info('send metadata: %s', json.dumps(metadata))
Takuto Ikuta84e43fa2021-01-19 02:51:50 +0000215 g.write(json.dumps(metadata).encode())
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000216
Takuto Ikuta84e43fa2021-01-19 02:51:50 +0000217 resp = request.urlopen(
218 request.Request('https://' + args.server + '/upload_ninja_log/',
219 data=output.getvalue(),
220 headers={'Content-Encoding': 'gzip'}))
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000221
Takuto Ikuta84e43fa2021-01-19 02:51:50 +0000222 if resp.status != 200:
223 logging.warning("unexpected status code for response: %s", resp.status)
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000224 return 1
225
Takuto Ikuta84e43fa2021-01-19 02:51:50 +0000226 logging.info('response header: %s', resp.headers)
227 logging.info('response content: %s', resp.read())
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000228 return 0
229
230
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000231if __name__ == '__main__':
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000232 sys.exit(main())