Bug 1628073 - Rewrite view_gecko_profile.py to open a URL; r=perftest-reviewers,sparky

The current implementation opens the built Firefox in the objdir. This is not
optimal as the built Firefox is not really great for viewing files in. The build
could be broken. With this patch, the profiles will instead be opened in the users
default browser.

Differential Revision: https://phabricator.services.mozilla.com/D70089
This commit is contained in:
Greg Tatum 2020-05-22 13:17:30 +00:00
parent 723f5d5bba
commit 01b425b6d5
6 changed files with 52 additions and 143 deletions

View File

@ -32,7 +32,7 @@ from cmdline import parse_args, CHROMIUM_DISTROS
from logger.logger import RaptorLogger
from manifest import get_raptor_test_list
from signal_handler import SignalHandler
from utils import view_gecko_profile
from utils import view_gecko_profile_from_raptor
from webextension import (
WebExtensionFirefox,
WebExtensionDesktopChrome,
@ -194,7 +194,7 @@ def main(args=sys.argv[1:]):
"Not launching profiler.firefox.com because DISABLE_PROFILE_LAUNCH=1"
)
else:
view_gecko_profile(args.binary)
view_gecko_profile_from_raptor()
if __name__ == "__main__":

View File

@ -60,20 +60,13 @@ def transform_subtest(str_to_transform, subtest_name):
return str_to_transform.replace('{subtest}', subtest_name)
def view_gecko_profile(ffox_bin):
# automatically load the latest talos gecko-profile archive in profiler.firefox.com
def view_gecko_profile_from_raptor():
# automatically load the latest raptor gecko-profile archive in profiler.firefox.com
LOG_GECKO = RaptorLogger(component='raptor-view-gecko-profile')
if sys.platform.startswith('win') and not ffox_bin.endswith(".exe"):
ffox_bin = ffox_bin + ".exe"
if not os.path.exists(ffox_bin):
LOG_GECKO.info("unable to find Firefox bin, cannot launch view-gecko-profile")
return
profile_zip = os.environ.get('RAPTOR_LATEST_GECKO_PROFILE_ARCHIVE', None)
if profile_zip is None or not os.path.exists(profile_zip):
LOG_GECKO.info("No local talos gecko profiles were found so not "
LOG_GECKO.info("No local raptor gecko profiles were found so not "
"launching profiler.firefox.com")
return
@ -91,18 +84,15 @@ def view_gecko_profile(ffox_bin):
command = [sys.executable,
view_gp,
'-b', ffox_bin,
'-p', profile_zip]
LOG_GECKO.info('Auto-loading this profile in perfhtml.io: %s' % profile_zip)
LOG_GECKO.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
# want to crash talos! just dump error and finish up talos as usual
try:
view_profile = subprocess.Popen(command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
view_profile = subprocess.Popen(command)
# that will leave it running in own instance and let talos finish up
except Exception as e:
LOG_GECKO.info("failed to launch view-gecko-profile tool, exeption: %s" % e)

View File

@ -340,22 +340,14 @@ function FindProxyForURL(url, host) {
if os.environ.get('DISABLE_PROFILE_LAUNCH', '0') == '1':
LOG.info("Not launching profiler.firefox.com because DISABLE_PROFILE_LAUNCH=1")
else:
view_gecko_profile(config['browser_path'])
view_gecko_profile_from_talos()
# 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 profiler.firefox.com
if sys.platform.startswith('win') and not ffox_bin.endswith(".exe"):
ffox_bin = ffox_bin + ".exe"
if not os.path.exists(ffox_bin):
LOG.info("unable to find Firefox bin, cannot launch view-gecko-profile")
return
def view_gecko_profile_from_talos():
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 profiler.firefox.com")
@ -375,18 +367,15 @@ def view_gecko_profile(ffox_bin):
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)
LOG.info('Auto-loading this profile in profiler.firefox.com: %s' % profile_zip)
LOG.info(' '.join(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)
view_profile = subprocess.Popen(command)
# 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)

View File

@ -10,12 +10,8 @@ pip install -r requirements.txt
Then the command line:
python view_gecko_profile.py -b <path to browser binary> -p <path to gecko_profile.zip>
python view_gecko_profile.py -p <path to gecko_profile.zip>
i.e. For Firefox:
i.e.:
python view_gecko_profile.py -b "/Users/rwood/mozilla-unified/obj-x86_64-apple-darwin17.4.0/dist/Nightly.app/Contents/MacOS/firefox" -p /Users/rwood/mozilla-unified/testing/mozharness/build/blobber_upload_dir/profile_damp.zip
i.e. For Chrome:
python view_gecko_profile.py -b "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" -p /Users/rwood/mozilla-unified/testing/mozharness/build/blobber_upload_dir/profile_damp.zip
python view_gecko_profile.py -p /Users/rwood/mozilla-unified/testing/mozharness/build/blobber_upload_dir/profile_damp.zip

View File

@ -1,2 +1 @@
mozrunner ~= 7.0
wptserve ~= 1.4.0
mozlog==6.0

View File

@ -5,6 +5,8 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import absolute_import
import sys
import argparse
import json
@ -17,134 +19,70 @@ from threading import Thread
from mozlog import commandline, get_default_logger
from mozlog.commandline import add_logging_group
from mozrunner import runners
from wptserve import server, handlers
import SocketServer
import SimpleHTTPServer
here = os.path.abspath(os.path.dirname(__file__))
class ProfileServingHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
"""Extends the basic SimpleHTTPRequestHandler (which serves a directory
of files) to include request headers required by profiler.firefox.com"""
def end_headers(self):
self.send_header("Access-Control-Allow-Origin", "https://profiler.firefox.com")
SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self)
class ViewGeckoProfile(object):
"""Container class for ViewGeckoProfile"""
def __init__(self, browser_binary, gecko_profile_zip):
def __init__(self, gecko_profile_data_path):
self.log = get_default_logger(component='view-gecko-profile')
self.browser_bin = browser_binary
self.gecko_profile_zip = gecko_profile_zip
self.gecko_profile_dir = os.path.dirname(gecko_profile_zip)
self.perfhtmlio_url = "https://profiler.firefox.com/from-url/"
self.gecko_profile_data_path = gecko_profile_data_path
self.gecko_profile_dir = os.path.dirname(gecko_profile_data_path)
self.profiler_url = "https://profiler.firefox.com/from-url/"
self.httpd = None
self.host = None
self.host = '127.0.0.1'
self.port = None
# Create the runner
self.output_handler = OutputHandler()
process_args = {
'processOutputLine': [self.output_handler],
}
runner_cls = runners['firefox']
self.runner = runner_cls(
browser_binary, profile=None, process_args=process_args)
def start_http_server(self):
self.write_server_headers()
def setup_http_server(self):
# pick a free port
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', 0))
port = sock.getsockname()[1]
self.port = sock.getsockname()[1]
sock.close()
_webserver = '127.0.0.1:%d' % port
self.httpd = self.setup_webserver(_webserver)
self.httpd.start()
os.chdir(self.gecko_profile_dir)
self.httpd = SocketServer.TCPServer((self.host, self.port), ProfileServingHTTPRequestHandler)
self.log.info("File server started at: %s:%s" % (self.host, self.port))
def write_server_headers(self):
# to add specific headers for serving files via wptserve, write out a headers dir file
# see http://wptserve.readthedocs.io/en/latest/handlers.html#file-handlers
self.log.info("writing wptserve headers file")
headers_file = os.path.join(self.gecko_profile_dir, '__dir__.headers')
file = open(headers_file, 'w')
file.write("Access-Control-Allow-Origin: *")
file.close()
self.log.info("wrote wpt headers file: %s" % headers_file)
def setup_webserver(self, webserver):
self.log.info("starting webserver on %r" % webserver)
self.host, self.port = webserver.split(':')
return server.WebTestHttpd(port=int(self.port), doc_root=self.gecko_profile_dir,
routes=[("GET", "*", handlers.file_handler)])
def handle_single_request(self):
self.httpd.handle_request()
def encode_url(self):
# Encode url i.e.: https://profiler.firefox.com/from-url/http... (the profile_zip served locally)
url = 'http://' + self.host + ':' + self.port + '/' + os.path.basename(self.gecko_profile_zip)
url = "http://{}:{}/{}".format(self.host, self.port,
os.path.basename(self.path_to_gecko_profile_data))
self.log.info("raw url is:")
self.log.info(url)
encoded_url = urllib.quote(url, safe='')
self.log.info('encoded url is:')
self.log.info(encoded_url)
self.perfhtmlio_url = self.perfhtmlio_url + encoded_url
self.profiler_url = self.profiler_url + encoded_url
self.log.info('full url is:')
self.log.info(self.perfhtmlio_url)
self.log.info(self.profiler_url)
def start_browser_perfhtml(self):
self.log.info("Starting browser and opening the gecko profile zip in profiler.firefox.com...")
self.runner.cmdargs.append(self.perfhtmlio_url)
self.runner.start()
first_time = int(time.time()) * 1000
proc = self.runner.process_handler
self.output_handler.proc = proc
try:
self.runner.wait(timeout=None)
except Exception:
self.log.info("Failed to start browser")
class OutputHandler(object):
def __init__(self):
self.proc = None
self.kill_thread = Thread(target=self.wait_for_quit)
self.kill_thread.daemon = True
self.log = get_default_logger(component='view_gecko_profile')
def __call__(self, line):
if not line.strip():
return
line = line.decode('utf-8', errors='replace')
try:
data = json.loads(line)
except ValueError:
self.process_output(line)
return
if isinstance(data, dict) and 'action' in data:
self.log.log_raw(data)
else:
self.process_output(json.dumps(data))
def process_output(self, line):
self.log.process_output(self.proc.pid, line)
def wait_for_quit(self, timeout=5):
"""Wait timeout seconds for the process to exit. If it hasn't
exited by then, kill it.
"""
time.sleep(timeout)
if self.proc.poll() is None:
self.proc.kill()
def open_profile_in_browser(self):
# Open the file in the user's preferred browser.
self.log.info("Opening the profile data in profiler.firefox.com...")
import webbrowser
webbrowser.open_new_tab(self.profiler_url)
def create_parser(mach_interface=False):
parser = argparse.ArgumentParser()
add_arg = parser.add_argument
add_arg('-b', '--binary', required=True, dest='binary',
help="path to browser executable")
add_arg('-p', '--profile-zip', required=True, dest='profile_zip',
help="path to the gecko profiles zip file to open in profiler.firefox.com")
@ -155,9 +93,6 @@ def create_parser(mach_interface=False):
def verify_options(parser, args):
ctx = vars(args)
if not os.path.isfile(args.binary):
parser.error("{binary} does not exist!".format(**ctx))
if not os.path.isfile(args.profile_zip):
parser.error("{profile_zip} does not exist!".format(**ctx))
@ -174,12 +109,12 @@ def main(args=sys.argv[1:]):
commandline.setup_logging('view-gecko-profile', args, {'tbpl': sys.stdout})
LOG = get_default_logger(component='view-gecko-profile')
view_gecko_profile = ViewGeckoProfile(args.binary, args.profile_zip)
view_gecko_profile = ViewGeckoProfile(args.profile_zip)
view_gecko_profile.start_http_server()
view_gecko_profile.setup_http_server()
view_gecko_profile.encode_url()
view_gecko_profile.start_browser_perfhtml()
view_gecko_profile.open_profile_in_browser()
view_gecko_profile.handle_single_request()
if __name__ == "__main__":
main()