Bug 1550702 - [raptor] Switch to dumpProfileToFile() for saving the Gecko profile. r=perftest-reviewers,sparky
authorHenrik Skupin <mail@hskupin.info>
Wed, 03 Jul 2019 19:42:40 +0000
changeset 481159 e90b1260461be7c27e99818497a7144504a3d3ac
parent 481158 66be066c4ac045545e800549b313b6d9c0c26780
child 481160 61732637cead0280583e01cc997a03f4843688a0
push id36234
push useropoprus@mozilla.com
push dateThu, 04 Jul 2019 03:31:17 +0000
treeherdermozilla-central@8ad5fbc5b935 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersperftest-reviewers, sparky
bugs1550702
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1550702 - [raptor] Switch to dumpProfileToFile() for saving the Gecko profile. r=perftest-reviewers,sparky Differential Revision: https://phabricator.services.mozilla.com/D33010
testing/raptor/raptor/control_server.py
testing/raptor/raptor/raptor.py
testing/raptor/webext/raptor/runner.js
--- a/testing/raptor/raptor/control_server.py
+++ b/testing/raptor/raptor/control_server.py
@@ -5,30 +5,31 @@
 # control server for raptor performance framework
 # communicates with the raptor browser webextension
 from __future__ import absolute_import
 
 import BaseHTTPServer
 import datetime
 import json
 import os
+import shutil
 import socket
 import threading
 import time
 
 from logger.logger import RaptorLogger
 
 LOG = RaptorLogger(component='raptor-control-server')
 
 here = os.path.abspath(os.path.dirname(__file__))
 
 
 def MakeCustomHandlerClass(results_handler,
                            shutdown_browser,
-                           write_raw_gecko_profile,
+                           handle_gecko_profile,
                            background_app,
                            foreground_app):
 
     class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
         """
         Control server expects messages of the form
         {'type': 'messagetype', 'data':...}
 
@@ -102,17 +103,17 @@ def MakeCustomHandlerClass(results_handl
         """
         wait_after_messages = {}
         waiting_in_state = None
         wait_timeout = 60
 
         def __init__(self, *args, **kwargs):
             self.results_handler = results_handler
             self.shutdown_browser = shutdown_browser
-            self.write_raw_gecko_profile = write_raw_gecko_profile
+            self.handle_gecko_profile = handle_gecko_profile
             self.background_app = background_app
             self.foreground_app = foreground_app
             super(MyHandler, self).__init__(*args, **kwargs)
 
         def log_request(self, code='-', size='-'):
             if code != 200:
                 super(MyHandler, self).log_request(code, size)
 
@@ -170,22 +171,21 @@ def MakeCustomHandlerClass(results_handl
 
             if MyHandler.wait_after_messages.get(wait_key, None) is not None:
                 # If the wait is False, it was continued and we just set it back
                 # to True for the next time. If it was removed by clear, we
                 # leave it alone so it will not cause a wait any more.
                 MyHandler.wait_after_messages[wait_key] = True
 
             if data['type'] == "webext_gecko_profile":
-                # received gecko profiling results
-                _test = str(data['data'][0])
-                _pagecycle = str(data['data'][1])
-                _raw_profile = data['data'][2]
-                LOG.info("received gecko profile for test %s pagecycle %s" % (_test, _pagecycle))
-                self.write_raw_gecko_profile(_test, _pagecycle, _raw_profile)
+                # received file name of the saved gecko profile
+                filename = str(data['data'])
+                LOG.info("received gecko profile filename: {}".format(filename))
+                self.handle_gecko_profile(filename)
+
             elif data['type'] == 'webext_results':
                 LOG.info("received " + data['type'] + ": " + str(data['data']))
                 self.results_handler.add(data['data'])
             elif data['type'] == "webext_raptor-page-timeout":
                 LOG.info("received " + data['type'] + ": " + str(data['data']))
 
                 if len(data['data']) == 2:
                     data['data'].append("")
@@ -277,32 +277,33 @@ class RaptorControlServer():
         self.port = None
         self.results_handler = results_handler
         self.browser_proc = None
         self._finished = False
         self.device = None
         self.app_name = None
         self.gecko_profile_dir = None
         self.debug_mode = debug_mode
+        self.user_profile = None
 
     def start(self):
         config_dir = os.path.join(here, 'tests')
         os.chdir(config_dir)
 
         # pick a free port
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         sock.bind(('', 0))
         self.port = sock.getsockname()[1]
         sock.close()
         server_address = ('', self.port)
 
         server_class = ThreadedHTTPServer
         handler_class = MakeCustomHandlerClass(self.results_handler,
                                                self.shutdown_browser,
-                                               self.write_raw_gecko_profile,
+                                               self.handle_gecko_profile,
                                                self.background_app,
                                                self.foreground_app)
 
         httpd = server_class(server_address, handler_class)
 
         self._server_thread = threading.Thread(target=httpd.serve_forever)
         self._server_thread.setDaemon(True)  # don't hang on exit
         self._server_thread.start()
@@ -320,27 +321,22 @@ class RaptorControlServer():
         if self.device is not None:
             LOG.info("shutting down android app %s" % self.app_name)
         else:
             LOG.info("shutting down browser (pid: %d)" % self.browser_proc.pid)
         self.kill_thread = threading.Thread(target=self.wait_for_quit)
         self.kill_thread.daemon = True
         self.kill_thread.start()
 
-    def write_raw_gecko_profile(self, test, pagecycle, profile):
-        profile_file = '%s_pagecycle_%s.profile' % (test, pagecycle)
-        profile_path = os.path.join(self.gecko_profile_dir, profile_file)
-        LOG.info("writing raw gecko profile to disk: %s" % str(profile_path))
-
-        try:
-            with open(profile_path, 'w') as profile_file:
-                json.dump(profile, profile_file)
-                profile_file.close()
-        except Exception:
-            LOG.critical("Encountered an exception whie writing raw gecko profile to disk")
+    def handle_gecko_profile(self, filename):
+        # Move the stored profile to a location outside the Firefox profile
+        source_path = os.path.join(self.user_profile.profile, "profiler", filename)
+        target_path = os.path.join(self.gecko_profile_dir, filename)
+        shutil.move(source_path, target_path)
+        LOG.info("moved gecko profile to {}".format(target_path))
 
     def is_app_in_background(self):
         # Get the app view state: foreground->False, background->True
         current_focus = self.device.shell_output(
             "dumpsys window windows | grep mCurrentFocus"
         ).strip()
         return self.app_name not in current_focus
 
--- a/testing/raptor/raptor/raptor.py
+++ b/testing/raptor/raptor/raptor.py
@@ -130,21 +130,21 @@ class Raptor(object):
         # if running debug-mode reduce the pause after browser startup
         if self.debug_mode:
             self.post_startup_delay = min(self.post_startup_delay, 3000)
             LOG.info("debug-mode enabled, reducing post-browser startup pause to %d ms"
                      % self.post_startup_delay)
 
         LOG.info("main raptor init, config is: %s" % str(self.config))
 
-        # create results holder
+        # setup the control server
         self.results_handler = RaptorResultsHandler()
+        self.start_control_server()
 
         self.build_browser_profile()
-        self.start_control_server()
 
     @property
     def profile_data_dir(self):
         if 'MOZ_DEVELOPER_REPO_DIR' in os.environ:
             return os.path.join(os.environ['MOZ_DEVELOPER_REPO_DIR'], 'testing', 'profiles')
         if build:
             return os.path.join(build.topsrcdir, 'testing', 'profiles')
         return os.path.join(here, 'profile_data')
@@ -262,18 +262,19 @@ class Raptor(object):
         with open(os.path.join(self.profile_data_dir, 'profiles.json'), 'r') as fh:
             base_profiles = json.load(fh)['raptor']
 
         for profile in base_profiles:
             path = os.path.join(self.profile_data_dir, profile)
             LOG.info("Merging profile: {}".format(path))
             self.profile.merge(path)
 
-        # add profile dir to our config
+        # share the profile dir with the config and the control server
         self.config['local_profile_dir'] = self.profile.profile
+        self.control_server.user_profile = self.profile
 
     def start_control_server(self):
         self.control_server = RaptorControlServer(self.results_handler, self.debug_mode)
         self.control_server.start()
 
         if self.config['enable_control_server_wait']:
             self.control_server_wait_set('webext_status/__raptor_shutdownBrowser')
 
@@ -514,16 +515,17 @@ class RaptorDesktop(Raptor):
             self.runner.env['MOZ_WEBRENDER'] = '0'
 
     def launch_desktop_browser(self, test):
         raise NotImplementedError
 
     def start_runner_proc(self):
         # launch the browser via our previously-created runner
         self.runner.start()
+
         proc = self.runner.process_handler
         self.output_handler.proc = proc
 
         # give our control server the browser process so it can shut it down later
         self.control_server.browser_proc = proc
 
     def run_test(self, test, timeout):
         # tests will be run warm (i.e. NO browser restart between page-cycles)
@@ -660,19 +662,20 @@ class RaptorDesktopFirefox(RaptorDesktop
         self.start_runner_proc()
 
         if self.config['is_release_build'] and test.get('playback') is not None:
             self.enable_non_local_connections()
 
         # if geckoProfile is enabled, initialize it
         if self.config['gecko_profile'] is True:
             self._init_gecko_profiling(test)
-            # tell the control server the gecko_profile dir; the control server will
-            # receive the actual gecko profiles from the web ext and will write them
-            # to disk; then profiles are picked up by gecko_profile.symbolicate
+            # tell the control server the gecko_profile dir; the control server
+            # will receive the filename of the stored gecko profile from the web
+            # extension, and will move it out of the browser user profile to
+            # this directory; where it is picked-up by gecko_profile.symbolicate
             self.control_server.gecko_profile_dir = self.gecko_profiler.gecko_profile_dir
 
 
 class RaptorDesktopChrome(RaptorDesktop):
 
     def setup_chrome_desktop_for_playback(self):
         # if running a pageload test on google chrome, add the cmd line options
         # to turn on the proxy and ignore security certificate errors
--- a/testing/raptor/webext/raptor/runner.js
+++ b/testing/raptor/webext/raptor/runner.js
@@ -355,25 +355,25 @@ async function startGeckoProfiling() {
 }
 
 async function stopGeckoProfiling() {
   postToControlServer("status", "stopping gecko profiling");
   await browser.geckoProfiler.stop();
 }
 
 async function getGeckoProfile() {
-  // get the profile and send to control server
-  postToControlServer("status", "retrieving gecko profile");
-  let arrayBuffer = await browser.geckoProfiler.getProfileAsArrayBuffer();
-  let textDecoder = new TextDecoder();
-  let profile = JSON.parse(textDecoder.decode(arrayBuffer));
-  raptorLog(profile);
-  postToControlServer("gecko_profile", [testName, pageCycle, profile]);
-  // stop the profiler; must stop so it clears before next cycle
+  // trigger saving the gecko profile, and send the file name to the control server
+  postToControlServer("status", "saving gecko profile");
+  let fileName = `${testName}_pagecycle_${pageCycle}.profile`;
+  await browser.geckoProfiler.dumpProfileToFile(fileName);
+  postToControlServer("gecko_profile", fileName);
+
+  // must stop the profiler so it clears the buffer before next cycle
   await stopGeckoProfiling();
+
   // resume if we have more pagecycles left
   if (pageCycle + 1 <= pageCycles) {
     await startGeckoProfiling();
   }
 }
 
 async function nextCycle() {
   pageCycle++;