summaryrefslogtreecommitdiffstats
path: root/testing/mozharness/scripts/android_emulator_pgo.py
diff options
context:
space:
mode:
Diffstat (limited to 'testing/mozharness/scripts/android_emulator_pgo.py')
-rw-r--r--testing/mozharness/scripts/android_emulator_pgo.py332
1 files changed, 332 insertions, 0 deletions
diff --git a/testing/mozharness/scripts/android_emulator_pgo.py b/testing/mozharness/scripts/android_emulator_pgo.py
new file mode 100644
index 0000000000..3aae63c161
--- /dev/null
+++ b/testing/mozharness/scripts/android_emulator_pgo.py
@@ -0,0 +1,332 @@
+#!/usr/bin/env python
+# ***** BEGIN LICENSE BLOCK *****
+# 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/.
+# ***** END LICENSE BLOCK *****
+
+from __future__ import absolute_import
+import copy
+import json
+import time
+import glob
+import os
+import sys
+import posixpath
+import subprocess
+
+# load modules from parent dir
+sys.path.insert(1, os.path.dirname(sys.path[0]))
+
+from mozharness.base.script import BaseScript, PreScriptAction
+from mozharness.mozilla.automation import EXIT_STATUS_DICT, TBPL_RETRY
+from mozharness.mozilla.mozbase import MozbaseMixin
+from mozharness.mozilla.testing.android import AndroidMixin
+from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options
+
+PAGES = [
+ "js-input/webkit/PerformanceTests/Speedometer/index.html",
+ "blueprint/sample.html",
+ "blueprint/forms.html",
+ "blueprint/grid.html",
+ "blueprint/elements.html",
+ "js-input/3d-thingy.html",
+ "js-input/crypto-otp.html",
+ "js-input/sunspider/3d-cube.html",
+ "js-input/sunspider/3d-morph.html",
+ "js-input/sunspider/3d-raytrace.html",
+ "js-input/sunspider/access-binary-trees.html",
+ "js-input/sunspider/access-fannkuch.html",
+ "js-input/sunspider/access-nbody.html",
+ "js-input/sunspider/access-nsieve.html",
+ "js-input/sunspider/bitops-3bit-bits-in-byte.html",
+ "js-input/sunspider/bitops-bits-in-byte.html",
+ "js-input/sunspider/bitops-bitwise-and.html",
+ "js-input/sunspider/bitops-nsieve-bits.html",
+ "js-input/sunspider/controlflow-recursive.html",
+ "js-input/sunspider/crypto-aes.html",
+ "js-input/sunspider/crypto-md5.html",
+ "js-input/sunspider/crypto-sha1.html",
+ "js-input/sunspider/date-format-tofte.html",
+ "js-input/sunspider/date-format-xparb.html",
+ "js-input/sunspider/math-cordic.html",
+ "js-input/sunspider/math-partial-sums.html",
+ "js-input/sunspider/math-spectral-norm.html",
+ "js-input/sunspider/regexp-dna.html",
+ "js-input/sunspider/string-base64.html",
+ "js-input/sunspider/string-fasta.html",
+ "js-input/sunspider/string-tagcloud.html",
+ "js-input/sunspider/string-unpack-code.html",
+ "js-input/sunspider/string-validate-input.html",
+]
+
+
+class AndroidProfileRun(TestingMixin, BaseScript, MozbaseMixin, AndroidMixin):
+ """
+ Mozharness script to generate an android PGO profile using the emulator
+ """
+
+ config_options = copy.deepcopy(testing_config_options)
+
+ def __init__(self, require_config_file=False):
+ super(AndroidProfileRun, self).__init__(
+ config_options=self.config_options,
+ all_actions=[
+ "setup-avds",
+ "download",
+ "create-virtualenv",
+ "start-emulator",
+ "verify-device",
+ "install",
+ "run-tests",
+ ],
+ require_config_file=require_config_file,
+ config={
+ "virtualenv_modules": [],
+ "virtualenv_requirements": [],
+ "require_test_zip": True,
+ "mozbase_requirements": "mozbase_source_requirements.txt",
+ },
+ )
+
+ # these are necessary since self.config is read only
+ c = self.config
+ self.installer_path = c.get("installer_path")
+ self.device_serial = "emulator-5554"
+
+ def query_abs_dirs(self):
+ if self.abs_dirs:
+ return self.abs_dirs
+ abs_dirs = super(AndroidProfileRun, self).query_abs_dirs()
+ dirs = {}
+
+ dirs["abs_test_install_dir"] = os.path.join(abs_dirs["abs_src_dir"], "testing")
+ dirs["abs_xre_dir"] = os.path.join(abs_dirs["abs_work_dir"], "hostutils")
+ dirs["abs_blob_upload_dir"] = "/builds/worker/artifacts/blobber_upload_dir"
+ dirs["abs_avds_dir"] = os.path.join(abs_dirs["abs_work_dir"], ".android")
+
+ for key in dirs.keys():
+ if key not in abs_dirs:
+ abs_dirs[key] = dirs[key]
+ self.abs_dirs = abs_dirs
+ return self.abs_dirs
+
+ ##########################################
+ # Actions for AndroidProfileRun #
+ ##########################################
+
+ def preflight_install(self):
+ # in the base class, this checks for mozinstall, but we don't use it
+ pass
+
+ @PreScriptAction("create-virtualenv")
+ def pre_create_virtualenv(self, action):
+ dirs = self.query_abs_dirs()
+ self.register_virtualenv_module(
+ "marionette",
+ os.path.join(dirs["abs_test_install_dir"], "marionette", "client"),
+ )
+
+ def download(self):
+ """
+ Download host utilities
+ """
+ dirs = self.query_abs_dirs()
+ self.xre_path = self.download_hostutils(dirs["abs_xre_dir"])
+
+ def install(self):
+ """
+ Install APKs on the device.
+ """
+ assert (
+ self.installer_path is not None
+ ), "Either add installer_path to the config or use --installer-path."
+ self.install_apk(self.installer_path)
+ self.info("Finished installing apps for %s" % self.device_serial)
+
+ def run_tests(self):
+ """
+ Generate the PGO profile data
+ """
+ from mozhttpd import MozHttpd
+ from mozprofile import Preferences
+ from mozdevice import ADBDeviceFactory, ADBTimeoutError
+ from six import string_types
+ from marionette_driver.marionette import Marionette
+
+ app = self.query_package_name()
+
+ IP = "10.0.2.2"
+ PORT = 8888
+
+ PATH_MAPPINGS = {
+ "/js-input/webkit/PerformanceTests": "third_party/webkit/PerformanceTests",
+ }
+
+ dirs = self.query_abs_dirs()
+ topsrcdir = dirs["abs_src_dir"]
+ adb = self.query_exe("adb")
+
+ path_mappings = {
+ k: os.path.join(topsrcdir, v) for k, v in PATH_MAPPINGS.items()
+ }
+ httpd = MozHttpd(
+ port=PORT,
+ docroot=os.path.join(topsrcdir, "build", "pgo"),
+ path_mappings=path_mappings,
+ )
+ httpd.start(block=False)
+
+ profile_data_dir = os.path.join(topsrcdir, "testing", "profiles")
+ with open(os.path.join(profile_data_dir, "profiles.json"), "r") as fh:
+ base_profiles = json.load(fh)["profileserver"]
+
+ prefpaths = [
+ os.path.join(profile_data_dir, profile, "user.js")
+ for profile in base_profiles
+ ]
+
+ prefs = {}
+ for path in prefpaths:
+ prefs.update(Preferences.read_prefs(path))
+
+ interpolation = {"server": "%s:%d" % httpd.httpd.server_address, "OOP": "false"}
+ for k, v in prefs.items():
+ if isinstance(v, string_types):
+ v = v.format(**interpolation)
+ prefs[k] = Preferences.cast(v)
+
+ outputdir = self.config.get("output_directory", "/sdcard/pgo_profile")
+ jarlog = posixpath.join(outputdir, "en-US.log")
+ profdata = posixpath.join(outputdir, "default_%p_random_%m.profraw")
+
+ env = {}
+ env["XPCOM_DEBUG_BREAK"] = "warn"
+ env["MOZ_IN_AUTOMATION"] = "1"
+ env["MOZ_JAR_LOG_FILE"] = jarlog
+ env["LLVM_PROFILE_FILE"] = profdata
+
+ if self.query_minidump_stackwalk():
+ os.environ["MINIDUMP_STACKWALK"] = self.minidump_stackwalk_path
+ os.environ["MINIDUMP_SAVE_PATH"] = self.query_abs_dirs()["abs_blob_upload_dir"]
+ if not self.symbols_path:
+ self.symbols_path = os.environ.get("MOZ_FETCHES_DIR")
+
+ # Force test_root to be on the sdcard for android pgo
+ # builds which fail for Android 4.3 when profiles are located
+ # in /data/local/tmp/test_root with
+ # E AndroidRuntime: FATAL EXCEPTION: Gecko
+ # E AndroidRuntime: java.lang.IllegalArgumentException: \
+ # Profile directory must be writable if specified: /data/local/tmp/test_root/profile
+ # This occurs when .can-write-sentinel is written to
+ # the profile in
+ # mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfile.java.
+ # This is not a problem on later versions of Android. This
+ # over-ride of test_root should be removed when Android 4.3 is no
+ # longer supported.
+ sdcard_test_root = "/sdcard/test_root"
+ adbdevice = ADBDeviceFactory(
+ adb=adb, device="emulator-5554", test_root=sdcard_test_root
+ )
+ if adbdevice.test_root != sdcard_test_root:
+ # If the test_root was previously set and shared
+ # the initializer will not have updated the shared
+ # value. Force it to match the sdcard_test_root.
+ adbdevice.test_root = sdcard_test_root
+ adbdevice.mkdir(outputdir, parents=True)
+
+ try:
+ # Run Fennec a first time to initialize its profile
+ driver = Marionette(
+ app="fennec",
+ package_name=app,
+ adb_path=adb,
+ bin="geckoview-androidTest.apk",
+ prefs=prefs,
+ connect_to_running_emulator=True,
+ startup_timeout=1000,
+ env=env,
+ symbols_path=self.symbols_path,
+ )
+ driver.start_session()
+
+ # Now generate the profile and wait for it to complete
+ for page in PAGES:
+ driver.navigate("http://%s:%d/%s" % (IP, PORT, page))
+ timeout = 2
+ if "Speedometer/index.html" in page:
+ # The Speedometer test actually runs many tests internally in
+ # javascript, so it needs extra time to run through them. The
+ # emulator doesn't get very far through the whole suite, but
+ # this extra time at least lets some of them process.
+ timeout = 360
+ time.sleep(timeout)
+
+ driver.set_context("chrome")
+ driver.execute_script(
+ """
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Components.interfaces.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null);
+ return cancelQuit.data;
+ """
+ )
+ driver.execute_script(
+ """
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit)
+ """
+ )
+
+ # There is a delay between execute_script() returning and the profile data
+ # actually getting written out, so poll the device until we get a profile.
+ for i in range(50):
+ if not adbdevice.process_exist(app):
+ break
+ time.sleep(2)
+ else:
+ raise Exception("Android App (%s) never quit" % app)
+
+ # Pull all the profraw files and en-US.log
+ adbdevice.pull(outputdir, "/builds/worker/workspace/")
+ except ADBTimeoutError:
+ self.fatal(
+ "INFRA-ERROR: Failed with an ADBTimeoutError",
+ EXIT_STATUS_DICT[TBPL_RETRY],
+ )
+
+ profraw_files = glob.glob("/builds/worker/workspace/*.profraw")
+ if not profraw_files:
+ self.fatal("Could not find any profraw files in /builds/worker/workspace")
+ merge_cmd = [
+ os.path.join(os.environ["MOZ_FETCHES_DIR"], "clang/bin/llvm-profdata"),
+ "merge",
+ "-o",
+ "/builds/worker/workspace/merged.profdata",
+ ] + profraw_files
+ rc = subprocess.call(merge_cmd)
+ if rc != 0:
+ self.fatal(
+ "INFRA-ERROR: Failed to merge profile data. Corrupt profile?",
+ EXIT_STATUS_DICT[TBPL_RETRY],
+ )
+
+ # tarfile doesn't support xz in this version of Python
+ tar_cmd = [
+ "tar",
+ "-acvf",
+ "/builds/worker/artifacts/profdata.tar.xz",
+ "-C",
+ "/builds/worker/workspace",
+ "merged.profdata",
+ "en-US.log",
+ ]
+ subprocess.check_call(tar_cmd)
+
+ httpd.stop()
+
+
+if __name__ == "__main__":
+ test = AndroidProfileRun()
+ test.run_and_exit()