Bug 1425308 - Make it easier to view talos gecko profiles in perf-html.io when running locally
MozReview-Commit-ID: 4F3RQcUXfMg
--- a/testing/mozharness/mozharness/mozilla/testing/talos.py
+++ b/testing/mozharness/mozharness/mozilla/testing/talos.py
@@ -10,35 +10,41 @@ run talos tests in a virtualenv
import os
import sys
import pprint
import copy
import re
import shutil
import subprocess
+import tempfile
import json
+import mozinfo
+import mozrunner
import mozharness
+
from mozharness.base.config import parse_config_file
from mozharness.base.errors import PythonErrorList
from mozharness.base.log import OutputParser, DEBUG, ERROR, CRITICAL
from mozharness.base.log import INFO, WARNING
from mozharness.base.python import Python3Virtualenv
from mozharness.mozilla.blob_upload import BlobUploadMixin, blobupload_config_options
from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options
from mozharness.base.vcs.vcsbase import MercurialScript
from mozharness.mozilla.testing.errors import TinderBoxPrintRe
from mozharness.mozilla.buildbot import TBPL_SUCCESS, TBPL_WORST_LEVEL_TUPLE
from mozharness.mozilla.buildbot import TBPL_RETRY, TBPL_FAILURE, TBPL_WARNING
from mozharness.mozilla.tooltool import TooltoolMixin
from mozharness.mozilla.testing.codecoverage import (
CodeCoverageMixin,
code_coverage_config_options
)
+from mozprocess import processhandler
+from mozprofile.profile import Profile
scripts_path = os.path.abspath(os.path.dirname(os.path.dirname(mozharness.__file__)))
external_tools_path = os.path.join(scripts_path, 'external_tools')
TalosErrorList = PythonErrorList + [
{'regex': re.compile(r'''run-as: Package '.*' is unknown'''), 'level': DEBUG},
{'substr': r'''FAIL: Graph server unreachable''', 'level': CRITICAL},
@@ -175,24 +181,26 @@ class Talos(TestingMixin, MercurialScrip
kwargs.setdefault('all_actions', ['clobber',
'read-buildbot-config',
'download-and-extract',
'populate-webroot',
'create-virtualenv',
'install',
'setup-mitmproxy',
'run-tests',
+ 'start-gecko-profile-viewer',
])
kwargs.setdefault('default_actions', ['clobber',
'download-and-extract',
'populate-webroot',
'create-virtualenv',
'install',
'setup-mitmproxy',
'run-tests',
+ 'start-gecko-profile-viewer',
])
kwargs.setdefault('config', {})
super(Talos, self).__init__(**kwargs)
self.workdir = self.query_abs_dirs()['abs_work_dir'] # convenience
self.run_local = self.config.get('run_local')
self.installer_url = self.config.get("installer_url")
@@ -748,16 +756,71 @@ class Talos(TestingMixin, MercurialScrip
if not self.run_local:
# copy results to upload dir so they are included as an artifact
dest = os.path.join(env['MOZ_UPLOAD_DIR'], 'perfherder-data.json')
self._artifact_perf_data(dest)
self.buildbot_status(parser.worst_tbpl_status,
level=parser.worst_log_level)
+ def start_gecko_profile_viewer(self):
+ # if running locally and gecko profiling is on, start a new
+ # webserver and serve the talos gecko profiles output folder
+ if not self.run_local or ("--geckoProfile" not in self.config.get('talos_extra_options')):
+ self.info("Skipping view gecko profile step")
+ return
+
+ # if the user has set the 'TALOS_DISABLE_PROF_AUTO_VIEW=1' env var then don't launch the viewer
+ if os.getenv('TALOS_DISABLE_PERFHTML_AUTO_LAUNCH') == '1':
+ self.info("TALOS_DISABLE_PERFHTML_AUTO_LAUNCH is set, skipping view gecko profile step")
+ return
+
+ gecko_profiles_out_dir = os.path.join(self.repo_path,
+ 'testing/talos/talos/gecko_profiles_out')
+
+ # make sure some gecko profiles exist
+ if not self._local_gecko_profiles_exist(gecko_profiles_out_dir):
+ # no local gecko profile found; nothing to display
+ self.critical("Couldn't find any gecko profiles in: %s" % gecko_profiles_out_dir)
+ return
+
+ # start Firefox with gecko viewer url
+ self._start_perfhtmlio(gecko_profiles_out_dir)
+
+ def _local_gecko_profiles_exist(self, gecko_profiles_out_dir):
+ for root, dirs, files in os.walk(gecko_profiles_out_dir):
+ for name in files:
+ self.info(name)
+ if ".profile" in name:
+ return True
+ return False
+
+ def _start_perfhtmlio(self, gecko_profiles_out_dir):
+ load_url = "https://perf-html.io/"
+
+ # make a new Firefox profile (in caes Firefox is already running elsewhere)
+ profile_dir = os.path.join(tempfile.mkdtemp(), 'profile')
+ profile = Profile(profile_dir)
+
+ # Firefox cmd line
+ command = [self.config['binary_path'], '-profile', profile_dir, load_url]
+ self.info(' '.join(command))
+ self.info("Starting Firefox and loading %s" % load_url)
+ perfhtml_proc = processhandler.ProcessHandler(command)
+ perfhtml_proc.run()
+
+ self.info("Started prcoess %d: %s" % (perfhtml_proc.pid, command))
+ self.info("*" * 75)
+ self.info(" In perf-html.io, click the 'Browse' button and navigate to:")
+ self.info(" %s" % gecko_profiles_out_dir)
+ self.info("*" * 75)
+
+ # give a few sec for it to startup etc. then leave it running but let talos finish up
+ perfhtml_proc.wait(timeout=3)
+
def fetch_python3(self):
manifest_file = os.path.join(
self.talos_path,
'talos',
'mitmproxy',
self.config.get('python3_manifest')[self.platform_name()])
output_dir = self.query_abs_dirs()['abs_work_dir']
# Slowdown: The unzipped Python3 installation gets deleted every time
--- a/testing/talos/mach_commands.py
+++ b/testing/talos/mach_commands.py
@@ -49,16 +49,17 @@ class TalosRunner(MozbuildObject):
def make_config(self):
default_actions = ['populate-webroot']
if not os.path.exists(self.virtualenv_path):
default_actions.append('create-virtualenv')
default_actions.extend([
'setup-mitmproxy',
'run-tests',
+ 'start-gecko-profile-viewer',
])
self.config = {
'run_local': True,
'talos_json': self.talos_json,
'binary_path': self.binary_path,
'repo_path': self.topsrcdir,
'obj_path': self.topobjdir,
'log_name': 'talos',
--- a/testing/talos/talos/cmdline.py
+++ b/testing/talos/talos/cmdline.py
@@ -76,27 +76,23 @@ def create_parser(mach_interface=False):
add_arg("--mozAfterPaint", action='store_true', dest="tpmozafterpaint",
help="wait for MozAfterPaint event before recording the time")
add_arg("--firstPaint", action='store_true', dest="firstpaint",
help="Also report the first paint value in supported tests")
add_arg("--useHero", action='store_true', dest="tphero",
help="use Hero elementtiming attribute to record the time")
add_arg("--userReady", action='store_true', dest="userready",
help="Also report the user ready value in supported tests")
- add_arg('--spsProfile', action="store_true", dest="gecko_profile",
- help="(Deprecated - Use --geckoProfile instead.) Profile the "
- "run and output the results in $MOZ_UPLOAD_DIR.")
- add_arg('--spsProfileInterval', dest='gecko_profile_interval', type=float,
- help="(Deprecated - Use --geckoProfileInterval instead.) How "
- "frequently to take samples (ms)")
- add_arg('--spsProfileEntries', dest="gecko_profile_entries", type=int,
- help="(Deprecated - Use --geckoProfileEntries instead.) How "
- "many samples to take with the profiler")
add_arg('--geckoProfile', action="store_true", dest="gecko_profile",
- help="Profile the run and output the results in $MOZ_UPLOAD_DIR.")
+ help="Profile the talos run and output the results in $MOZ_UPLOAD_DIR. "
+ "When running talos locally, profiles will also be available at: "
+ "'/testing/talos/talos/gecko_profiles_out'. "
+ "After talos is finished, Perf-html.io will be launched in Firefox so you can "
+ "analyze the local profiles. To disable auto-launching of Perf-html.io, set "
+ "the TALOS_DISABLE_PERFHTML_AUTO_LAUNCH=1 env var.")
add_arg('--geckoProfileInterval', dest='gecko_profile_interval', type=float,
help="How frequently to take samples (ms)")
add_arg('--geckoProfileEntries', dest="gecko_profile_entries", type=int,
help="How many samples to take with the profiler")
add_arg('--extension', dest='extensions', action='append',
default=['${talos}/talos-powers',
'${talos}/pageloader'],
help="Extension to install while running")
--- a/testing/talos/talos/gecko_profile.py
+++ b/testing/talos/talos/gecko_profile.py
@@ -66,16 +66,18 @@ class GeckoProfile(object):
"gecko_profile_threads": gecko_profile_threads
}
# when running locally, profiles will be zipped up, and also stored in a
# talos gecko profiles output folder, so they can easily be viewed later
if self.browser_config['develop']:
if os.path.exists(self.browser_config['gecko_profiles_out_dir']):
mozfile.remove(self.browser_config['gecko_profiles_out_dir'])
+ # make path available as env var (used later if automatically displaying profile)
+ os.environ["GECKO_PROFILES_OUT_DIR"] = self.browser_config['gecko_profiles_out_dir']
def option(self, name):
return self.profiling_info["gecko_profile_" + name]
def update_env(self, env):
"""
update the given env to update some env vars if required.
"""