blob: 843f106a83dfa5218b969bece64977e5b0c8e575 [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
29
30from third_party import httplib2
31
Takuto Ikutac8069af2019-01-09 06:24:56 +000032# These build configs affect build performance a lot.
33# TODO(tikuta): Add 'blink_symbol_level', 'closure_compile' and
34# 'use_jumbo_build'.
35WHITELISTED_CONFIGS = (
36 'symbol_level', 'use_goma', 'is_debug', 'is_component_build', 'enable_nacl',
37 'host_os', 'host_cpu', 'target_os', 'target_cpu'
38)
39
Takuto Ikuta9af233a2018-11-29 03:53:53 +000040def IsGoogler(server):
41 """Check whether this script run inside corp network."""
42 try:
43 h = httplib2.Http()
44 _, content = h.request('https://'+server+'/should-upload', 'GET')
45 return content == 'Success'
46 except httplib2.HttpLib2Error:
47 return False
48
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000049def ParseGNArgs(gn_args):
Takuto Ikuta61cb9d62018-12-17 23:45:49 +000050 """Parse gn_args as json and return config dictionary."""
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000051 configs = json.loads(gn_args)
52 build_configs = {}
Takuto Ikutac8069af2019-01-09 06:24:56 +000053
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000054 for config in configs:
Takuto Ikutac8069af2019-01-09 06:24:56 +000055 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']
62
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000063 return build_configs
64
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000065def GetBuildTargetFromCommandLine(cmdline):
66 """Get build targets from commandline."""
67
68 # Skip argv0.
69 idx = 1
70
71 # 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')
75
76 targets = []
77
78 while idx < len(cmdline):
79 if cmdline[idx] in onearg_flags:
80 idx += 2
81 continue
82
83 if (cmdline[idx][:2] in onearg_flags or
84 cmdline[idx] in zeroarg_flags):
85 idx += 1
86 continue
87
88 targets.append(cmdline[idx])
89 idx += 1
90
91 return targets
92
Takuto Ikutac8069af2019-01-09 06:24:56 +000093def GetJflag(cmdline):
94 """Parse cmdline to get flag value for -j"""
95
96 for i in range(len(cmdline)):
97 if (cmdline[i] == '-j' and i + 1 < len(cmdline) and
98 cmdline[i+1].isdigit()):
99 return int(cmdline[i+1])
100
101 if (cmdline[i].startswith('-j') and
102 cmdline[i][len('-j'):].isdigit()):
103 return int(cmdline[i][len('-j'):])
104
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000105
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000106def GetMetadata(cmdline, ninjalog):
Takuto Ikutac8069af2019-01-09 06:24:56 +0000107 """Get metadata for uploaded ninjalog.
108
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 Ikuta9af233a2018-11-29 03:53:53 +0000115 build_dir = os.path.dirname(ninjalog)
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000116
117 build_configs = {}
118
119 try:
Takuto Ikutac8069af2019-01-09 06:24:56 +0000120 args = ['gn', 'args', build_dir, '--list', '--short', '--json']
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000121 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)
129 build_configs = {}
130
131 # Stringify config.
132 for k in build_configs:
133 build_configs[k] = str(build_configs[k])
134
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000135 metadata = {
136 'platform': platform.system(),
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000137 'cpu_core': multiprocessing.cpu_count(),
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000138 'build_configs': build_configs,
Takuto Ikutac8069af2019-01-09 06:24:56 +0000139 'targets': GetBuildTargetFromCommandLine(cmdline),
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000140 }
141
Takuto Ikutac8069af2019-01-09 06:24:56 +0000142 jflag = GetJflag(cmdline)
143 if jflag is not None:
144 metadata['jobs'] = jflag
145
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000146 return metadata
147
148def GetNinjalog(cmdline):
Takuto Ikuta61cb9d62018-12-17 23:45:49 +0000149 """GetNinjalog returns the path to ninjalog from cmdline."""
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000150 # ninjalog is in current working directory by default.
151 ninjalog_dir = '.'
152
153 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
161
162 if cmd.startswith('-C') and len(cmd) > len('-C'):
163 ninjalog_dir = cmd[len('-C'):]
164
165 return os.path.join(ninjalog_dir, '.ninja_log')
166
167def main():
168 parser = argparse.ArgumentParser()
169 parser.add_argument('--server',
170 default='chromium-build-stats.appspot.com',
171 help='server to upload ninjalog file.')
172 parser.add_argument('--ninjalog', help='ninjalog file to upload.')
173 parser.add_argument('--verbose', action='store_true',
174 help='Enable verbose logging.')
175 parser.add_argument('--cmdline', required=True, nargs=argparse.REMAINDER,
176 help='command line args passed to ninja.')
177
178 args = parser.parse_args()
179
180 if args.verbose:
181 logging.basicConfig(level=logging.INFO)
182 else:
183 # Disable logging.
184 logging.disable(logging.CRITICAL)
185
186 if not IsGoogler(args.server):
187 return 0
188
189
190 ninjalog = args.ninjalog or GetNinjalog(args.cmdline)
191 if not os.path.isfile(ninjalog):
192 logging.warn("ninjalog is not found in %s", ninjalog)
193 return 1
194
195 output = cStringIO.StringIO()
196
197 with open(ninjalog) as f:
198 with gzip.GzipFile(fileobj=output, mode='wb') as g:
199 g.write(f.read())
200 g.write('# end of ninja log\n')
201
202 metadata = GetMetadata(args.cmdline, ninjalog)
Takuto Ikutac8069af2019-01-09 06:24:56 +0000203 logging.info('send metadata: %s', json.dumps(metadata))
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000204 g.write(json.dumps(metadata))
205
206 h = httplib2.Http()
207 resp_headers, content = h.request(
208 'https://'+args.server+'/upload_ninja_log/', 'POST',
209 body=output.getvalue(), headers={'Content-Encoding': 'gzip'})
210
211 if resp_headers.status != 200:
212 logging.warn("unexpected status code for response: %s",
213 resp_headers.status)
214 return 1
215
216 logging.info('response header: %s', resp_headers)
217 logging.info('response content: %s', content)
218 return 0
219
220if __name__ == '__main__':
221 sys.exit(main())