blob: 04909fb2958ed5cbbe28329ffa338181f3c50a14 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2011 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.
import optparse
import os
import re
import sys
import time
import urllib2
__version__ = '1.0'
BUILDER_URL = 'http://build.chromium.org/p/chromium.perf/builders/%s'
BUILD_URL = 'http://build.chromium.org/p/chromium.perf/builders/%s/builds/%s'
STEP_STDIO_URL = ('http://build.chromium.org/p/chromium.perf/builders/%s/'
'builds/%s/steps/%s/logs/stdio')
USAGE = ''
# The revision range where the issue occurred.
LOWER_REV = 69491
UPPER_REV = 70280
# These were the active bots running the page cycler at that time.
BUILDERS = {
'Linux%20Perf%20(1)': {'lower': 2035, 'upper': 2389},
'Linux%20Perf%20(lowmem)': {'lower': 165, 'upper': 317},
'Mac10.5%20Perf(1)': {'lower': 1018, 'upper': 1209},
'Mac10.6%20Perf(1)': {'lower': 1509, 'upper': 1800},
'Vista%20Perf%20(1)': {'lower': 2135, 'upper': 2471},
'Vista%20Perf%20(dbg)': {'lower': 1438, 'upper': 1738},
'Vista%20Perf%20(jank)': {'lower': 249, 'upper': 417},
'XP%20Perf%20(1)': {'lower': 2035, 'upper': 2361},
'XP%20Perf%20(dbg)': {'lower': 1081, 'upper': 1287},
'XP%20Perf%20(jank)': {'lower': 406, 'upper': 552},
}
# These were the active page cycler steps at that time.
STEPS = [
'page_cycler_moz',
'page_cycler_morejs',
'page_cycler_intl1',
'page_cycler_intl2',
'page_cycler_dhtml',
'page_cycler_database',
'page_cycler_indexeddb',
'page_cycler_moz-http',
'page_cycler_bloat-http',
]
def GetBuilderUrl(builder):
return BUILDER_URL % builder
def GetBuildUrl(builder, buildnumber):
return BUILD_URL % (builder, buildnumber)
def GetStepStdioUrl(builder, buildnumber, test):
return STEP_STDIO_URL % (builder, buildnumber, test)
# Floating point representation of last time we fetched a URL.
last_fetched_at = None
def FetchUrlContents(url):
global last_fetched_at
if last_fetched_at and ((time.time() - last_fetched_at) <= 0.1):
# Sleep for a tenth of a second to avoid overloading the server.
time.sleep(0.1)
try:
last_fetched_at = time.time()
connection = urllib2.urlopen(url)
except urllib2.HTTPError, e:
if e.code == 404:
return None
raise e
text = connection.read().strip()
connection.close()
return text
def GetLatestBuildFromUrl(builder):
text = FetchUrlContents(GetBuilderUrl(builder))
if not text:
# Master doesn't know about this builder.
raise Exception('no such builder %s' % builder)
matching = False
for line in text.splitlines():
# First wait until we get to "Recent Builds".
match_data = re.match(r'.*Recent Builds.*', line)
if match_data:
matching = True
# If we've made it to "Recent Builds", then look for the first build line.
if matching:
match_data = re.match(r'.*/builds/(\d+)".*', line)
if match_data:
return int(match_data.group(1))
# Malformed output.
raise Exception('master returned output missing latest build number')
latestdata = {}
def GetLatestBuild(builder):
global latestdata
if builder not in latestdata:
latestdata[builder] = GetLatestBuildFromUrl(builder)
return latestdata[builder]
def GetHighestRevisionForBuildFromUrl(builder, build):
url = GetBuildUrl(builder, build)
text = FetchUrlContents(url)
if not text:
# Master doesn't know about this build.
return None
# Match both 'Got Revision: ' and 'Revision: '.
match_data = re.match(r'.*Revision: (\d+).*', text, re.DOTALL)
if not match_data:
# Malformed output.
raise Exception('master returned output missing revision number, '
'url = %s' % url)
return int(match_data.group(1))
builderdata = {}
def GetHighestRevisionForBuild(builder, build):
global builderdata
if builder not in builderdata:
builderdata[builder] = {}
if build not in builderdata[builder]:
builderdata[builder][build] = GetHighestRevisionForBuildFromUrl(builder,
build)
return builderdata[builder][build]
def GetBuildRange(builder, build):
thisbuild_revmax = GetHighestRevisionForBuild(builder, build)
prevbuild_revmax = GetHighestRevisionForBuild(builder, build-1)
if prevbuild_revmax and thisbuild_revmax == prevbuild_revmax:
# If this build and the previous build are for the same one revision, let
# this build's min revision be the same as its max.
thisbuild_revmin = prevbuild_revmax
elif prevbuild_revmax:
# If this build is newer than the previous build, let this build's min
# revision be one revision newer than the previous build's max revision.
thisbuild_revmin = prevbuild_revmax + 1
else:
# No data was available for the previous build, so we don't know this
# build's minimum revision.
thisbuild_revmin = None
if thisbuild_revmax < thisbuild_revmin:
raise Exception('ERROR: builder %s build %s revmax is less than revmin' % (
builder, build))
return (thisbuild_revmin, thisbuild_revmax)
FIND_TOP_REV = [
['TopRevInBuild', 'found'],
['BottomRevInBuild', 'buildmin'],
['BuildBetweenRevs', 'buildmin'],
['BuildBelowBottomRev', 'buildmin'],
['BuildAboveTopRev', 'buildmax'],
]
FIND_BOTTOM_REV = [
['BottomRevInBuild', 'found'],
['TopRevInBuild', 'buildmax'],
['BuildBetweenRevs', 'buildmax'],
['BuildBelowBottomRev', 'buildmin'],
['BuildAboveTopRev', 'buildmax'],
]
def BisectBuildRange(builder, routines, lowerbuild, upperbuild):
if upperbuild < lowerbuild:
raise Exception('ERROR: builder %s upperbuild %s cannot be less than '
'lowerbuild %s' % (builder, upperbuild, lowerbuild))
buildmin = lowerbuild
buildmax = upperbuild
buildguess = buildmax
iteration = 0
while ((buildmax - buildmin) > 0):
iteration += 1
(revmin, revmax) = GetBuildRange(builder, buildguess)
bottom_rev_in_build = (revmin <= LOWER_REV <= revmax)
top_rev_in_build = (revmin <= UPPER_REV <= revmax)
build_between_revs = (LOWER_REV < revmin <= revmax < UPPER_REV)
build_below_bottom_rev = (revmin < LOWER_REV)
build_above_top_rev = (UPPER_REV < revmax)
for routine in routines:
if (routine[0] == 'BottomRevInBuild' and bottom_rev_in_build):
if routine[1] == 'found': return buildguess
elif routine[1] == 'buildmin': buildmin = buildguess
elif routine[1] == 'buildmax': buildmax = buildguess
elif (routine[0] == 'TopRevInBuild' and top_rev_in_build):
if routine[1] == 'found': return buildguess
elif routine[1] == 'buildmin': buildmin = buildguess
elif routine[1] == 'buildmax': buildmax = buildguess
elif (routine[0] == 'BuildBetweenRevs' and build_between_revs):
if routine[1] == 'buildmin': buildmin = buildguess
elif routine[1] == 'buildmax': buildmax = buildguess
elif (routine[0] == 'BuildBelowBottomRev' and build_below_bottom_rev):
if routine[1] == 'buildmin': buildmin = buildguess
elif routine[1] == 'buildmax': buildmax = buildguess
elif (routine[0] == 'BuildAboveTopRev' and build_above_top_rev):
if routine[1] == 'buildmin': buildmin = buildguess
elif routine[1] == 'buildmax': buildmax = buildguess
buildguess = buildmin + int((buildmax - buildmin) / 2)
if iteration > 20:
raise Exception('ERROR: failed to bisect within 20 attempts')
return None
def SaveStdio(builder, build, step, stdio):
build = str(build)
buildpath = os.path.join(builder, build)
steppath = os.path.join(builder, build, step)
if not os.path.exists(builder):
os.mkdir(builder)
if not os.path.exists(buildpath):
os.mkdir(buildpath)
stepfile = open(steppath, 'w')
stepfile.write(stdio)
stepfile.close()
def Main(args):
parser = optparse.OptionParser(usage=USAGE, version=__version__)
parser.add_option('-s', '--build-range', action='store_true',
dest='build_range', default=False,
help='get builder build ranges')
parser.add_option('-d', '--download', action='store_true',
dest='download', default=False,
help='download page cycler data')
parser.add_option('-q', '--quick-mode', action='store_true',
dest='quick_mode', default=False,
help='only download oldest available output')
options, args = parser.parse_args(args)
# Change to this script's directory.
os.chdir(os.path.dirname(os.path.abspath(__file__)))
if options.build_range:
builder_keys = BUILDERS.keys()
builder_keys.sort()
print 'BUILDERS = {'
for builder in builder_keys:
if BUILDERS[builder]['lower'] and BUILDERS[builder]['upper']:
print ' \'%s\': {\'lower\': %d, \'upper\': %d},' % (
builder, BUILDERS[builder]['lower'], BUILDERS[builder]['upper'])
continue
latest_build = GetLatestBuild(builder)
if not BUILDERS[builder]['lower']:
BUILDERS[builder]['lower'] = BisectBuildRange(
builder, FIND_BOTTOM_REV, 1, latest_build)
if not BUILDERS[builder]['upper']:
BUILDERS[builder]['upper'] = BisectBuildRange(
builder, FIND_TOP_REV, 1, latest_build)
print ' \'%s\': {\'lower\': %d, \'upper\': %d},' % (
builder, BUILDERS[builder]['lower'], BUILDERS[builder]['upper'])
print '}'
elif options.download:
builder_keys = BUILDERS.keys()
builder_keys.sort()
for builder in builder_keys:
if not BUILDERS[builder]['lower'] or not BUILDERS[builder]['upper']:
print 'skipping builder %s' % builder
continue
build_stdio_present = 0
for build in range(BUILDERS[builder]['lower'],
BUILDERS[builder]['upper']):
stdio_retrieved = False
stdio_present = False
for step in STEPS:
# Skip files that already exist.
if os.path.exists(os.path.join(builder, str(build), step)):
continue
stdio = FetchUrlContents(GetStepStdioUrl(builder, build, step))
stdio_retrieved = True
if stdio:
stdio_present = True
SaveStdio(builder, build, step, stdio)
else:
SaveStdio(builder, build, step, 'missing')
if options.quick_mode:
# Break out of downloading the rest of the steps for now since
# we assume they're also missing.
break
if stdio_retrieved:
print '%s/%s' % (builder, build)
if stdio_present:
build_stdio_present += 1
# Now since we want to prefer finding the oldest data, break to the next
# builder.
if options.quick_mode and (build_stdio_present > 10):
break
if __name__ == '__main__':
sys.exit(Main(sys.argv))