blob: eb7f85473fadbc10cef382a8f185f89872814ba9 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Upload a single build command stats file to appengine."""
import optparse
import os
import re
import urllib
import urllib2
from chromite.lib import cros_build_lib as cros_lib
from chromite.lib import operation
MODULE = os.path.splitext(os.path.basename(__file__))[0]
oper = operation.Operation(MODULE)
# To test with an app engine instance on localhost, set envvar
# export CROS_BUILD_STATS_SITE="http://localhost:8080"
PAGE = 'upload_command_stats'
DEFAULT_SITE = 'http://chromiumos-build-stats.appspot.com'
SITE = os.environ.get('CROS_BUILD_STATS_SITE', DEFAULT_SITE)
URL = '%s/%s' % (SITE, PAGE)
UPLOAD_TIMEOUT = 5
DISABLE_FILE = os.path.join(os.environ['HOME'], '.disable_build_stats_upload')
DOMAIN_RE = re.compile(r'\.(?:corp\.google\.com|golo\.chromium\.org)$')
GIT_ID_RE = re.compile(r'(?:@google\.com|@chromium\.org)$')
class Stats(object):
"""Manage one set of build command stats."""
__slots__ = (
'data',
'loaders',
)
def __init__(self):
self.data = {}
self.loaders = [None,
Stats._LoadLinesV1, # Version 1 loader (at index 1)
]
def LoadFile(self, stat_file):
"""Load command stats file at |stat_file| into |self.data|."""
with open(stat_file, 'r') as f:
first_line = f.readline().rstrip()
match = re.match(r'Chromium OS .+ Version (\d+)$', first_line)
if not match:
oper.Die('Stats file not in expected format')
version = int(match.group(1))
loader = self._GetLinesLoader(version)
if loader:
loader(self, f.readlines())
else:
oper.Die('Stats file version %s not supported.' % version)
def _GetLinesLoader(self, version):
if version < len(self.loaders) and version >= 0:
return self.loaders[version]
return None
def _LoadLinesV1(self, stat_lines):
"""Load stat lines in Version 1 format."""
self.data = {}
for line in stat_lines:
# Each line has following format:
# attribute_name Rest of line is value for attribute_name
# Note that some attributes may have no value after their name.
attr, _sep, value = line.rstrip().partition(' ')
if not attr:
attr = line.rstrip()
self.data[attr] = value
def Upload(self, url):
"""Upload stats in |self.data| to |url|."""
oper.Notice('Uploading command stats to %r' % url)
data = urllib.urlencode(self.data)
request = urllib2.Request(url)
try:
urllib2.urlopen(request, data)
except (urllib2.HTTPError, urllib2.URLError) as ex:
oper.Die('Failed to upload command stats to %r: "%s"' % (url, ex))
def UploadConditionsMet():
"""Return True if upload conditions are met."""
# Verify that host domain is in golo.chromium.org or corp.google.com.
domain = cros_lib.GetHostDomain()
if not DOMAIN_RE.search(domain):
return False
# Verify that git user email is from chromium.org or google.com.
cwd = os.path.dirname(os.path.realpath(__file__))
git_id = cros_lib.GetProjectUserEmail(cwd, quiet=True)
if not GIT_ID_RE.search(git_id):
return False
# Check for presence of file that disables stats upload.
if os.path.exists(DISABLE_FILE):
return False
return True
def main(argv):
"""Main function."""
# This is not meant to be a user-friendly script. It takes one and
# only one argument, which is a build stats file to be uploaded
usage = 'Usage: %prog [-h|--help] build_stats_file'
epilog = (
'This script is not intended to be run manually. It is used as'
' part of the build command statistics project.'
)
parser = optparse.OptionParser(usage=usage, epilog=epilog)
(_options, args) = parser.parse_args(argv)
if len(args) > 1:
oper.Die('Only one argument permitted.')
if len(args) < 1:
oper.Die('Missing command stats file argument.')
# Silently do nothing if the conditions for uploading are not met.
if UploadConditionsMet():
stats = Stats()
stats.LoadFile(args[0])
try:
with cros_lib.SubCommandTimeout(UPLOAD_TIMEOUT):
stats.Upload(URL)
except cros_lib.TimeoutError:
oper.Die('Timed out during upload - waited %i seconds' % UPLOAD_TIMEOUT)