blob: d57afed855bcdc3abf1c26c1c55c7dc4ba487c4d [file] [log] [blame]
Takuto Ikuta9af233a2018-11-29 03:53:531#!/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:3927import subprocess
Takuto Ikuta9af233a2018-11-29 03:53:5328import sys
Takuto Ikuta36248fc2019-01-11 03:02:3229import time
Takuto Ikuta9af233a2018-11-29 03:53:5330
31from third_party import httplib2
32
Takuto Ikutac8069af2019-01-09 06:24:5633# 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:5341def 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:3950def ParseGNArgs(gn_args):
Takuto Ikuta61cb9d62018-12-17 23:45:4951 """Parse gn_args as json and return config dictionary."""
Takuto Ikuta96fdf7c2018-12-03 09:18:3952 configs = json.loads(gn_args)
53 build_configs = {}
Takuto Ikutac8069af2019-01-09 06:24:5654
Takuto Ikuta96fdf7c2018-12-03 09:18:3955 for config in configs:
Takuto Ikutac8069af2019-01-09 06:24:5656 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:3964 return build_configs
65
Takuto Ikutacf56a4b2018-12-18 05:47:2666def 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:5694def 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:39106
Takuto Ikuta9af233a2018-11-29 03:53:53107def GetMetadata(cmdline, ninjalog):
Takuto Ikutac8069af2019-01-09 06:24:56108 """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:53115
Takuto Ikuta9af233a2018-11-29 03:53:53116 build_dir = os.path.dirname(ninjalog)
Takuto Ikuta96fdf7c2018-12-03 09:18:39117
118 build_configs = {}
119
120 try:
Takuto Ikutac8069af2019-01-09 06:24:56121 args = ['gn', 'args', build_dir, '--list', '--short', '--json']
Takuto Ikuta96fdf7c2018-12-03 09:18:39122 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:53136 metadata = {
137 'platform': platform.system(),
Takuto Ikuta9af233a2018-11-29 03:53:53138 'cpu_core': multiprocessing.cpu_count(),
Takuto Ikuta96fdf7c2018-12-03 09:18:39139 'build_configs': build_configs,
Takuto Ikutac8069af2019-01-09 06:24:56140 'targets': GetBuildTargetFromCommandLine(cmdline),
Takuto Ikuta9af233a2018-11-29 03:53:53141 }
142
Takuto Ikutac8069af2019-01-09 06:24:56143 jflag = GetJflag(cmdline)
144 if jflag is not None:
145 metadata['jobs'] = jflag
146
Takuto Ikuta9af233a2018-11-29 03:53:53147 return metadata
148
149def GetNinjalog(cmdline):
Takuto Ikuta61cb9d62018-12-17 23:45:49150 """GetNinjalog returns the path to ninjalog from cmdline."""
Takuto Ikuta9af233a2018-11-29 03:53:53151 # 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:32196 # 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:53202 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:56210 logging.info('send metadata: %s', json.dumps(metadata))
Takuto Ikuta9af233a2018-11-29 03:53:53211 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())