Skip to content

Instantly share code, notes, and snippets.

@davehunt
Created March 20, 2014 21:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davehunt/9674346 to your computer and use it in GitHub Desktop.
Save davehunt/9674346 to your computer and use it in GitHub Desktop.
b2gperf regression hunter
import argparse
import logging
import re
import time
import sys
from BeautifulSoup import BeautifulSoup
import jenkins
import requests
# TODO Set low priority in Jenkins for regression hunting jobs
# TODO Support app names with spaces
def url_links(url, regex=None, auth=None):
r = requests.get(url, auth=auth)
r.raise_for_status()
soup = BeautifulSoup(r.text)
links = [link.get('href') for link in soup.findAll('a')]
if regex:
return [link for link in links if re.match(regex, link)]
else:
return links
def get_builds(branch, device, good_rev, bad_rev, eng=False, max_builds=20,
auth=None):
revisions = []
path = '' if branch == 'mozilla-central' else 'integration/'
pushlog_url = 'https://hg.mozilla.org/%s%s/json-pushes?fromchange=%s' \
'&tochange=%s' % (path, branch, good_rev, bad_rev)
print 'Getting revisions from: %s' % pushlog_url
r = requests.get(pushlog_url)
r.raise_for_status()
pushlog = r.json()
for push_id in sorted(pushlog.keys()):
push = pushlog[push_id]
revisions.append((push['changesets'][-1], push['date']))
print '--------> %d revisions found' % len(revisions)
revisions.sort(key=lambda r: r[1]) # sort revisions by date
if not revisions:
return []
start_time = revisions[0][1]
end_time = revisions[-1][1]
raw_revisions = map(lambda l: l[0], revisions)
base_url = 'https://pvtbuilds.mozilla.org/pvt/mozilla.org/b2gotoro/' \
'tinderbox-builds/%s-%s%s/' % (
branch, device, '-eng' if eng else '')
range = 60 * 60 * 4 # anything within four hours
format = '%Y%m%d%H%M%S'
print 'Getting builds from: %s' % base_url
ts = map(lambda l: int(time.mktime(time.strptime(l.strip('/'), format))),
url_links(base_url, '^\d{14}/$', auth))
print '--------> %d builds found' % len(ts)
ts_in_range = filter(lambda t: t > (start_time - range) and
t < (end_time + range), ts)
print '--------> %d builds within range' % len(ts_in_range)
builds = []
for t in [time.strftime(format, time.localtime(t)) for t in ts_in_range]:
for link in url_links('%s%s/' % (base_url, t), '^sources\.xml$', auth):
url = '%s%s/%s' % (base_url, t, link)
r = requests.get(url, auth=auth)
search = re.search('<project .* path="gecko" remote="hgmozillaorg"'
' revision="(\w{12})"/>', r.text)
if search:
for revision in raw_revisions:
if search.group(1) == revision[:12]:
builds.append((revision, '%s%s' % (base_url, t)))
print '--------> %d builds matching revisions' % len(builds)
if len(builds) > max_builds:
builds = builds[:max_builds]
print 'Build count exceeds maximum. Only the first %s builds will ' \
'be selected.' % max_builds
raw_input('Press return to continue or ctrl-c to abort.')
print 'Selected range: %s:%s' % (builds[0][0][:12], builds[-1][0][:12])
return builds
def cli(args=sys.argv[1:]):
parser = argparse.ArgumentParser(
description='Trigger Jenkins jobs for all tinderbox builds '
'between revisions.')
parser.add_argument(
'-v', '--verbose',
action='store_true',
default=False,
help='verbose output')
parser.add_argument(
'--dry-run',
action='store_true',
default=False,
help='output the jobs to console without triggering them')
parser.add_argument(
'-m',
dest='max_builds',
type=int,
default=20,
help='maximum number of builds to trigger (default: %(default)s)')
parser.add_argument(
'-b',
dest='branch',
default='mozilla-central',
help='branch to use (default: %(default)s)')
parser.add_argument(
'--eng',
action='store_true',
default=False,
help='limit to engineering builds')
parser.add_argument(
'-a',
dest='apps',
metavar='APP',
nargs='*',
help='names of applications to test')
parser.add_argument(
'-u',
dest='username',
help='username for access to the builds')
parser.add_argument(
'-p',
dest='password',
help='password for access to the builds')
parser.add_argument(
'-j',
dest='jenkins_url',
default='http://localhost:8080',
help='url of jenkins instance (default: %(default)s)')
parser.add_argument(
'-e',
dest='email',
help='email address to send result notifications')
parser.add_argument(
'device_name',
help='name of device')
parser.add_argument(
'job_name',
help='name of the jenkins job to execute')
parser.add_argument(
'good_rev',
help='last known good revision')
parser.add_argument(
'bad_rev',
help='first known bad revision')
args = parser.parse_args()
log_level = logging.DEBUG if args.verbose else logging.WARN
logging.basicConfig(level=log_level)
auth = any([args.username, args.password]) and \
(args.username, args.password) or None
builds = get_builds(args.branch, args.device_name, args.good_rev,
args.bad_rev, args.eng, args.max_builds, auth)
j = jenkins.Jenkins(args.jenkins_url)
if builds:
parameters = {'NOTIFICATION_ADDRESS': args.email}
if args.apps:
print 'Application%s under test: %s' % (
's' if len(args.apps) > 1 else '', ', '.join(args.apps))
parameters.update({'APP_NAMES': ' '.join(args.apps)})
print 'Results will be sent to: %s' % args.email
for build in builds:
build_url = build[1]
parameters.update({'BUILD_URL': build_url})
print 'Triggering %s for: %s' % (args.job_name, build_url)
if not args.dry_run:
j.build_job(args.job_name, parameters)
time.sleep(0.5)
else:
print 'No builds to trigger.'
if __name__ == '__main__':
cli()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment