blob: d57afed855bcdc3abf1c26c1c55c7dc4ba487c4d [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.
5
6"""
7This is script to upload ninja_log from googler.
8
9Server side implementation is in
10https://cs.chromium.org/chromium/infra/go/src/infra/appengine/chromium_build_stats/
11
12Uploaded ninjalog is stored in BigQuery table having following schema.
13https://cs.chromium.org/chromium/infra/go/src/infra/appengine/chromium_build_stats/ninjaproto/ninjalog.proto
14
15The log will be used to analyze user side build performance.
16"""
17
18import argparse
19import cStringIO
20import gzip
21import json
22import logging
23import multiprocessing
24import os
25import platform
26import socket
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000027import subprocess
Takuto Ikuta9af233a2018-11-29 03:53:53 +000028import sys
Takuto Ikuta36248fc2019-01-11 03:02:32 +000029import time
Takuto Ikuta9af233a2018-11-29 03:53:53 +000030
31from third_party import httplib2
32
Takuto Ikutac8069af2019-01-09 06:24:56 +000033# These build configs affect build performance a lot.
34# TODO(tikuta): Add 'blink_symbol_level', 'closure_compile' and
35# 'use_jumbo_build'.
36WHITELISTED_CONFIGS = (
37 'symbol_level', 'use_goma', 'is_debug', 'is_component_build', 'enable_nacl',
38 'host_os', 'host_cpu', 'target_os', 'target_cpu'
39)
40
Takuto Ikuta9af233a2018-11-29 03:53:53 +000041def IsGoogler(server):
42 """Check whether this script run inside corp network."""
43 try:
44 h = httplib2.Http()
45 _, content = h.request('https://'+server+'/should-upload', 'GET')
46 return content == 'Success'
47 except httplib2.HttpLib2Error:
48 return False
49
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000050def ParseGNArgs(gn_args):
Takuto Ikuta61cb9d62018-12-17 23:45:49 +000051 """Parse gn_args as json and return config dictionary."""
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000052 configs = json.loads(gn_args)
53 build_configs = {}
Takuto Ikutac8069af2019-01-09 06:24:56 +000054
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000055 for config in configs:
Takuto Ikutac8069af2019-01-09 06:24:56 +000056 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']
63
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000064 return build_configs
65
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000066def GetBuildTargetFromCommandLine(cmdline):
67 """Get build targets from commandline."""
68
69 # Skip argv0.
70 idx = 1
71
72 # 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')
76
77 targets = []
78
79 while idx < len(cmdline):
80 if cmdline[idx] in onearg_flags:
81 idx += 2
82 continue
83
84 if (cmdline[idx][:2] in onearg_flags or
85 cmdline[idx] in zeroarg_flags):
86 idx += 1
87 continue
88
89 targets.append(cmdline[idx])
90 idx += 1
91
92 return targets
93
Takuto Ikutac8069af2019-01-09 06:24:56 +000094def GetJflag(cmdline):
95 """Parse cmdline to get flag value for -j"""
96
97 for i in range(len(cmdline)):
98 if (cmdline[i] == '-j' and i + 1 < len(cmdline) and
99 cmdline[i+1].isdigit()):
100 return int(cmdline[i+1])
101
102 if (cmdline[i].startswith('-j') and
103 cmdline[i][len('-j'):].isdigit()):
104 return int(cmdline[i][len('-j'):])
105
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000106
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000107def GetMetadata(cmdline, ninjalog):
Takuto Ikutac8069af2019-01-09 06:24:56 +0000108 """Get metadata for uploaded ninjalog.
109
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 Ikuta9af233a2018-11-29 03:53:53 +0000116 build_dir = os.path.dirname(ninjalog)
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000117
118 build_configs = {}
119
120 try:
Takuto Ikutac8069af2019-01-09 06:24:56 +0000121 args = ['gn', 'args', build_dir, '--list', '--short', '--json']
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000122 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)
130 build_configs = {}
131
132 # Stringify config.
133 for k in build_configs:
134 build_configs[k] = str(build_configs[k])
135
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000136 metadata = {
137 'platform': platform.system(),
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000138 'cpu_core': multiprocessing.cpu_count(),
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000139 'build_configs': build_configs,
Takuto Ikutac8069af2019-01-09 06:24:56 +0000140 'targets': GetBuildTargetFromCommandLine(cmdline),
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000141 }
142
Takuto Ikutac8069af2019-01-09 06:24:56 +0000143 jflag = GetJflag(cmdline)
144 if jflag is not None:
145 metadata['jobs'] = jflag
146
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000147 return metadata
148
149def GetNinjalog(cmdline):
Takuto Ikuta61cb9d62018-12-17 23:45:49 +0000150 """GetNinjalog returns the path to ninjalog from cmdline."""
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000151 # ninjalog is in current working directory by default.
152 ninjalog_dir = '.'
153
154 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
162
163 if cmd.startswith('-C') and len(cmd) > len('-C'):
164 ninjalog_dir = cmd[len('-C'):]
165
166 return os.path.join(ninjalog_dir, '.ninja_log')
167
168def main():
169 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', action='store_true',
175 help='Enable verbose logging.')
176 parser.add_argument('--cmdline', required=True, nargs=argparse.REMAINDER,
177 help='command line args passed to ninja.')
178
179 args = parser.parse_args()
180
181 if args.verbose:
182 logging.basicConfig(level=logging.INFO)
183 else:
184 # Disable logging.
185 logging.disable(logging.CRITICAL)
186
187 if not IsGoogler(args.server):
188 return 0
189
190
191 ninjalog = args.ninjalog or GetNinjalog(args.cmdline)
192 if not os.path.isfile(ninjalog):
193 logging.warn("ninjalog is not found in %s", ninjalog)
194 return 1
195
Takuto Ikuta36248fc2019-01-11 03:02:32 +0000196 # We assume that each ninja invocation interval takes at least 2 seconds.
197 # This is not to have duplicate entry in server when current build is no-op.
198 if os.stat(ninjalog).st_mtime < time.time() - 2:
199 logging.info("ninjalog is not updated recently %s", ninjalog)
200 return 0
201
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000202 output = cStringIO.StringIO()
203
204 with open(ninjalog) as f:
205 with gzip.GzipFile(fileobj=output, mode='wb') as g:
206 g.write(f.read())
207 g.write('# end of ninja log\n')
208
209 metadata = GetMetadata(args.cmdline, ninjalog)
Takuto Ikutac8069af2019-01-09 06:24:56 +0000210 logging.info('send metadata: %s', json.dumps(metadata))
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000211 g.write(json.dumps(metadata))
212
213 h = httplib2.Http()
214 resp_headers, content = h.request(
215 'https://'+args.server+'/upload_ninja_log/', 'POST',
216 body=output.getvalue(), headers={'Content-Encoding': 'gzip'})
217
218 if resp_headers.status != 200:
219 logging.warn("unexpected status code for response: %s",
220 resp_headers.status)
221 return 1
222
223 logging.info('response header: %s', resp_headers)
224 logging.info('response content: %s', content)
225 return 0
226
227if __name__ == '__main__':
228 sys.exit(main())