Bug 1425308 - Automatically view local talos gecko profile in perf-html.io; r?jmaher draft
authorRob Wood <rwood@mozilla.com>
Mon, 28 May 2018 11:51:02 -0400
changeset 801123 e50ca8e0e07daa317f0786dd0486ae244c25da3e
parent 801090 35a9ad46b395f60f04eb9abb3522781464f38e9d
push id111580
push userrwood@mozilla.com
push dateTue, 29 May 2018 19:20:59 +0000
reviewersjmaher
bugs1425308
milestone62.0a1
Bug 1425308 - Automatically view local talos gecko profile in perf-html.io; r?jmaher When running talos locally with --geckoProfile set, the latest gecko-profile archive will automatically be loaded in perf-html.io using the view-gecko-profile tool. To disable this automatic perf-html.io launching, set TALOS_DISABLE_PROFILE_LAUNCH=1. MozReview-Commit-ID: 8tpLnsPAXD9
testing/mozharness/mozharness/mozilla/testing/talos.py
testing/talos/talos/cmdline.py
testing/talos/talos/gecko_profile.py
testing/talos/talos/run_tests.py
--- a/testing/mozharness/mozharness/mozilla/testing/talos.py
+++ b/testing/mozharness/mozharness/mozilla/testing/talos.py
@@ -597,16 +597,25 @@ class Talos(TestingMixin, MercurialScrip
         )
         super(Talos, self).create_virtualenv()
         # talos in harness requires what else is
         # listed in talos requirements.txt file.
         self.install_module(
             requirements=[os.path.join(self.talos_path,
                                        'requirements.txt')]
         )
+        # if running locally and gecko profiing is on, we will be using the
+        # view-gecko-profile tool which has its own requirements too
+        if self.gecko_profile and self.run_local:
+            tools = os.path.join(self.config['repo_path'], 'testing', 'tools')
+            view_gecko_profile_req = os.path.join(tools,
+                                              'view_gecko_profile',
+                                              'requirements.txt')
+            self.info("installing requirements for the view-gecko-profile tool")
+            self.install_module(requirements=[view_gecko_profile_req])
 
     def _validate_treeherder_data(self, parser):
         # late import is required, because install is done in create_virtualenv
         import jsonschema
 
         if len(parser.found_perf_data) != 1:
             self.critical("PERFHERDER_DATA was seen %d times, expected 1."
                           % len(parser.found_perf_data))
--- a/testing/talos/talos/cmdline.py
+++ b/testing/talos/talos/cmdline.py
@@ -86,17 +86,20 @@ def create_parser(mach_interface=False):
                  "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 TALOS_DISABLE_PROFILE_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',
--- a/testing/talos/talos/gecko_profile.py
+++ b/testing/talos/talos/gecko_profile.py
@@ -199,16 +199,19 @@ class GeckoProfile(object):
                     arc.write(profile_path, path_in_zip)
                 except Exception:
                     LOG.exception(
                         "Failed to copy profile {0} as {1} to"
                         " archive {2}".format(profile_path,
                                               path_in_zip,
                                               self.profile_arcname)
                     )
+            # save the latest gecko profile archive to an env var, so later on
+            # it can be viewed automatically via the view-gecko-profile tool
+            os.environ['TALOS_LATEST_GECKO_PROFILE_ARCHIVE'] = self.profile_arcname
 
     def clean(self):
         """
         Clean up temp folders created with the instance creation.
         """
         mozfile.remove(self.option('dir'))
         if self.cleanup:
             for symbol_path in self.symbol_paths.values():
--- a/testing/talos/talos/run_tests.py
+++ b/testing/talos/talos/run_tests.py
@@ -2,16 +2,17 @@
 
 # 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/.
 from __future__ import absolute_import, print_function
 
 import copy
 import os
+import subprocess
 import sys
 import time
 import traceback
 import urllib
 
 import mozhttpd
 import mozversion
 import utils
@@ -323,21 +324,79 @@ def run_tests(config, browser_config):
 
     # output results
     if results_urls and not browser_config['no_upload_results']:
         talos_results.output(results_urls)
         if browser_config['develop'] or config['gecko_profile']:
             print("Thanks for running Talos locally. Results are in %s"
                   % (results_urls['output_urls']))
 
+    # when running talos locally with gecko profiling on, use the view-gecko-profile
+    # tool to automatically load the latest gecko profile in perf-html.io
+    if config['gecko_profile'] and browser_config['develop']:
+        if os.environ.get('TALOS_DISABLE_PROFILE_LAUNCH', '0') == '1':
+            LOG.info("Not launching perf-html.io because TALOS_DISABLE_PROFILE_LAUNCH=1")
+        else:
+            view_gecko_profile(config['browser_path'])
+
     # we will stop running tests on a failed test, or we will return 0 for
     # green
     return 0
 
 
+def view_gecko_profile(ffox_bin):
+    # automatically load the latest talos gecko-profile archive in perf-html.io
+    if not os.path.exists(ffox_bin):
+        LOG.info("unable to find Firefox bin, cannot launch view-gecko-profile")
+        return
+
+    profile_zip = os.environ.get('TALOS_LATEST_GECKO_PROFILE_ARCHIVE', None)
+    if profile_zip is None or not os.path.exists(profile_zip):
+        LOG.info("No local talos gecko profiles were found so not launching perf-html.io")
+        return
+
+    # need the view-gecko-profile tool, it's in repo/testing/tools
+    repo_dir = os.environ.get('MOZ_DEVELOPER_REPO_DIR', None)
+    if repo_dir is None:
+        LOG.info("unable to find MOZ_DEVELOPER_REPO_DIR, can't launch view-gecko-profile")
+        return
+
+    view_gp = os.path.join(repo_dir, 'testing', 'tools',
+                           'view_gecko_profile', 'view_gecko_profile.py')
+    if not os.path.exists(view_gp):
+        LOG.info("unable to find the view-gecko-profile tool, cannot launch it")
+        return
+
+    command = ['python',
+               view_gp,
+               '-b', ffox_bin,
+               '-p', profile_zip]
+
+    LOG.info('Auto-loading this profile in perfhtml.io: %s' % profile_zip)
+    LOG.info(command)
+
+    # if the view-gecko-profile tool fails to launch for some reason, we don't
+    # want to crash talos! just dump error and finsh up talos as usual
+    try:
+        view_profile = subprocess.Popen(command,
+                                        stdout=subprocess.PIPE,
+                                        stderr=subprocess.PIPE)
+        # that will leave it running in own instance and let talos finish up
+    except Exception as e:
+        LOG.info("failed to launch view-gecko-profile tool, exeption: %s" % e)
+        return
+
+    time.sleep(5)
+    ret = view_profile.poll()
+    if ret is None:
+        LOG.info("view-gecko-profile successfully started as pid %d" % view_profile.pid)
+    else:
+        LOG.error('view-gecko-profile process failed to start, poll returned: %s' % ret)
+
+
 def make_comparison_result(base_and_reference_results):
     ''' Receive a test result object meant to be used as a base vs reference test. The result
     object will have one test with two subtests; instead of traditional subtests we want to
     treat them as separate tests, comparing them together and reporting the comparison results.
 
     Results with multiple pages used as subtests would look like this normally, with the overall
     result value being the mean of the pages/subtests: