blob: 42be072db8242f407ada14be987b73b03d6b736d [file] [log] [blame]
Takuto Ikuta9af233a2018-11-29 03:53:53 +00001#!/usr/bin/env python
2# 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
18import cStringIO
19import gzip
20import 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
Edward Lesmes19610432020-01-07 03:30:49 +000029import httplib2
Takuto Ikuta9af233a2018-11-29 03:53:53 +000030
Takuto Ikutac8069af2019-01-09 06:24:56 +000031# These build configs affect build performance a lot.
Bruce Dawson9e633032020-08-04 21:50:29 +000032# TODO(https://crbug.com/900161): Add 'blink_symbol_level' and
33# 'enable_js_type_check'.
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000034WHITELISTED_CONFIGS = ('symbol_level', 'use_goma', 'is_debug',
35 'is_component_build', 'enable_nacl', 'host_os',
36 'host_cpu', 'target_os', 'target_cpu')
37
Takuto Ikutac8069af2019-01-09 06:24:56 +000038
Takuto Ikuta9af233a2018-11-29 03:53:53 +000039def IsGoogler(server):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000040 """Check whether this script run inside corp network."""
41 try:
42 h = httplib2.Http()
43 _, content = h.request('https://' + server + '/should-upload', 'GET')
44 return content == 'Success'
45 except httplib2.HttpLib2Error:
46 return False
47
Takuto Ikuta9af233a2018-11-29 03:53:53 +000048
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000049def ParseGNArgs(gn_args):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000050 """Parse gn_args as json and return config dictionary."""
51 configs = json.loads(gn_args)
52 build_configs = {}
Takuto Ikutac8069af2019-01-09 06:24:56 +000053
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000054 for config in configs:
55 key = config["name"]
56 if key not in WHITELISTED_CONFIGS:
57 continue
58 if 'current' in config:
59 build_configs[key] = config['current']['value']
60 else:
61 build_configs[key] = config['default']['value']
Takuto Ikutac8069af2019-01-09 06:24:56 +000062
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000063 return build_configs
64
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000065
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000066def GetBuildTargetFromCommandLine(cmdline):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000067 """Get build targets from commandline."""
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000068
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000069 # Skip argv0.
70 idx = 1
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000071
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000072 # Skipping all args that involve these flags, and taking all remaining args
73 # as targets.
74 onearg_flags = ('-C', '-f', '-j', '-k', '-l', '-d', '-t', '-w')
75 zeroarg_flags = ('--version', '-n', '-v')
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000076
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000077 targets = []
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000078
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000079 while idx < len(cmdline):
80 if cmdline[idx] in onearg_flags:
81 idx += 2
82 continue
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000083
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000084 if (cmdline[idx][:2] in onearg_flags or cmdline[idx] in zeroarg_flags):
85 idx += 1
86 continue
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000087
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000088 targets.append(cmdline[idx])
89 idx += 1
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000090
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000091 return targets
92
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000093
Takuto Ikutac8069af2019-01-09 06:24:56 +000094def GetJflag(cmdline):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000095 """Parse cmdline to get flag value for -j"""
Takuto Ikutac8069af2019-01-09 06:24:56 +000096
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000097 for i in range(len(cmdline)):
98 if (cmdline[i] == '-j' and i + 1 < len(cmdline)
99 and cmdline[i + 1].isdigit()):
100 return int(cmdline[i + 1])
Takuto Ikutac8069af2019-01-09 06:24:56 +0000101
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000102 if (cmdline[i].startswith('-j') and cmdline[i][len('-j'):].isdigit()):
103 return int(cmdline[i][len('-j'):])
Takuto Ikutac8069af2019-01-09 06:24:56 +0000104
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000105
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000106def GetMetadata(cmdline, ninjalog):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000107 """Get metadata for uploaded ninjalog.
Takuto Ikutac8069af2019-01-09 06:24:56 +0000108
109 Returned metadata has schema defined in
110 https://cs.chromium.org?q="type+Metadata+struct+%7B"+file:%5Einfra/go/src/infra/appengine/chromium_build_stats/ninjalog/
111
112 TODO(tikuta): Collect GOMA_* env var.
113 """
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000114
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000115 build_dir = os.path.dirname(ninjalog)
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000116
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000117 build_configs = {}
118
119 try:
120 args = ['gn', 'args', build_dir, '--list', '--short', '--json']
121 if sys.platform == 'win32':
122 # gn in PATH is bat file in windows environment (except cygwin).
123 args = ['cmd', '/c'] + args
124
125 gn_args = subprocess.check_output(args)
126 build_configs = ParseGNArgs(gn_args)
127 except subprocess.CalledProcessError as e:
128 logging.error("Failed to call gn %s", e)
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000129 build_configs = {}
130
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000131 # Stringify config.
132 for k in build_configs:
133 build_configs[k] = str(build_configs[k])
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000134
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000135 metadata = {
136 'platform': platform.system(),
137 'cpu_core': multiprocessing.cpu_count(),
138 'build_configs': build_configs,
139 'targets': GetBuildTargetFromCommandLine(cmdline),
140 }
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000141
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000142 jflag = GetJflag(cmdline)
143 if jflag is not None:
144 metadata['jobs'] = jflag
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000145
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000146 return metadata
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000147
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000148
149def GetNinjalog(cmdline):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000150 """GetNinjalog returns the path to ninjalog from cmdline."""
151 # ninjalog is in current working directory by default.
152 ninjalog_dir = '.'
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000153
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000154 i = 0
155 while i < len(cmdline):
156 cmd = cmdline[i]
157 i += 1
158 if cmd == '-C' and i < len(cmdline):
159 ninjalog_dir = cmdline[i]
160 i += 1
161 continue
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000162
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000163 if cmd.startswith('-C') and len(cmd) > len('-C'):
164 ninjalog_dir = cmd[len('-C'):]
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000165
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000166 return os.path.join(ninjalog_dir, '.ninja_log')
167
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000168
169def main():
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000170 parser = argparse.ArgumentParser()
171 parser.add_argument('--server',
172 default='chromium-build-stats.appspot.com',
173 help='server to upload ninjalog file.')
174 parser.add_argument('--ninjalog', help='ninjalog file to upload.')
175 parser.add_argument('--verbose',
176 action='store_true',
177 help='Enable verbose logging.')
178 parser.add_argument('--cmdline',
179 required=True,
180 nargs=argparse.REMAINDER,
181 help='command line args passed to ninja.')
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000182
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000183 args = parser.parse_args()
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000184
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000185 if args.verbose:
186 logging.basicConfig(level=logging.INFO)
187 else:
188 # Disable logging.
189 logging.disable(logging.CRITICAL)
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000190
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000191 if not IsGoogler(args.server):
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000192 return 0
193
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000194 ninjalog = args.ninjalog or GetNinjalog(args.cmdline)
195 if not os.path.isfile(ninjalog):
Gavin Make6a62332020-12-04 21:57:10 +0000196 logging.warning("ninjalog is not found in %s", ninjalog)
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000197 return 1
198
199 # We assume that each ninja invocation interval takes at least 2 seconds.
200 # This is not to have duplicate entry in server when current build is no-op.
201 if os.stat(ninjalog).st_mtime < time.time() - 2:
202 logging.info("ninjalog is not updated recently %s", ninjalog)
203 return 0
204
205 output = cStringIO.StringIO()
206
207 with open(ninjalog) as f:
208 with gzip.GzipFile(fileobj=output, mode='wb') as g:
209 g.write(f.read())
210 g.write('# end of ninja log\n')
211
212 metadata = GetMetadata(args.cmdline, ninjalog)
213 logging.info('send metadata: %s', json.dumps(metadata))
214 g.write(json.dumps(metadata))
215
216 h = httplib2.Http()
217 resp_headers, content = h.request('https://' + args.server +
218 '/upload_ninja_log/',
219 'POST',
220 body=output.getvalue(),
221 headers={'Content-Encoding': 'gzip'})
222
223 if resp_headers.status != 200:
Gavin Make6a62332020-12-04 21:57:10 +0000224 logging.warning("unexpected status code for response: %s",
225 resp_headers.status)
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000226 return 1
227
228 logging.info('response header: %s', resp_headers)
229 logging.info('response content: %s', content)
230 return 0
231
232
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000233if __name__ == '__main__':
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000234 sys.exit(main())