Commit e34fd7dd authored by sullivan@chromium.org's avatar sullivan@chromium.org

Implement --browser=<perf trybot>

BUG=401180

Review URL: https://codereview.chromium.org/461043003

Cr-Commit-Position: refs/heads/master@{#291246}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@291246 0039d316-1c4b-4281-b951-d872f2087c98
parent 3f63b4aa
......@@ -67,7 +67,7 @@ class PossibleAndroidBrowser(possible_browser.PossibleBrowser):
def __init__(self, browser_type, finder_options, backend_settings, apk_name):
super(PossibleAndroidBrowser, self).__init__(browser_type, 'android',
finder_options, backend_settings.supports_tab_control)
assert browser_type in FindAllBrowserTypes(), \
assert browser_type in FindAllBrowserTypes(finder_options), \
('Please add %s to android_browser_finder.FindAllBrowserTypes' %
browser_type)
self._backend_settings = backend_settings
......@@ -177,7 +177,7 @@ def CanFindAvailableBrowsers(logging=real_logging):
return False
def FindAllBrowserTypes():
def FindAllBrowserTypes(_):
return CHROME_PACKAGE_NAMES.keys()
......
......@@ -20,7 +20,7 @@ class PossibleCrOSBrowser(possible_browser.PossibleBrowser):
def __init__(self, browser_type, finder_options, cri, is_guest):
super(PossibleCrOSBrowser, self).__init__(browser_type, 'cros',
finder_options, True)
assert browser_type in FindAllBrowserTypes(), \
assert browser_type in FindAllBrowserTypes(finder_options), \
('Please add %s to cros_browser_finder.FindAllBrowserTypes()' %
browser_type)
self._cri = cri
......@@ -72,7 +72,7 @@ def CanFindAvailableBrowsers(finder_options):
finder_options.cros_remote or
cros_interface.HasSSH())
def FindAllBrowserTypes():
def FindAllBrowserTypes(_):
return [
'cros-chrome',
'cros-chrome-guest',
......
......@@ -24,7 +24,7 @@ class PossibleDesktopBrowser(possible_browser.PossibleBrowser):
target_os = sys.platform.lower()
super(PossibleDesktopBrowser, self).__init__(browser_type, target_os,
finder_options, not is_content_shell)
assert browser_type in FindAllBrowserTypes(), \
assert browser_type in FindAllBrowserTypes(finder_options), \
('Please add %s to desktop_browser_finder.FindAllBrowserTypes' %
browser_type)
self._local_executable = executable
......@@ -88,7 +88,7 @@ def SelectDefaultBrowser(possible_browsers):
def CanFindAvailableBrowsers():
return not cros_interface.IsRunningOnCrosDevice()
def FindAllBrowserTypes():
def FindAllBrowserTypes(_):
return [
'exact',
'release',
......
......@@ -62,7 +62,7 @@ def CanFindAvailableBrowsers():
return platform.GetHostPlatform().GetOSName() == 'mac'
def FindAllBrowserTypes():
def FindAllBrowserTypes(_):
return IOS_BROWSERS.values()
......
# Copyright 2014 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.
# Copyright 2014 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.
"""Finds perf trybots that can run telemetry tests."""
import json
import logging
import re
import subprocess
import sys
import urllib2
from telemetry import decorators
from telemetry.core import platform
from telemetry.core import possible_browser
# TODO(sullivan): Check for blink changes
CONFIG_FILENAME = 'tools/run-perf-test.cfg'
class PossibleTrybotBrowser(possible_browser.PossibleBrowser):
"""A script that sends a job to a trybot."""
def __init__(self, browser_type, finder_options):
target_os = browser_type.split('-')[1]
self._buildername = '%s_perf_bisect' % browser_type.replace(
'trybot-', '').replace('-', '_')
super(PossibleTrybotBrowser, self).__init__(browser_type, target_os,
finder_options, True)
def Create(self):
raise NotImplementedError()
def SupportsOptions(self, finder_options):
if (finder_options.android_device or
finder_options.chrome_root or
finder_options.cros_remote or
finder_options.extensions_to_load or
finder_options.interactive or
finder_options.profile_dir):
return False
return True
def IsRemote(self):
return True
def _RunProcess(self, cmd):
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()
returncode = proc.poll()
return (returncode, out, err)
def RunRemote(self):
"""Sends a tryjob to a perf trybot.
This creates a branch, telemetry-tryjob, switches to that branch, edits
the bisect config, commits it, uploads the CL to rietveld, and runs a
tryjob on the given bot.
"""
returncode, original_branchname, err = self._RunProcess(
['git', 'rev-parse', '--abbrev-ref', 'HEAD'])
if returncode:
logging.error('Must be in a git repository to send changes to trybots.')
if err:
logging.error('Git error: %s', err)
return
original_branchname = original_branchname.strip()
# Check if the tree is dirty: make sure the index is up to date and then
# run diff-index
self._RunProcess(['git', 'update-index', '--refresh', '-q'])
returncode, out, err = self._RunProcess(['git', 'diff-index', 'HEAD'])
if out:
logging.error(
'Cannot send a try job with a dirty tree. Commit locally first.')
return
# Make sure the tree does have local commits.
returncode, out, err = self._RunProcess(
['git', 'log', 'origin/master..HEAD'])
if not out:
logging.error('No local changes on branch %s. browser=%s argument sends '
'local changes to the %s perf trybot.', original_branchname,
self._browser_type, self._buildername)
return
# Create/check out the telemetry-tryjob branch, and edit the configs
# for the tryjob there.
returncode, out, err = self._RunProcess(
['git', 'checkout', '-b', 'telemetry-tryjob'])
if returncode:
logging.error('Error creating branch telemetry-tryjob. '
'Please delete it if it exists.')
logging.error(err)
return
# Generate the command line for the perf trybots
arguments = sys.argv
if self._target_os == 'win':
arguments[0] = 'python tools\\perf\\run_measurement'
else:
arguments[0] = './tools/perf/run_measurement'
for index, arg in enumerate(arguments):
if arg.startswith('--browser='):
if self._target_os == 'android':
arguments[index] = '--browser=android-chrome-shell'
else:
arguments[index] = '--browser=release'
command = ' '.join(arguments)
# Add the correct command to the config file and commit it.
config = {
'command': command,
'repeat_count': '1',
'max_time_minutes': '120',
'truncate_percent': '0',
}
try:
config_file = open(CONFIG_FILENAME, 'w')
except IOError:
logging.error('Cannot find %s. Please run from src dir.', CONFIG_FILENAME)
return
config_file.write('config = %s' % json.dumps(
config, sort_keys=True, indent=2, separators=(',', ': ')))
config_file.close()
returncode, out, err = self._RunProcess(
['git', 'commit', '-a', '-m', 'bisect config'])
if returncode:
logging.error('Could not commit bisect config change, error %s', err)
return
# Upload the CL to rietveld and run a try job.
returncode, out, err = self._RunProcess([
'git', 'cl', 'upload', '-f', '--bypass-hooks', '-m',
'CL for perf tryjob'
])
if returncode:
logging.error('Could upload to reitveld, error %s', err)
return
match = re.search(r'https://codereview.chromium.org/[\d]+', out)
if not match:
logging.error('Could not upload CL to reitveld! Output %s', out)
return
print 'Uploaded try job to reitveld. View progress at %s' % match.group(0)
returncode, out, err = self._RunProcess([
'git', 'cl', 'try', '-m', 'tryserver.chromium.perf',
'-b', self._buildername])
if returncode:
logging.error('Could not try CL, error %s', err)
return
# Checkout original branch and delete telemetry-tryjob branch.
returncode, out, err = self._RunProcess(
['git', 'checkout', original_branchname])
if returncode:
logging.error(
('Could not check out %s. Please check it out and manually '
'delete the telemetry-tryjob branch. Error message: %s'),
original_branchname, err)
return
returncode, out, err = self._RunProcess(
['git', 'branch', '-D', 'telemetry-tryjob'])
if returncode:
logging.error(('Could not delete telemetry-tryjob branch. '
'Please delete it manually. Error %s'), err)
return
def _InitPlatformIfNeeded(self):
if self._platform:
return
self._platform = platform.GetHostPlatform()
# pylint: disable=W0212
self._platform_backend = self._platform._platform_backend
def SelectDefaultBrowser(_):
return None
def CanFindAvailableBrowsers():
return True
@decorators.Cache
def _GetTrybotList():
f = urllib2.urlopen(
'http://build.chromium.org/p/tryserver.chromium.perf/json')
builders = json.loads(f.read()).get('builders', {}).keys()
builders = ['trybot-%s' % b.replace('_perf_bisect', '').replace('_', '-')
for b in builders if not b.endswith('_perf_bisect_builder')]
return builders
def FindAllBrowserTypes(finder_options):
# Listing browsers requires an http request; only do this if the user is
# running with browser=list or a browser=trybot-* argument.
if (finder_options.browser_type and
(finder_options.browser_type == 'list' or
finder_options.browser_type.startswith('trybot'))):
return _GetTrybotList()
return []
def FindAllAvailableBrowsers(finder_options):
"""Find all perf trybots on tryserver.chromium.perf."""
return [PossibleTrybotBrowser(b, finder_options) for b in
FindAllBrowserTypes(finder_options)]
# Copyright 2014 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 json
import logging
import StringIO
import unittest
from telemetry.core import browser_options
from telemetry.core.backends.remote import trybot_browser_finder
from telemetry.unittest import simple_mock
from telemetry.unittest import system_stub
class TrybotBrowserFinderTest(unittest.TestCase):
def setUp(self):
self.log_output = StringIO.StringIO()
self.stream_handler = logging.StreamHandler(self.log_output)
logging.getLogger().addHandler(self.stream_handler)
self._real_subprocess = trybot_browser_finder.subprocess
self._real_urllib2 = trybot_browser_finder.urllib2
self._stubs = system_stub.Override(trybot_browser_finder, ['sys', 'open'])
def tearDown(self):
logging.getLogger().removeHandler(self.stream_handler)
self.log_output.close()
trybot_browser_finder.subprocess = self._real_subprocess
trybot_browser_finder.urllib2 = self._real_urllib2
self._stubs.Restore()
def _ExpectProcesses(self, args):
mock_subprocess = simple_mock.MockObject()
mock_subprocess.SetAttribute('PIPE', simple_mock.MockObject())
for arg in args:
mock_popen = simple_mock.MockObject()
mock_popen.ExpectCall('communicate').WillReturn(arg[1][1:])
mock_popen.ExpectCall('poll').WillReturn(arg[1][0])
mock_subprocess.ExpectCall(
'Popen').WithArgs(arg[0]).WillReturn(mock_popen)
trybot_browser_finder.subprocess = mock_subprocess
def test_find_all_browser_types_list(self):
finder_options = browser_options.BrowserFinderOptions(browser_type='list')
trybot_browser_finder.urllib2 = simple_mock.MockObject()
trybot_browser_finder.urllib2.ExpectCall('urlopen').WithArgs(
'http://build.chromium.org/p/tryserver.chromium.perf/json').WillReturn(
StringIO.StringIO(json.dumps({'builders': {
'android_nexus4_perf_bisect': 'stuff',
'mac_10_9_perf_bisect': 'otherstuff',
'win_perf_bisect_builder': 'not a trybot',
}})))
self.assertEquals(
['trybot-android-nexus4', 'trybot-mac-10-9'],
# pylint: disable=W0212
sorted(trybot_browser_finder.FindAllBrowserTypes(finder_options)))
def test_find_all_browser_types_trybot(self):
finder_options = browser_options.BrowserFinderOptions(
browser_type='trybot-win')
trybot_browser_finder.urllib2 = simple_mock.MockObject()
trybot_browser_finder.urllib2.ExpectCall('urlopen').WithArgs(
'http://build.chromium.org/p/tryserver.chromium.perf/json').WillReturn(
StringIO.StringIO(json.dumps({'builders': {
'android_nexus4_perf_bisect': 'stuff',
'mac_10_9_perf_bisect': 'otherstuff',
'win_perf_bisect_builder': 'not a trybot',
}})))
self.assertEquals(
['trybot-android-nexus4', 'trybot-mac-10-9'],
# pylint: disable=W0212
sorted(trybot_browser_finder.FindAllBrowserTypes(finder_options)))
def test_find_all_browser_types_non_trybot_browser(self):
finder_options = browser_options.BrowserFinderOptions(
browser_type='release')
trybot_browser_finder.urllib2 = simple_mock.MockObject()
self.assertEquals(
[],
# pylint: disable=W0212
sorted(trybot_browser_finder.FindAllBrowserTypes(finder_options)))
def test_constructor(self):
finder_options = browser_options.BrowserFinderOptions()
browser = trybot_browser_finder.PossibleTrybotBrowser(
'trybot-android-nexus4', finder_options)
# pylint: disable=W0212
self.assertEquals('android', browser._target_os)
# pylint: disable=W0212
self.assertEquals('android_nexus4_perf_bisect', browser._buildername)
def test_no_git(self):
finder_options = browser_options.BrowserFinderOptions()
browser = trybot_browser_finder.PossibleTrybotBrowser(
'trybot-android-nexus4', finder_options)
self._ExpectProcesses((
(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], (128, None, None)),
))
browser.RunRemote()
self.assertEquals(
'Must be in a git repository to send changes to trybots.\n',
self.log_output.getvalue())
def test_dirty_tree(self):
finder_options = browser_options.BrowserFinderOptions()
browser = trybot_browser_finder.PossibleTrybotBrowser(
'trybot-android-nexus4', finder_options)
self._ExpectProcesses((
(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], (0, 'br', None)),
(['git', 'update-index', '--refresh', '-q'], (0, None, None,)),
(['git', 'diff-index', 'HEAD'], (0, 'dirty tree', None)),
))
browser.RunRemote()
self.assertEquals(
'Cannot send a try job with a dirty tree. Commit locally first.\n',
self.log_output.getvalue())
def test_no_local_commits(self):
finder_options = browser_options.BrowserFinderOptions()
browser = trybot_browser_finder.PossibleTrybotBrowser(
'trybot-android-nexus4', finder_options)
self._ExpectProcesses((
(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], (0, 'br', None)),
(['git', 'update-index', '--refresh', '-q'], (0, None, None,)),
(['git', 'diff-index', 'HEAD'], (0, '', None)),
(['git', 'log', 'origin/master..HEAD'], (0, '', None)),
))
browser.RunRemote()
self.assertEquals(
('No local changes on branch br. browser=trybot-android-nexus4 '
'argument sends local changes to the android_nexus4_perf_bisect '
'perf trybot.\n'),
self.log_output.getvalue())
def test_branch_checkout_fails(self):
finder_options = browser_options.BrowserFinderOptions()
browser = trybot_browser_finder.PossibleTrybotBrowser(
'trybot-android-nexus4', finder_options)
self._ExpectProcesses((
(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], (0, 'br', None)),
(['git', 'update-index', '--refresh', '-q'], (0, None, None,)),
(['git', 'diff-index', 'HEAD'], (0, '', None)),
(['git', 'log', 'origin/master..HEAD'], (0, 'logs here', None)),
(['git', 'checkout', '-b', 'telemetry-tryjob'],
(1, None, 'fatal: A branch named \'telemetry-try\' already exists.')),
))
browser.RunRemote()
self.assertEquals(
('Error creating branch telemetry-tryjob. '
'Please delete it if it exists.\n'
'fatal: A branch named \'telemetry-try\' already exists.\n'),
self.log_output.getvalue())
def _GetConfigForBrowser(self, name, branch):
finder_options = browser_options.BrowserFinderOptions()
browser = trybot_browser_finder.PossibleTrybotBrowser(name, finder_options)
bot = '%s_perf_bisect' % name.replace('trybot-', '').replace('-', '_')
self._ExpectProcesses((
(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], (0, branch, None)),
(['git', 'update-index', '--refresh', '-q'], (0, None, None,)),
(['git', 'diff-index', 'HEAD'], (0, '', None)),
(['git', 'log', 'origin/master..HEAD'], (0, 'logs here', None)),
(['git', 'checkout', '-b', 'telemetry-tryjob'], (0, None, None)),
(['git', 'commit', '-a', '-m', 'bisect config'], (0, None, None)),
(['git', 'cl', 'upload', '-f', '--bypass-hooks', '-m',
'CL for perf tryjob'],
(0, 'stuff https://codereview.chromium.org/12345 stuff', None)),
(['git', 'cl', 'try', '-m', 'tryserver.chromium.perf', '-b', bot],
(0, None, None)),
(['git', 'checkout', branch], (0, None, None)),
(['git', 'branch', '-D', 'telemetry-tryjob'], (0, None, None))
))
self._stubs.sys.argv = [
'tools/perf/run_benchmark',
'--browser=%s' % browser,
'sunspider']
cfg = StringIO.StringIO()
self._stubs.open.files = {'tools/run-perf-test.cfg': cfg}
browser.RunRemote()
return cfg.getvalue()
def test_config_android(self):
config = self._GetConfigForBrowser('trybot-android-nexus4', 'somebranch')
self.assertEquals(
('config = {\n'
' "command": "./tools/perf/run_measurement '
'--browser=android-chrome-shell sunspider",\n'
' "max_time_minutes": "120",\n'
' "repeat_count": "1",\n'
' "truncate_percent": "0"\n'
'}'), config)
def test_config_mac(self):
config = self._GetConfigForBrowser('trybot-mac-10-9', 'currentwork')
self.assertEquals(
('config = {\n'
' "command": "./tools/perf/run_measurement '
'--browser=release sunspider",\n'
' "max_time_minutes": "120",\n'
' "repeat_count": "1",\n'
' "truncate_percent": "0"\n'
'}'), config)
......@@ -30,7 +30,7 @@ class PossibleWebDriverBrowser(possible_browser.PossibleBrowser):
target_os = sys.platform.lower()
super(PossibleWebDriverBrowser, self).__init__(browser_type, target_os,
finder_options, False)
assert browser_type in FindAllBrowserTypes(), \
assert browser_type in FindAllBrowserTypes(finder_options), \
('Please add %s to webdriver_desktop_browser_finder.FindAllBrowserTypes'
% browser_type)
......@@ -81,7 +81,7 @@ class PossibleDesktopIE(PossibleWebDriverBrowser):
def SelectDefaultBrowser(_):
return None
def FindAllBrowserTypes():
def FindAllBrowserTypes(_):
if webdriver:
return [
'internet-explorer',
......
......@@ -12,6 +12,7 @@ from telemetry.core.backends.chrome import android_browser_finder
from telemetry.core.backends.chrome import cros_browser_finder
from telemetry.core.backends.chrome import desktop_browser_finder
from telemetry.core.backends.chrome import ios_browser_finder
from telemetry.core.backends.remote import trybot_browser_finder
from telemetry.core.backends.webdriver import webdriver_desktop_browser_finder
BROWSER_FINDERS = [
......@@ -19,6 +20,7 @@ BROWSER_FINDERS = [
android_browser_finder,
cros_browser_finder,
ios_browser_finder,
trybot_browser_finder,
webdriver_desktop_browser_finder,
]
......@@ -31,10 +33,9 @@ class BrowserFinderException(Exception):
pass
@decorators.Cache
def FindAllBrowserTypes():
def FindAllBrowserTypes(options):
return reduce(operator.add,
[bf.FindAllBrowserTypes() for bf in BROWSER_FINDERS])
[bf.FindAllBrowserTypes(options) for bf in BROWSER_FINDERS])
@decorators.Cache
......@@ -69,8 +70,8 @@ def FindBrowser(options):
browsers = []
default_browsers = []
for finder in BROWSER_FINDERS:
if (options.browser_type and options.browser_type != 'any' and
options.browser_type not in finder.FindAllBrowserTypes()):
if(options.browser_type and options.browser_type != 'any' and
options.browser_type not in finder.FindAllBrowserTypes(options)):
continue
curr_browsers = finder.FindAllAvailableBrowsers(options)
new_default_browser = finder.SelectDefaultBrowser(curr_browsers)
......@@ -99,7 +100,7 @@ def FindBrowser(options):
'\n'.join(sorted(set([b.browser_type for b in browsers]))))
if options.browser_type == 'any':
types = FindAllBrowserTypes()
types = FindAllBrowserTypes(options)
def CompareBrowsersOnTypePriority(x, y):
x_idx = types.index(x.browser_type)
y_idx = types.index(y.browser_type)
......
......@@ -63,7 +63,7 @@ class BrowserFinderOptions(optparse.Values):
default=None,
help='Browser type to run, '
'in order of priority. Supported values: list,%s' %
','.join(browser_finder.FindAllBrowserTypes()))
','.join(browser_finder.FindAllBrowserTypes(self)))
group.add_option('--browser-executable',
dest='browser_executable',
help='The exact browser to run.')
......
......@@ -54,5 +54,14 @@ class PossibleBrowser(object):
"""Tests for extension support."""
raise NotImplementedError()
def IsRemote(self):
return False
def RunRemote(self):
pass
def UpdateExecutableIfNeeded(self):
pass
def last_modification_time(self):
return -1
......@@ -360,6 +360,10 @@ def Run(test, page_set, expectations, finder_options, results):
logging.warning('Pass --also-run-disabled-tests to squelch this message.')
return
if possible_browser.IsRemote():
possible_browser.RunRemote()
sys.exit(0)
# Reorder page set based on options.
pages = _ShuffleAndFilterPageSet(page_set, finder_options)
......
......@@ -139,6 +139,12 @@ class OpenFunctionStub(object):
else:
return self._data
def write(self, data):
self._data.write(data)
def close(self):
pass
def __init__(self):
self.files = {}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment