blob: b602156f775fc756ce35f1a6c22d75587708a439 [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):
42 """Parse gn_args as json and return config dictionary.
43
44 >>> ParseGNArgs("[]")
45 {}
46 >>> ParseGNArgs('[{\
47 "current": {"value": "true"}, \
48 "default": {"value": "false"}, \
49 "name": "is_component_build"}]')
50 {u'is_component_build': u'true'}
51 """
52 configs = json.loads(gn_args)
53 build_configs = {}
54 for config in configs:
55 build_configs[config["name"]] = config["current"]["value"]
56 return build_configs
57
58
Takuto Ikuta9af233a2018-11-29 03:53:53 +000059def GetMetadata(cmdline, ninjalog):
60 """Get metadata for uploaded ninjalog."""
61
Takuto Ikuta9af233a2018-11-29 03:53:53 +000062 build_dir = os.path.dirname(ninjalog)
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000063
64 build_configs = {}
65
66 try:
67 args = ['gn', 'args', build_dir, '--list', '--overrides-only',
68 '--short', '--json']
69 if sys.platform == 'win32':
70 # gn in PATH is bat file in windows environment (except cygwin).
71 args = ['cmd', '/c'] + args
72
73 gn_args = subprocess.check_output(args)
74 build_configs = ParseGNArgs(gn_args)
75 except subprocess.CalledProcessError as e:
76 logging.error("Failed to call gn %s", e)
77 build_configs = {}
78
79 # Stringify config.
80 for k in build_configs:
81 build_configs[k] = str(build_configs[k])
82
Takuto Ikuta9af233a2018-11-29 03:53:53 +000083 metadata = {
84 'platform': platform.system(),
85 'cwd': build_dir,
86 'hostname': socket.gethostname(),
87 'cpu_core': multiprocessing.cpu_count(),
88 'cmdline': cmdline,
Takuto Ikuta96fdf7c2018-12-03 09:18:39 +000089 'build_configs': build_configs,
Takuto Ikuta9af233a2018-11-29 03:53:53 +000090 }
91
92 return metadata
93
94def GetNinjalog(cmdline):
95 """GetNinjalog returns the path to ninjalog from cmdline.
96
97 >>> GetNinjalog(['ninja'])
98 './.ninja_log'
99 >>> GetNinjalog(['ninja', '-C', 'out/Release'])
100 'out/Release/.ninja_log'
101 >>> GetNinjalog(['ninja', '-Cout/Release'])
102 'out/Release/.ninja_log'
103 >>> GetNinjalog(['ninja', '-C'])
104 './.ninja_log'
105 >>> GetNinjalog(['ninja', '-C', 'out/Release', '-C', 'out/Debug'])
106 'out/Debug/.ninja_log'
107 """
108 # ninjalog is in current working directory by default.
109 ninjalog_dir = '.'
110
111 i = 0
112 while i < len(cmdline):
113 cmd = cmdline[i]
114 i += 1
115 if cmd == '-C' and i < len(cmdline):
116 ninjalog_dir = cmdline[i]
117 i += 1
118 continue
119
120 if cmd.startswith('-C') and len(cmd) > len('-C'):
121 ninjalog_dir = cmd[len('-C'):]
122
123 return os.path.join(ninjalog_dir, '.ninja_log')
124
125def main():
126 parser = argparse.ArgumentParser()
127 parser.add_argument('--server',
128 default='chromium-build-stats.appspot.com',
129 help='server to upload ninjalog file.')
130 parser.add_argument('--ninjalog', help='ninjalog file to upload.')
131 parser.add_argument('--verbose', action='store_true',
132 help='Enable verbose logging.')
133 parser.add_argument('--cmdline', required=True, nargs=argparse.REMAINDER,
134 help='command line args passed to ninja.')
135
136 args = parser.parse_args()
137
138 if args.verbose:
139 logging.basicConfig(level=logging.INFO)
140 else:
141 # Disable logging.
142 logging.disable(logging.CRITICAL)
143
144 if not IsGoogler(args.server):
145 return 0
146
147
148 ninjalog = args.ninjalog or GetNinjalog(args.cmdline)
149 if not os.path.isfile(ninjalog):
150 logging.warn("ninjalog is not found in %s", ninjalog)
151 return 1
152
153 output = cStringIO.StringIO()
154
155 with open(ninjalog) as f:
156 with gzip.GzipFile(fileobj=output, mode='wb') as g:
157 g.write(f.read())
158 g.write('# end of ninja log\n')
159
160 metadata = GetMetadata(args.cmdline, ninjalog)
161 logging.info('send metadata: %s', metadata)
162 g.write(json.dumps(metadata))
163
164 h = httplib2.Http()
165 resp_headers, content = h.request(
166 'https://'+args.server+'/upload_ninja_log/', 'POST',
167 body=output.getvalue(), headers={'Content-Encoding': 'gzip'})
168
169 if resp_headers.status != 200:
170 logging.warn("unexpected status code for response: %s",
171 resp_headers.status)
172 return 1
173
174 logging.info('response header: %s', resp_headers)
175 logging.info('response content: %s', content)
176 return 0
177
178if __name__ == '__main__':
179 sys.exit(main())