blob: 2db0155b91a9e8aae670dee05dc1b9c721fcdfa0 [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.
Takuto Ikuta6c7b8292020-08-03 17:47:51 +000032# TODO(tikuta): Add 'blink_symbol_level'and 'closure_compile'.
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000033WHITELISTED_CONFIGS = ('symbol_level', 'use_goma', 'is_debug',
34 'is_component_build', 'enable_nacl', 'host_os',
35 'host_cpu', 'target_os', 'target_cpu')
36
Takuto Ikutac8069af2019-01-09 06:24:56 +000037
Takuto Ikuta9af233a2018-11-29 03:53:53 +000038def IsGoogler(server):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000039 """Check whether this script run inside corp network."""
40 try:
41 h = httplib2.Http()
42 _, content = h.request('https://' + server + '/should-upload', 'GET')
43 return content == 'Success'
44 except httplib2.HttpLib2Error:
45 return False
46
Takuto Ikuta9af233a2018-11-29 03:53:53 +000047
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000048def ParseGNArgs(gn_args):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000049 """Parse gn_args as json and return config dictionary."""
50 configs = json.loads(gn_args)
51 build_configs = {}
Takuto Ikutac8069af2019-01-09 06:24:56 +000052
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000053 for config in configs:
54 key = config["name"]
55 if key not in WHITELISTED_CONFIGS:
56 continue
57 if 'current' in config:
58 build_configs[key] = config['current']['value']
59 else:
60 build_configs[key] = config['default']['value']
Takuto Ikutac8069af2019-01-09 06:24:56 +000061
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000062 return build_configs
63
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000064
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000065def GetBuildTargetFromCommandLine(cmdline):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000066 """Get build targets from commandline."""
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000067
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000068 # Skip argv0.
69 idx = 1
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000070
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000071 # Skipping all args that involve these flags, and taking all remaining args
72 # as targets.
73 onearg_flags = ('-C', '-f', '-j', '-k', '-l', '-d', '-t', '-w')
74 zeroarg_flags = ('--version', '-n', '-v')
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000075
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000076 targets = []
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000077
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000078 while idx < len(cmdline):
79 if cmdline[idx] in onearg_flags:
80 idx += 2
81 continue
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000082
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000083 if (cmdline[idx][:2] in onearg_flags or cmdline[idx] in zeroarg_flags):
84 idx += 1
85 continue
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000086
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000087 targets.append(cmdline[idx])
88 idx += 1
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000089
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000090 return targets
91
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000092
Takuto Ikutac8069af2019-01-09 06:24:56 +000093def GetJflag(cmdline):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000094 """Parse cmdline to get flag value for -j"""
Takuto Ikutac8069af2019-01-09 06:24:56 +000095
Takuto Ikutaa2e91db2020-06-09 11:21:59 +000096 for i in range(len(cmdline)):
97 if (cmdline[i] == '-j' and i + 1 < len(cmdline)
98 and cmdline[i + 1].isdigit()):
99 return int(cmdline[i + 1])
Takuto Ikutac8069af2019-01-09 06:24:56 +0000100
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000101 if (cmdline[i].startswith('-j') and cmdline[i][len('-j'):].isdigit()):
102 return int(cmdline[i][len('-j'):])
Takuto Ikutac8069af2019-01-09 06:24:56 +0000103
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000104
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000105def GetMetadata(cmdline, ninjalog):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000106 """Get metadata for uploaded ninjalog.
Takuto Ikutac8069af2019-01-09 06:24:56 +0000107
108 Returned metadata has schema defined in
109 https://cs.chromium.org?q="type+Metadata+struct+%7B"+file:%5Einfra/go/src/infra/appengine/chromium_build_stats/ninjalog/
110
111 TODO(tikuta): Collect GOMA_* env var.
112 """
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000113
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000114 build_dir = os.path.dirname(ninjalog)
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000115
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000116 build_configs = {}
117
118 try:
119 args = ['gn', 'args', build_dir, '--list', '--short', '--json']
120 if sys.platform == 'win32':
121 # gn in PATH is bat file in windows environment (except cygwin).
122 args = ['cmd', '/c'] + args
123
124 gn_args = subprocess.check_output(args)
125 build_configs = ParseGNArgs(gn_args)
126 except subprocess.CalledProcessError as e:
127 logging.error("Failed to call gn %s", e)
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000128 build_configs = {}
129
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000130 # Stringify config.
131 for k in build_configs:
132 build_configs[k] = str(build_configs[k])
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000133
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000134 metadata = {
135 'platform': platform.system(),
136 'cpu_core': multiprocessing.cpu_count(),
137 'build_configs': build_configs,
138 'targets': GetBuildTargetFromCommandLine(cmdline),
139 }
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000140
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000141 jflag = GetJflag(cmdline)
142 if jflag is not None:
143 metadata['jobs'] = jflag
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000144
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000145 return metadata
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000146
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000147
148def GetNinjalog(cmdline):
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000149 """GetNinjalog returns the path to ninjalog from cmdline."""
150 # ninjalog is in current working directory by default.
151 ninjalog_dir = '.'
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000152
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000153 i = 0
154 while i < len(cmdline):
155 cmd = cmdline[i]
156 i += 1
157 if cmd == '-C' and i < len(cmdline):
158 ninjalog_dir = cmdline[i]
159 i += 1
160 continue
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000161
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000162 if cmd.startswith('-C') and len(cmd) > len('-C'):
163 ninjalog_dir = cmd[len('-C'):]
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000164
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000165 return os.path.join(ninjalog_dir, '.ninja_log')
166
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000167
168def main():
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000169 parser = argparse.ArgumentParser()
170 parser.add_argument('--server',
171 default='chromium-build-stats.appspot.com',
172 help='server to upload ninjalog file.')
173 parser.add_argument('--ninjalog', help='ninjalog file to upload.')
174 parser.add_argument('--verbose',
175 action='store_true',
176 help='Enable verbose logging.')
177 parser.add_argument('--cmdline',
178 required=True,
179 nargs=argparse.REMAINDER,
180 help='command line args passed to ninja.')
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000181
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000182 args = parser.parse_args()
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000183
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000184 if args.verbose:
185 logging.basicConfig(level=logging.INFO)
186 else:
187 # Disable logging.
188 logging.disable(logging.CRITICAL)
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000189
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000190 if not IsGoogler(args.server):
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000191 return 0
192
Takuto Ikutaa2e91db2020-06-09 11:21:59 +0000193 ninjalog = args.ninjalog or GetNinjalog(args.cmdline)
194 if not os.path.isfile(ninjalog):
195 logging.warn("ninjalog is not found in %s", ninjalog)
196 return 1
197
198 # We assume that each ninja invocation interval takes at least 2 seconds.
199 # This is not to have duplicate entry in server when current build is no-op.
200 if os.stat(ninjalog).st_mtime < time.time() - 2:
201 logging.info("ninjalog is not updated recently %s", ninjalog)
202 return 0
203
204 output = cStringIO.StringIO()
205
206 with open(ninjalog) as f:
207 with gzip.GzipFile(fileobj=output, mode='wb') as g:
208 g.write(f.read())
209 g.write('# end of ninja log\n')
210
211 metadata = GetMetadata(args.cmdline, ninjalog)
212 logging.info('send metadata: %s', json.dumps(metadata))
213 g.write(json.dumps(metadata))
214
215 h = httplib2.Http()
216 resp_headers, content = h.request('https://' + args.server +
217 '/upload_ninja_log/',
218 'POST',
219 body=output.getvalue(),
220 headers={'Content-Encoding': 'gzip'})
221
222 if resp_headers.status != 200:
223 logging.warn("unexpected status code for response: %s", resp_headers.status)
224 return 1
225
226 logging.info('response header: %s', resp_headers)
227 logging.info('response content: %s', content)
228 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())