123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- #!/usr/bin/env python3
- # Copyright 2018 The Chromium Authors. All rights reserved.
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- """
- This is script to upload ninja_log from googler.
- Server side implementation is in
- https://cs.chromium.org/chromium/infra/go/src/infra/appengine/chromium_build_stats/
- Uploaded ninjalog is stored in BigQuery table having following schema.
- https://cs.chromium.org/chromium/infra/go/src/infra/appengine/chromium_build_stats/ninjaproto/ninjalog.proto
- The log will be used to analyze user side build performance.
- """
- import argparse
- import gzip
- import io
- import json
- import logging
- import multiprocessing
- import os
- import platform
- import subprocess
- import sys
- import time
- from third_party.six.moves.urllib import request
- # These build configs affect build performance.
- ALLOWLISTED_CONFIGS = ('symbol_level', 'use_goma', 'is_debug',
- 'is_component_build', 'enable_nacl', 'host_os',
- 'host_cpu', 'target_os', 'target_cpu',
- 'blink_symbol_level', 'is_java_debug',
- 'treat_warnings_as_errors', 'disable_android_lint',
- 'use_errorprone_java_compiler', 'incremental_install')
- def IsGoogler():
- """Check whether this user is Googler or not."""
- p = subprocess.run('goma_auth info',
- capture_output=True,
- text=True,
- shell=True)
- if p.returncode != 0:
- return False
- l = p.stdout.splitlines()[0]
- # |l| will be like 'Login as <user>@google.com' for googler using goma.
- return l.startswith('Login as ') and l.endswith('@google.com')
- def ParseGNArgs(gn_args):
- """Parse gn_args as json and return config dictionary."""
- configs = json.loads(gn_args)
- build_configs = {}
- for config in configs:
- key = config["name"]
- if key not in ALLOWLISTED_CONFIGS:
- continue
- if 'current' in config:
- build_configs[key] = config['current']['value']
- else:
- build_configs[key] = config['default']['value']
- return build_configs
- def GetBuildTargetFromCommandLine(cmdline):
- """Get build targets from commandline."""
- # Skip argv0.
- idx = 1
- # Skipping all args that involve these flags, and taking all remaining args
- # as targets.
- onearg_flags = ('-C', '-f', '-j', '-k', '-l', '-d', '-t', '-w')
- zeroarg_flags = ('--version', '-n', '-v')
- targets = []
- while idx < len(cmdline):
- if cmdline[idx] in onearg_flags:
- idx += 2
- continue
- if (cmdline[idx][:2] in onearg_flags or cmdline[idx] in zeroarg_flags):
- idx += 1
- continue
- targets.append(cmdline[idx])
- idx += 1
- return targets
- def GetJflag(cmdline):
- """Parse cmdline to get flag value for -j"""
- for i in range(len(cmdline)):
- if (cmdline[i] == '-j' and i + 1 < len(cmdline)
- and cmdline[i + 1].isdigit()):
- return int(cmdline[i + 1])
- if (cmdline[i].startswith('-j') and cmdline[i][len('-j'):].isdigit()):
- return int(cmdline[i][len('-j'):])
- def GetMetadata(cmdline, ninjalog):
- """Get metadata for uploaded ninjalog.
- Returned metadata has schema defined in
- https://cs.chromium.org?q="type+Metadata+struct+%7B"+file:%5Einfra/go/src/infra/appengine/chromium_build_stats/ninjalog/
- TODO(tikuta): Collect GOMA_* env var.
- """
- build_dir = os.path.dirname(ninjalog)
- build_configs = {}
- try:
- args = ['gn', 'args', build_dir, '--list', '--short', '--json']
- if sys.platform == 'win32':
- # gn in PATH is bat file in windows environment (except cygwin).
- args = ['cmd', '/c'] + args
- gn_args = subprocess.check_output(args)
- build_configs = ParseGNArgs(gn_args)
- except subprocess.CalledProcessError as e:
- logging.error("Failed to call gn %s", e)
- build_configs = {}
- # Stringify config.
- for k in build_configs:
- build_configs[k] = str(build_configs[k])
- metadata = {
- 'platform': platform.system(),
- 'cpu_core': multiprocessing.cpu_count(),
- 'build_configs': build_configs,
- 'targets': GetBuildTargetFromCommandLine(cmdline),
- }
- jflag = GetJflag(cmdline)
- if jflag is not None:
- metadata['jobs'] = jflag
- return metadata
- def GetNinjalog(cmdline):
- """GetNinjalog returns the path to ninjalog from cmdline."""
- # ninjalog is in current working directory by default.
- ninjalog_dir = '.'
- i = 0
- while i < len(cmdline):
- cmd = cmdline[i]
- i += 1
- if cmd == '-C' and i < len(cmdline):
- ninjalog_dir = cmdline[i]
- i += 1
- continue
- if cmd.startswith('-C') and len(cmd) > len('-C'):
- ninjalog_dir = cmd[len('-C'):]
- return os.path.join(ninjalog_dir, '.ninja_log')
- def main():
- parser = argparse.ArgumentParser()
- parser.add_argument('--server',
- default='chromium-build-stats.appspot.com',
- help='server to upload ninjalog file.')
- parser.add_argument('--ninjalog', help='ninjalog file to upload.')
- parser.add_argument('--verbose',
- action='store_true',
- help='Enable verbose logging.')
- parser.add_argument('--cmdline',
- required=True,
- nargs=argparse.REMAINDER,
- help='command line args passed to ninja.')
- args = parser.parse_args()
- if args.verbose:
- logging.basicConfig(level=logging.INFO)
- else:
- # Disable logging.
- logging.disable(logging.CRITICAL)
- if not IsGoogler():
- return 0
- ninjalog = args.ninjalog or GetNinjalog(args.cmdline)
- if not os.path.isfile(ninjalog):
- logging.warning("ninjalog is not found in %s", ninjalog)
- return 1
- # We assume that each ninja invocation interval takes at least 2 seconds.
- # This is not to have duplicate entry in server when current build is no-op.
- if os.stat(ninjalog).st_mtime < time.time() - 2:
- logging.info("ninjalog is not updated recently %s", ninjalog)
- return 0
- output = io.BytesIO()
- with open(ninjalog) as f:
- with gzip.GzipFile(fileobj=output, mode='wb') as g:
- g.write(f.read().encode())
- g.write(b'# end of ninja log\n')
- metadata = GetMetadata(args.cmdline, ninjalog)
- logging.info('send metadata: %s', json.dumps(metadata))
- g.write(json.dumps(metadata).encode())
- resp = request.urlopen(
- request.Request('https://' + args.server + '/upload_ninja_log/',
- data=output.getvalue(),
- headers={'Content-Encoding': 'gzip'}))
- if resp.status != 200:
- logging.warning("unexpected status code for response: %s", resp.status)
- return 1
- logging.info('response header: %s', resp.headers)
- logging.info('response content: %s', resp.read())
- return 0
- if __name__ == '__main__':
- sys.exit(main())
|