blob: 255d6eb95c082adcbe845d579155eab1a78a1ae3 [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
32def IsGoogler(server):
33 """Check whether this script run inside corp network."""
34 try:
35 h = httplib2.Http()
36 _, content = h.request('https://'+server+'/should-upload', 'GET')
37 return content == 'Success'
38 except httplib2.HttpLib2Error:
39 return False
40
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000041def ParseGNArgs(gn_args):
Takuto Ikuta61cb9d62018-12-17 23:45:49 +000042 """Parse gn_args as json and return config dictionary."""
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000043 configs = json.loads(gn_args)
44 build_configs = {}
45 for config in configs:
46 build_configs[config["name"]] = config["current"]["value"]
47 return build_configs
48
Takuto Ikutacf56a4b2018-12-18 05:47:26 +000049def GetBuildTargetFromCommandLine(cmdline):
50 """Get build targets from commandline."""
51
52 # Skip argv0.
53 idx = 1
54
55 # Skipping all args that involve these flags, and taking all remaining args
56 # as targets.
57 onearg_flags = ('-C', '-f', '-j', '-k', '-l', '-d', '-t', '-w')
58 zeroarg_flags = ('--version', '-n', '-v')
59
60 targets = []
61
62 while idx < len(cmdline):
63 if cmdline[idx] in onearg_flags:
64 idx += 2
65 continue
66
67 if (cmdline[idx][:2] in onearg_flags or
68 cmdline[idx] in zeroarg_flags):
69 idx += 1
70 continue
71
72 targets.append(cmdline[idx])
73 idx += 1
74
75 return targets
76
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000077
Takuto Ikuta9af233a2018-11-29 03:53:53 +000078def GetMetadata(cmdline, ninjalog):
79 """Get metadata for uploaded ninjalog."""
80
Takuto Ikuta9af233a2018-11-29 03:53:53 +000081 build_dir = os.path.dirname(ninjalog)
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000082
83 build_configs = {}
84
85 try:
86 args = ['gn', 'args', build_dir, '--list', '--overrides-only',
87 '--short', '--json']
88 if sys.platform == 'win32':
89 # gn in PATH is bat file in windows environment (except cygwin).
90 args = ['cmd', '/c'] + args
91
92 gn_args = subprocess.check_output(args)
93 build_configs = ParseGNArgs(gn_args)
94 except subprocess.CalledProcessError as e:
95 logging.error("Failed to call gn %s", e)
96 build_configs = {}
97
98 # Stringify config.
99 for k in build_configs:
100 build_configs[k] = str(build_configs[k])
101
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000102 metadata = {
103 'platform': platform.system(),
104 'cwd': build_dir,
105 'hostname': socket.gethostname(),
106 'cpu_core': multiprocessing.cpu_count(),
107 'cmdline': cmdline,
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +0000108 'build_configs': build_configs,
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000109 }
110
111 return metadata
112
113def GetNinjalog(cmdline):
Takuto Ikuta61cb9d62018-12-17 23:45:49 +0000114 """GetNinjalog returns the path to ninjalog from cmdline."""
Takuto Ikuta9af233a2018-11-29 03:53:53 +0000115 # ninjalog is in current working directory by default.
116 ninjalog_dir = '.'
117
118 i = 0
119 while i < len(cmdline):
120 cmd = cmdline[i]
121 i += 1
122 if cmd == '-C' and i < len(cmdline):
123 ninjalog_dir = cmdline[i]
124 i += 1
125 continue
126
127 if cmd.startswith('-C') and len(cmd) > len('-C'):
128 ninjalog_dir = cmd[len('-C'):]
129
130 return os.path.join(ninjalog_dir, '.ninja_log')
131
132def main():
133 parser = argparse.ArgumentParser()
134 parser.add_argument('--server',
135 default='chromium-build-stats.appspot.com',
136 help='server to upload ninjalog file.')
137 parser.add_argument('--ninjalog', help='ninjalog file to upload.')
138 parser.add_argument('--verbose', action='store_true',
139 help='Enable verbose logging.')
140 parser.add_argument('--cmdline', required=True, nargs=argparse.REMAINDER,
141 help='command line args passed to ninja.')
142
143 args = parser.parse_args()
144
145 if args.verbose:
146 logging.basicConfig(level=logging.INFO)
147 else:
148 # Disable logging.
149 logging.disable(logging.CRITICAL)
150
151 if not IsGoogler(args.server):
152 return 0
153
154
155 ninjalog = args.ninjalog or GetNinjalog(args.cmdline)
156 if not os.path.isfile(ninjalog):
157 logging.warn("ninjalog is not found in %s", ninjalog)
158 return 1
159
160 output = cStringIO.StringIO()
161
162 with open(ninjalog) as f:
163 with gzip.GzipFile(fileobj=output, mode='wb') as g:
164 g.write(f.read())
165 g.write('# end of ninja log\n')
166
167 metadata = GetMetadata(args.cmdline, ninjalog)
168 logging.info('send metadata: %s', metadata)
169 g.write(json.dumps(metadata))
170
171 h = httplib2.Http()
172 resp_headers, content = h.request(
173 'https://'+args.server+'/upload_ninja_log/', 'POST',
174 body=output.getvalue(), headers={'Content-Encoding': 'gzip'})
175
176 if resp_headers.status != 200:
177 logging.warn("unexpected status code for response: %s",
178 resp_headers.status)
179 return 1
180
181 logging.info('response header: %s', resp_headers)
182 logging.info('response content: %s', content)
183 return 0
184
185if __name__ == '__main__':
186 sys.exit(main())