Bug 1425308 - Make it easier to view talos gecko profiles in perf-html.io when running locally (WIP) draft
authorRob Wood <rwood@mozilla.com>
Tue, 03 Apr 2018 17:08:19 -0400
changeset 776885 c6bd69da3cb6855fd5a7b43ace8824155b2bfa93
parent 776675 bd15a594caa8e4b7e491180f90396c1e33b3bbbd
push id105030
push userrwood@mozilla.com
push dateTue, 03 Apr 2018 21:32:29 +0000
bugs1425308
milestone61.0a1
Bug 1425308 - Make it easier to view talos gecko profiles in perf-html.io when running locally (WIP) MozReview-Commit-ID: IWofHQsFEbf
testing/mozharness/mozharness/mozilla/testing/talos.py
testing/talos/mach_commands.py
testing/talos/talos/cmdline.py
--- 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},
@@ -163,24 +169,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,89 @@ 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_PERF_HTML_AUTO_LAUNCH') == '1':
+            self.info("TALOS_DISABLE_PERF_HTML_AUTO_LAUNCH is set, skipping view gecko profile step")
+            return
+
+        gecko_profiles_out_dir = self.query_abs_dirs()['abs_blob_upload_dir']
+        self.info("Looking for talos gecko profile zip files in: %s" % gecko_profiles_out_dir)
+
+        # get a local talos gecko profile zip
+        local_profile_zip = self.get_local_gecko_profile_zip(gecko_profiles_out_dir)
+        if local_profile_zip is None:
+            # no local gecko profile found; nothing to display
+            self.critical("Couldn't find any gecko profiles in: %s" % gecko_profiles_out_dir)
+            return
+
+        self.info("Profile zip to open is:")
+        self.info(local_profile_zip)
+        # start Firefox with gecko viewer url
+        self._start_perfhtmlio(gecko_profiles_out_dir, local_profile_zip)
+
+    def get_local_gecko_profile_zip(self, gecko_profiles_out_dir):
+        # look in the out dir and find a talos profiles zip file to send to perf-html.io
+        profile_zips_found = []
+        for root, dirs, files in os.walk(gecko_profiles_out_dir):
+            for name in files:
+                if "profile_" in name and ".zip" in name:
+                    profile_zips_found.append(os.path.join(root, name))
+        if len(profile_zips_found) == 0:
+            return None
+        if len(profile_zips_found) == 1:
+            return profile_zips_found[0]
+        # more than one local talos profiles zip, so sort by date to get newest
+        profile_zips_found.sort(key=lambda s: os.path.getmtime(s))
+        return profile_zips_found[-1]
+
+    def _start_perfhtmlio(self, gecko_profiles_out_dir, profile_zip):
+        load_url = "https://perf-html.io/"
+
+        # TODO start a local http server, serve out the talos gecko profile zip file(s)
+
+        # TODO: encode the URL we want to use i.e.
+        # https://perf-html.io/from-url/http... (the profile_zip served locally)
+
+        # 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))
+
+        # Now start Firefox and browse to perf-html.io to load the talos profile zip
+        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))
+
+        # give a few sec for it to startup etc. then leave it running but let talos finish up
+        # (we don't want the local talos run blocked while user is viewing the gecko profile)
+        perfhtml_proc.wait(timeout=3)
+
+        # TODO: How can we have the python localhost server stop when Firefox is shutdown?
+        # OR do we want to block talos while the profile is being displayed in perf-html.io?
+        # then when user closes Firefox then kill the server and finish up?
+
     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,21 @@ 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 run and output the results in $MOZ_UPLOAD_DIR. "
+                 "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 the TALOS_DISABLE_PERF_HTML_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'],
             help="Extension to install while running")
     add_arg('--fast', action='store_true',