diff --git a/python/mozperftest/mozperftest/browser/__init__.py b/python/mozperftest/mozperftest/browser/__init__.py index 9c37c173159a..9662a7247aab 100644 --- a/python/mozperftest/mozperftest/browser/__init__.py +++ b/python/mozperftest/mozperftest/browser/__init__.py @@ -2,13 +2,15 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. from mozperftest.browser.browsertime import BrowsertimeRunner +from mozperftest.browser.profile import Profile +from mozperftest.layers import Layers def get_layers(): - return (BrowsertimeRunner,) + return (Profile, BrowsertimeRunner) def pick_browser(env, flavor, mach_cmd): if flavor == "script": - return BrowsertimeRunner(env, mach_cmd) + return Layers(env, mach_cmd, get_layers()) raise NotImplementedError(flavor) diff --git a/python/mozperftest/mozperftest/browser/browsertime.py b/python/mozperftest/mozperftest/browser/browsertime.py index 76265396690e..10ecf1bab999 100644 --- a/python/mozperftest/mozperftest/browser/browsertime.py +++ b/python/mozperftest/mozperftest/browser/browsertime.py @@ -67,7 +67,16 @@ class BrowsertimeRunner(NodeRunner): name = "browsertime (%s)" % NodeRunner.name arguments = { - "--cycles": {"type": int, "default": 1, "help": "Number of full cycles"} + "--browser-cycles": { + "type": int, + "default": 1, + "help": "Number of full cycles", + }, + "--browser-binary": { + "type": str, + "default": None, + "help": "Path to the desktop browser, or Android app name.", + }, } def __init__(self, env, mach_cmd): @@ -75,7 +84,6 @@ class BrowsertimeRunner(NodeRunner): self.topsrcdir = mach_cmd.topsrcdir self._mach_context = mach_cmd._mach_context self.virtualenv_manager = mach_cmd.virtualenv_manager - self.proxy = None self._created_dirs = [] @property @@ -409,19 +417,27 @@ class BrowsertimeRunner(NodeRunner): return extra_args - def get_profile(self, metadata): - # XXX we'll use conditioned profiles - from mozprofile import create_profile + def browsertime_android(self, metadata): + app_name = self.get_arg("android-app-name") - profile = create_profile(app="firefox") - prefs = metadata.get_browser_prefs() - profile.set_preferences(prefs) - self.info("Created profile at %s" % profile.profile) - self._created_dirs.append(profile.profile) - return profile + args_list = [ + # "--browser", "firefox", + "--android", + # Work around a `selenium-webdriver` issue where Browsertime + # fails to find a Firefox binary even though we're going to + # actually do things on an Android device. + # "--firefox.binaryPath", self.mach_cmd.get_binary_path(), + "--firefox.android.package", + app_name, + ] + activity = self.get_arg("android-activity") + if activity is not None: + args_list += ["--firefox.android.activity", activity] + + return args_list def __call__(self, metadata): - cycles = self.get_arg("cycles", 1) + cycles = self.get_arg("browser-cycles", 1) for cycle in range(1, cycles + 1): metadata.run_hook("before_cycle", cycle=cycle) try: @@ -431,9 +447,7 @@ class BrowsertimeRunner(NodeRunner): return metadata def _one_cycle(self, metadata): - # keep the object around - # see https://bugzilla.mozilla.org/show_bug.cgi?id=1625118 - profile = self.get_profile(metadata) + profile = self.get_arg("profile-directory") test_script = self.get_arg("tests")[0] output = self.get_arg("output") if output is not None: @@ -451,7 +465,7 @@ class BrowsertimeRunner(NodeRunner): "--resultDir", result_dir, "--firefox.profileTemplate", - profile.profile, + profile, "--iterations", "1", test_script, @@ -473,14 +487,12 @@ class BrowsertimeRunner(NodeRunner): args += ["--" + name, value] firefox_args = ["--firefox.developer"] + if self.get_arg("android"): + args.extend(self.browsertime_android(metadata)) + extra = self.extra_default_args(args=firefox_args) command = [self.browsertime_js] + extra + args self.info("Running browsertime with this command %s" % " ".join(command)) self.node(command) metadata.set_result(result_dir) return metadata - - def teardown(self): - for dir in self._created_dirs: - if os.path.exists(dir): - shutil.rmtree(dir) diff --git a/python/mozperftest/mozperftest/browser/profile.py b/python/mozperftest/mozperftest/browser/profile.py new file mode 100644 index 000000000000..30efeed84081 --- /dev/null +++ b/python/mozperftest/mozperftest/browser/profile.py @@ -0,0 +1,59 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +import os +import shutil + +from mozperftest.layers import Layer +from mozprofile import create_profile +from mozprofile.prefs import Preferences + + +HERE = os.path.dirname(__file__) + + +class Profile(Layer): + name = "profile" + arguments = { + "--profile-directory": {"type": str, "default": None, "help": "Profile to use"}, + "--profile-user-js": {"type": str, "default": None, "help": "Custom user.js"}, + } + + def __init__(self, env, mach_cmd): + super(Profile, self).__init__(env, mach_cmd) + self._created_dirs = [] + + def setup(self): + pass + + def __call__(self, metadata): + if self.get_arg("profile-directory") is not None: + # no need to create one or load a conditioned one + return + # XXX we'll use conditioned profiles later + # + # XXX keeping a reference on self, otherwise mozprofile + # silently deletes the dir in a __del__ call + self.profile = profile = create_profile(app="firefox") + prefs = metadata.get_browser_prefs() + + if prefs == {}: + prefs["mozperftest"] = "true" + + # apply custom user prefs if any + user_js = self.get_arg("profile-user-js") + if user_js is not None: + self.info("Applying use prefs from %s" % user_js) + default_prefs = dict(Preferences.read_prefs(user_js)) + prefs.update(default_prefs) + + profile.set_preferences(prefs) + self.info("Created profile at %s" % profile.profile) + self._created_dirs.append(profile.profile) + self.set_arg("profile-directory", profile.profile) + return metadata + + def teardown(self): + for dir in self._created_dirs: + if os.path.exists(dir): + shutil.rmtree(dir) diff --git a/python/mozperftest/mozperftest/environment.py b/python/mozperftest/mozperftest/environment.py index cad7401e119a..bb1e59958079 100644 --- a/python/mozperftest/mozperftest/environment.py +++ b/python/mozperftest/mozperftest/environment.py @@ -35,13 +35,18 @@ class MachEnvironment: finally: self.unfreeze() + def _normalize(self, name): + if name.startswith("--"): + name = name[2:] + return name.replace("-", "_") + def set_arg(self, name, value): """Sets the argument""" # see if we want to restrict to existing keys - self._mach_args[name] = value + self._mach_args[self._normalize(name)] = value def get_arg(self, name, default=None): - return self._mach_args.get(name, default) + return self._mach_args.get(self._normalize(name), default) def get_layer(self, name): for layer in self.layers: diff --git a/python/mozperftest/mozperftest/metrics/consoleoutput.py b/python/mozperftest/mozperftest/metrics/consoleoutput.py index 881a44e1a4bc..35ca7e564462 100644 --- a/python/mozperftest/mozperftest/metrics/consoleoutput.py +++ b/python/mozperftest/mozperftest/metrics/consoleoutput.py @@ -26,7 +26,7 @@ class ConsoleOutput(Layer): metadata.get_result(), self.warning, output=self.get_arg("output"), - prefix=self.get_arg("prefix"), + prefix=self.get_arg("perfherder-prefix"), ) res = cm.get_standardized_data( group_name="firefox", transformer="SingleJsonRetriever" @@ -34,7 +34,7 @@ class ConsoleOutput(Layer): _, results = res["file-output"], res["data"] # Filter out unwanted metrics - results = filter_metrics(results, self.get_arg("metrics")) + results = filter_metrics(results, self.get_arg("perfherder-metrics")) if not results: self.warning("No results left after filtering") return metadata diff --git a/python/mozperftest/mozperftest/metrics/perfherder.py b/python/mozperftest/mozperftest/metrics/perfherder.py index ec6116e05782..58282e79525a 100644 --- a/python/mozperftest/mozperftest/metrics/perfherder.py +++ b/python/mozperftest/mozperftest/metrics/perfherder.py @@ -19,12 +19,12 @@ class Perfherder(Layer): "default": False, "help": "Output data in the perfherder format.", }, - "--prefix": { + "--perfherder-prefix": { "type": str, "default": "", "help": "Prefix the output files with this string.", }, - "--metrics": { + "--perfherder-metrics": { "nargs": "*", "default": [], "help": "The metrics that should be retrieved from the data.", @@ -56,7 +56,7 @@ class Perfherder(Layer): metadata.get_result(), self.warning, output=self.get_arg("output"), - prefix=self.get_arg("prefix"), + prefix=self.get_arg("perfherder-prefix"), ) res = cm.get_standardized_data( group_name="firefox", transformer="SingleJsonRetriever" @@ -64,7 +64,7 @@ class Perfherder(Layer): _, results = res["file-output"], res["data"] # Filter out unwanted metrics - results = filter_metrics(results, self.get_arg("metrics")) + results = filter_metrics(results, self.get_arg("perfherder-metrics")) if not results: self.warning("No results left after filtering") return metadata diff --git a/python/mozperftest/mozperftest/system/__init__.py b/python/mozperftest/mozperftest/system/__init__.py index 081a53182371..91efa11ccfb3 100644 --- a/python/mozperftest/mozperftest/system/__init__.py +++ b/python/mozperftest/mozperftest/system/__init__.py @@ -3,10 +3,11 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. from mozperftest.layers import Layers from mozperftest.system.proxy import ProxyRunner +from mozperftest.system.android import AndroidDevice def get_layers(): - return (ProxyRunner,) + return (ProxyRunner, AndroidDevice) def pick_system(env, flavor, mach_cmd): diff --git a/python/mozperftest/mozperftest/system/android.py b/python/mozperftest/mozperftest/system/android.py new file mode 100644 index 000000000000..3b0eb1869641 --- /dev/null +++ b/python/mozperftest/mozperftest/system/android.py @@ -0,0 +1,92 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +import os +from mozdevice import ADBDevice +from mozperftest.layers import Layer + +HERE = os.path.dirname(__file__) + + +class DeviceError(Exception): + pass + + +class AndroidDevice(Layer): + name = "android" + + arguments = { + "--android": { + "action": "store_true", + "default": False, + "help": "Use an android device via ADB", + }, + "--android-intent": {"type": str, "default": None, "help": "Intent to use"}, + "--android-app-name": {"type": str, "default": None, "help": "App name"}, + "--android-activity": {"type": str, "default": None, "help": "Activity to use"}, + "--android-install-apk": { + "nargs": "*", + "default": [], + "help": "APK to install to the device", + }, + } + + def __init__(self, env, mach_cmd): + super(AndroidDevice, self).__init__(env, mach_cmd) + self.android_activity = self.app_name = self.device = None + + def setup(self): + pass + + def _setup_options(self, app_name="org.mozilla.firefox"): + self.app_name = app_name + try: + self.device = ADBDevice(verbose=True) + except AttributeError as e: + self.error("Could not connect to the phone. Is it connected?") + raise DeviceError(str(e)) + + # install APKs + for apk in self.get_arg("android-install-apk"): + self.info("Installing %s" % apk) + self.device.install_app(apk, replace=True) + self.info("Done.") + + if self.android_activity is None: + # guess the activity, given the app + if "fenix" in app_name: + self.android_activity = "org.mozilla.fenix.IntentReceiverActivity" + elif "geckoview_example" in app_name: + self.android_activity = ( + "org.mozilla.geckoview_example.GeckoViewActivity" + ) + self.set_arg("android_activity", self.android_activity) + + # checking that the app is installed + if not self.device.is_app_installed(self.app_name): + raise Exception("%s is not installed" % self.app_name) + + self.info("Android environment:") + self.info("\t- application name: %s" % self.app_name) + self.info("\t- activity: %s" % self.android_activity) + self.info("\t- intent: %s" % self.get_arg("android_intent")) + + def teardown(self): + pass + + def __call__(self, metadata): + android = self.get_arg("android") + app_name = self.get_arg("android-app-name") + if app_name is None: + app_name = self.get_arg("browser-binary") + if app_name is not None and app_name.startswith("org.mozilla.") and not android: + self.set_arg("android", True) + android = True + if not android: + return metadata + if app_name is None: + app_name = "org.mozilla.firefox" + self.set_arg("android-app-name", app_name) + self.metadata = metadata + self._setup_options(app_name) + return metadata diff --git a/python/mozperftest/mozperftest/system/proxy.py b/python/mozperftest/mozperftest/system/proxy.py index 75b60d2d5f52..6a8204d7d9c3 100644 --- a/python/mozperftest/mozperftest/system/proxy.py +++ b/python/mozperftest/mozperftest/system/proxy.py @@ -25,6 +25,9 @@ class ProxyRunner(Layer): pass def __call__(self, metadata): + if not self.get_arg("proxy"): + return metadata + self.metadata = metadata if not self.get_arg("proxy"): return metadata