summaryrefslogtreecommitdiffstats
path: root/build/pgo/profileserver.py
diff options
context:
space:
mode:
Diffstat (limited to 'build/pgo/profileserver.py')
-rwxr-xr-xbuild/pgo/profileserver.py235
1 files changed, 235 insertions, 0 deletions
diff --git a/build/pgo/profileserver.py b/build/pgo/profileserver.py
new file mode 100755
index 0000000000..94f54cbd17
--- /dev/null
+++ b/build/pgo/profileserver.py
@@ -0,0 +1,235 @@
+#!/usr/bin/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 glob
+import json
+import os
+import subprocess
+import sys
+
+import mozcrash
+from mozbuild.base import BinaryNotFoundException, MozbuildObject
+from mozfile import TemporaryDirectory
+from mozhttpd import MozHttpd
+from mozprofile import FirefoxProfile, Preferences
+from mozprofile.permissions import ServerLocations
+from mozrunner import CLI, FirefoxRunner
+from six import string_types
+
+PORT = 8888
+
+PATH_MAPPINGS = {
+ "/webkit/PerformanceTests": "third_party/webkit/PerformanceTests",
+ # It is tempting to map to `testing/talos/talos/tests` instead, to avoid
+ # writing `tests/` in every path, but we can't do that because some files
+ # refer to scripts located in `../..`.
+ "/talos": "testing/talos/talos",
+}
+
+
+def get_crashreports(directory, name=None):
+ rc = 0
+ upload_path = os.environ.get("UPLOAD_PATH")
+ if upload_path:
+ # For automation, log the minidumps with stackwalk and get them moved to
+ # the artifacts directory.
+ fetches_dir = os.environ.get("MOZ_FETCHES_DIR")
+ if not fetches_dir:
+ raise Exception(
+ "Unable to process minidump in automation because "
+ "$MOZ_FETCHES_DIR is not set in the environment"
+ )
+ stackwalk_binary = os.path.join(
+ fetches_dir, "minidump-stackwalk", "minidump-stackwalk"
+ )
+ if sys.platform == "win32":
+ stackwalk_binary += ".exe"
+ minidump_path = os.path.join(directory, "minidumps")
+ rc = mozcrash.check_for_crashes(
+ minidump_path,
+ symbols_path=fetches_dir,
+ stackwalk_binary=stackwalk_binary,
+ dump_save_path=upload_path,
+ test_name=name,
+ )
+ return rc
+
+
+if __name__ == "__main__":
+ cli = CLI()
+ debug_args, interactive = cli.debugger_arguments()
+ runner_args = cli.runner_args()
+
+ build = MozbuildObject.from_environment()
+
+ binary = runner_args.get("binary")
+ if not binary:
+ try:
+ binary = build.get_binary_path(where="staged-package")
+ except BinaryNotFoundException as e:
+ print("{}\n\n{}\n".format(e, e.help()))
+ sys.exit(1)
+ binary = os.path.normpath(os.path.abspath(binary))
+
+ path_mappings = {
+ k: os.path.join(build.topsrcdir, v) for k, v in PATH_MAPPINGS.items()
+ }
+ httpd = MozHttpd(
+ port=PORT,
+ docroot=os.path.join(build.topsrcdir, "build", "pgo"),
+ path_mappings=path_mappings,
+ )
+ httpd.start(block=False)
+
+ locations = ServerLocations()
+ locations.add_host(host="127.0.0.1", port=PORT, options="primary,privileged")
+
+ old_profraw_files = glob.glob("*.profraw")
+ for f in old_profraw_files:
+ os.remove(f)
+
+ with TemporaryDirectory() as profilePath:
+ # TODO: refactor this into mozprofile
+ profile_data_dir = os.path.join(build.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}
+ for k, v in prefs.items():
+ if isinstance(v, string_types):
+ v = v.format(**interpolation)
+ prefs[k] = Preferences.cast(v)
+
+ # Enforce e10s. This isn't in one of the user.js files because those
+ # are shared with android, which doesn't want this on. We can't
+ # interpolate because the formatting code only works for strings,
+ # and this is a bool pref.
+ prefs["browser.tabs.remote.autostart"] = True
+
+ profile = FirefoxProfile(
+ profile=profilePath,
+ preferences=prefs,
+ addons=[
+ os.path.join(
+ build.topsrcdir, "tools", "quitter", "quitter@mozilla.org.xpi"
+ )
+ ],
+ locations=locations,
+ )
+
+ env = os.environ.copy()
+ env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
+ env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1"
+ env["XPCOM_DEBUG_BREAK"] = "warn"
+ # We disable sandboxing to make writing profiling data actually work
+ # Bug 1553850 considers fixing this.
+ env["MOZ_DISABLE_CONTENT_SANDBOX"] = "1"
+ env["MOZ_DISABLE_RDD_SANDBOX"] = "1"
+ env["MOZ_DISABLE_SOCKET_PROCESS_SANDBOX"] = "1"
+ env["MOZ_DISABLE_GPU_SANDBOX"] = "1"
+ env["MOZ_DISABLE_GMP_SANDBOX"] = "1"
+ env["MOZ_DISABLE_NPAPI_SANDBOX"] = "1"
+ env["MOZ_DISABLE_VR_SANDBOX"] = "1"
+
+ # Ensure different pids write to different files
+ env["LLVM_PROFILE_FILE"] = "default_%p_random_%m.profraw"
+
+ # Write to an output file if we're running in automation
+ process_args = {"universal_newlines": True}
+ if "UPLOAD_PATH" in env:
+ process_args["logfile"] = os.path.join(
+ env["UPLOAD_PATH"], "profile-run-1.log"
+ )
+
+ # Run Firefox a first time to initialize its profile
+ runner = FirefoxRunner(
+ profile=profile,
+ binary=binary,
+ cmdargs=["data:text/html,<script>Quitter.quit()</script>"],
+ env=env,
+ process_args=process_args,
+ )
+ runner.start()
+ ret = runner.wait()
+ if ret:
+ print("Firefox exited with code %d during profile initialization" % ret)
+ logfile = process_args.get("logfile")
+ if logfile:
+ print("Firefox output (%s):" % logfile)
+ with open(logfile) as f:
+ print(f.read())
+ httpd.stop()
+ get_crashreports(profilePath, name="Profile initialization")
+ sys.exit(ret)
+
+ jarlog = os.getenv("JARLOG_FILE")
+ if jarlog:
+ env["MOZ_JAR_LOG_FILE"] = os.path.abspath(jarlog)
+ print("jarlog: %s" % env["MOZ_JAR_LOG_FILE"])
+ if os.path.exists(jarlog):
+ os.remove(jarlog)
+
+ if "UPLOAD_PATH" in env:
+ process_args["logfile"] = os.path.join(
+ env["UPLOAD_PATH"], "profile-run-2.log"
+ )
+ cmdargs = ["http://localhost:%d/index.html" % PORT]
+ runner = FirefoxRunner(
+ profile=profile,
+ binary=binary,
+ cmdargs=cmdargs,
+ env=env,
+ process_args=process_args,
+ )
+ runner.start(debug_args=debug_args, interactive=interactive)
+ ret = runner.wait()
+ httpd.stop()
+ if ret:
+ print("Firefox exited with code %d during profiling" % ret)
+ logfile = process_args.get("logfile")
+ if logfile:
+ print("Firefox output (%s):" % logfile)
+ with open(logfile) as f:
+ print(f.read())
+ get_crashreports(profilePath, name="Profiling run")
+ sys.exit(ret)
+
+ # Try to move the crash reports to the artifacts even if Firefox appears
+ # to exit successfully, in case there's a crash that doesn't set the
+ # return code to non-zero for some reason.
+ if get_crashreports(profilePath, name="Firefox exited successfully?") != 0:
+ print("Firefox exited successfully, but produced a crashreport")
+ sys.exit(1)
+
+ llvm_profdata = env.get("LLVM_PROFDATA")
+ if llvm_profdata:
+ profraw_files = glob.glob("*.profraw")
+ if not profraw_files:
+ print(
+ "Could not find profraw files in the current directory: %s"
+ % os.getcwd()
+ )
+ sys.exit(1)
+ merge_cmd = [
+ llvm_profdata,
+ "merge",
+ "-o",
+ "merged.profdata",
+ ] + profraw_files
+ rc = subprocess.call(merge_cmd)
+ if rc != 0:
+ print("INFRA-ERROR: Failed to merge profile data. Corrupt profile?")
+ # exit with TBPL_RETRY
+ sys.exit(4)