2019-03-18 23:53:29 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# 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 copy
|
2019-07-09 19:56:45 +00:00
|
|
|
import glob
|
2022-12-21 14:38:37 +00:00
|
|
|
import json
|
2019-03-18 23:53:29 +00:00
|
|
|
import os
|
|
|
|
import posixpath
|
|
|
|
import subprocess
|
2022-12-21 14:38:37 +00:00
|
|
|
import sys
|
|
|
|
import time
|
2019-03-18 23:53:29 +00:00
|
|
|
|
|
|
|
# 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",
|
2023-11-28 11:32:56 +00:00
|
|
|
"js-input/webkit/PerformanceTests/Speedometer3/index.html?startAutomatically=true",
|
2019-03-18 23:53:29 +00:00
|
|
|
"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
|
|
|
|
"""
|
2020-10-26 18:34:53 +00:00
|
|
|
|
2019-03-18 23:53:29 +00:00
|
|
|
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=[
|
|
|
|
"download",
|
|
|
|
"create-virtualenv",
|
2020-07-16 22:11:36 +00:00
|
|
|
"start-emulator",
|
2019-03-18 23:53:29 +00:00
|
|
|
"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_blob_upload_dir"] = "/builds/worker/artifacts/blobber_upload_dir"
|
2021-11-02 18:56:27 +00:00
|
|
|
work_dir = os.environ.get("MOZ_FETCHES_DIR") or abs_dirs["abs_work_dir"]
|
2024-03-22 20:34:15 +00:00
|
|
|
dirs["abs_xre_dir"] = os.path.join(work_dir, "hostutils")
|
2021-11-02 18:56:27 +00:00
|
|
|
dirs["abs_sdk_dir"] = os.path.join(work_dir, "android-sdk-linux")
|
|
|
|
dirs["abs_avds_dir"] = os.path.join(work_dir, "android-device")
|
|
|
|
dirs["abs_bundletool_path"] = os.path.join(work_dir, "bundletool.jar")
|
2019-03-18 23:53:29 +00:00
|
|
|
|
|
|
|
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",
|
2020-03-05 23:21:56 +00:00
|
|
|
os.path.join(dirs["abs_test_install_dir"], "marionette", "client"),
|
2019-03-18 23:53:29 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
def download(self):
|
|
|
|
"""
|
|
|
|
Download host utilities
|
|
|
|
"""
|
|
|
|
dirs = self.query_abs_dirs()
|
2024-03-22 20:34:15 +00:00
|
|
|
self.xre_path = dirs["abs_xre_dir"]
|
2019-03-18 23:53:29 +00:00
|
|
|
|
|
|
|
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."
|
2021-11-02 18:56:27 +00:00
|
|
|
self.install_android_app(self.installer_path)
|
2019-03-18 23:53:29 +00:00
|
|
|
self.info("Finished installing apps for %s" % self.device_serial)
|
|
|
|
|
|
|
|
def run_tests(self):
|
|
|
|
"""
|
|
|
|
Generate the PGO profile data
|
|
|
|
"""
|
2022-12-21 14:38:37 +00:00
|
|
|
from marionette_driver.marionette import Marionette
|
|
|
|
from mozdevice import ADBDeviceFactory, ADBTimeoutError
|
2019-03-18 23:53:29 +00:00
|
|
|
from mozhttpd import MozHttpd
|
|
|
|
from mozprofile import Preferences
|
|
|
|
from six import string_types
|
|
|
|
|
|
|
|
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()
|
2020-03-05 23:21:56 +00:00
|
|
|
topsrcdir = dirs["abs_src_dir"]
|
2019-03-18 23:53:29 +00:00
|
|
|
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)
|
|
|
|
|
2024-01-19 19:15:28 +00:00
|
|
|
adbdevice = ADBDeviceFactory(adb=adb, device="emulator-5554")
|
|
|
|
|
|
|
|
outputdir = posixpath.join(adbdevice.test_root, "pgo_profile")
|
2019-03-18 23:53:29 +00:00
|
|
|
jarlog = posixpath.join(outputdir, "en-US.log")
|
2019-07-09 19:56:45 +00:00
|
|
|
profdata = posixpath.join(outputdir, "default_%p_random_%m.profraw")
|
2019-03-18 23:53:29 +00:00
|
|
|
|
|
|
|
env = {}
|
|
|
|
env["XPCOM_DEBUG_BREAK"] = "warn"
|
|
|
|
env["MOZ_IN_AUTOMATION"] = "1"
|
|
|
|
env["MOZ_JAR_LOG_FILE"] = jarlog
|
|
|
|
env["LLVM_PROFILE_FILE"] = profdata
|
|
|
|
|
2020-05-16 21:24:32 +00:00
|
|
|
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")
|
|
|
|
|
2020-07-17 20:48:53 +00:00
|
|
|
adbdevice.mkdir(outputdir, parents=True)
|
2019-03-18 23:53:29 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
# Run Fennec a first time to initialize its profile
|
|
|
|
driver = Marionette(
|
|
|
|
app="fennec",
|
|
|
|
package_name=app,
|
|
|
|
adb_path=adb,
|
2019-09-20 15:17:41 +00:00
|
|
|
bin="geckoview-androidTest.apk",
|
2019-03-18 23:53:29 +00:00
|
|
|
prefs=prefs,
|
|
|
|
connect_to_running_emulator=True,
|
|
|
|
startup_timeout=1000,
|
|
|
|
env=env,
|
2020-05-16 21:24:32 +00:00
|
|
|
symbols_path=self.symbols_path,
|
2019-03-18 23:53:29 +00:00
|
|
|
)
|
|
|
|
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
|
2023-11-28 11:32:56 +00:00
|
|
|
if "Speedometer" in page:
|
|
|
|
# The Speedometer[23] test actually runs many tests internally in
|
2019-03-18 23:53:29 +00:00
|
|
|
# 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)
|
|
|
|
|
2024-08-22 10:19:09 +00:00
|
|
|
driver.quit(in_app=True)
|
2019-03-18 23:53:29 +00:00
|
|
|
|
2019-07-09 19:56:45 +00:00
|
|
|
# Pull all the profraw files and en-US.log
|
|
|
|
adbdevice.pull(outputdir, "/builds/worker/workspace/")
|
2019-03-18 23:53:29 +00:00
|
|
|
except ADBTimeoutError:
|
|
|
|
self.fatal(
|
|
|
|
"INFRA-ERROR: Failed with an ADBTimeoutError",
|
|
|
|
EXIT_STATUS_DICT[TBPL_RETRY],
|
|
|
|
)
|
|
|
|
|
2019-07-09 19:56:45 +00:00
|
|
|
profraw_files = glob.glob("/builds/worker/workspace/*.profraw")
|
|
|
|
if not profraw_files:
|
|
|
|
self.fatal("Could not find any profraw files in /builds/worker/workspace")
|
2023-11-07 20:57:53 +00:00
|
|
|
elif len(profraw_files) == 1:
|
|
|
|
self.fatal(
|
|
|
|
"Only found 1 profraw file. Did child processes terminate early?"
|
|
|
|
)
|
2019-07-02 21:51:23 +00:00
|
|
|
merge_cmd = [
|
2019-08-09 05:04:04 +00:00
|
|
|
os.path.join(os.environ["MOZ_FETCHES_DIR"], "clang/bin/llvm-profdata"),
|
2019-07-02 21:51:23 +00:00
|
|
|
"merge",
|
|
|
|
"-o",
|
2019-09-10 21:56:15 +00:00
|
|
|
"/builds/worker/workspace/merged.profdata",
|
2019-07-09 19:56:45 +00:00
|
|
|
] + profraw_files
|
2019-07-02 21:51:23 +00:00
|
|
|
rc = subprocess.call(merge_cmd)
|
|
|
|
if rc != 0:
|
|
|
|
self.fatal(
|
|
|
|
"INFRA-ERROR: Failed to merge profile data. Corrupt profile?",
|
|
|
|
EXIT_STATUS_DICT[TBPL_RETRY],
|
|
|
|
)
|
|
|
|
|
2019-03-18 23:53:29 +00:00
|
|
|
# tarfile doesn't support xz in this version of Python
|
|
|
|
tar_cmd = [
|
|
|
|
"tar",
|
|
|
|
"-acvf",
|
|
|
|
"/builds/worker/artifacts/profdata.tar.xz",
|
|
|
|
"-C",
|
|
|
|
"/builds/worker/workspace",
|
2019-09-10 21:56:15 +00:00
|
|
|
"merged.profdata",
|
2019-03-18 23:53:29 +00:00
|
|
|
"en-US.log",
|
|
|
|
]
|
|
|
|
subprocess.check_call(tar_cmd)
|
|
|
|
|
|
|
|
httpd.stop()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
test = AndroidProfileRun()
|
|
|
|
test.run_and_exit()
|