summaryrefslogtreecommitdiffstats
path: root/python/mozperftest/mozperftest/system/proxy.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/mozperftest/mozperftest/system/proxy.py')
-rw-r--r--python/mozperftest/mozperftest/system/proxy.py232
1 files changed, 232 insertions, 0 deletions
diff --git a/python/mozperftest/mozperftest/system/proxy.py b/python/mozperftest/mozperftest/system/proxy.py
new file mode 100644
index 0000000000..d43a65a0eb
--- /dev/null
+++ b/python/mozperftest/mozperftest/system/proxy.py
@@ -0,0 +1,232 @@
+# 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/.
+import json
+import os
+import pathlib
+import re
+import signal
+import tempfile
+import threading
+
+from mozdevice import ADBDevice
+from mozlog import get_proxy_logger
+from mozprocess import ProcessHandler
+
+from mozperftest.layers import Layer
+from mozperftest.utils import ON_TRY, download_file, get_output_dir, install_package
+
+LOG = get_proxy_logger(component="proxy")
+HERE = os.path.dirname(__file__)
+
+
+class OutputHandler(object):
+ def __init__(self):
+ self.proc = None
+ self.port = None
+ self.port_event = threading.Event()
+
+ def __call__(self, line):
+ line = line.strip()
+ if not line:
+ 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:
+ # Retrieve the port number for the proxy server from the logs of
+ # our subprocess.
+ m = re.match(r"Proxy running on port (\d+)", data.get("message", ""))
+ if m:
+ self.port = int(m.group(1))
+ self.port_event.set()
+ LOG.log_raw(data)
+ else:
+ self.process_output(json.dumps(data))
+
+ def finished(self):
+ self.port_event.set()
+
+ def process_output(self, line):
+ if self.proc is None:
+ LOG.process_output(line)
+ else:
+ LOG.process_output(self.proc.pid, line)
+
+ def wait_for_port(self):
+ self.port_event.wait()
+ return self.port
+
+
+class ProxyRunner(Layer):
+ """Use a proxy"""
+
+ name = "proxy"
+ activated = False
+
+ arguments = {
+ "mode": {
+ "type": str,
+ "choices": ["record", "playback"],
+ "help": "Proxy server mode. Use `playback` to replay from the provided file(s). "
+ "Use `record` to generate a new recording at the path specified by `--file`. "
+ "playback - replay from provided file. "
+ "record - generate a new recording at the specified path.",
+ },
+ "file": {
+ "type": str,
+ "nargs": "+",
+ "help": "The playback files to replay, or the file that a recording will be saved to. "
+ "For playback, it can be any combination of the following: zip file, manifest file, "
+ "or a URL to zip/manifest file. "
+ "For recording, it's a zip fle.",
+ },
+ "perftest-page": {
+ "type": str,
+ "default": None,
+ "help": "This option can be used to specify a single test to record rather than "
+ "having to continuously modify the pageload_sites.json. This flag should only be "
+ "used by the perftest team and selects items from "
+ "`testing/performance/pageload_sites.json` based on the name field. Note that "
+ "the login fields won't be checked with a request such as this (i.e. it overrides "
+ "those settings).",
+ },
+ }
+
+ def __init__(self, env, mach_cmd):
+ super(ProxyRunner, self).__init__(env, mach_cmd)
+ self.proxy = None
+ self.tmpdir = None
+
+ def setup(self):
+ try:
+ import mozproxy # noqa: F401
+ except ImportError:
+ # Install mozproxy and its vendored deps.
+ mozbase = pathlib.Path(self.mach_cmd.topsrcdir, "testing", "mozbase")
+ mozproxy_deps = ["mozinfo", "mozlog", "mozproxy"]
+ for i in mozproxy_deps:
+ install_package(
+ self.mach_cmd.virtualenv_manager, pathlib.Path(mozbase, i)
+ )
+
+ # set MOZ_HOST_BIN to find cerutil. Required to set certifcates on android
+ os.environ["MOZ_HOST_BIN"] = self.mach_cmd.bindir
+
+ def run(self, metadata):
+ self.metadata = metadata
+ replay_file = self.get_arg("file")
+
+ # Check if we have a replay file
+ if replay_file is None:
+ raise ValueError("Proxy file not provided!!")
+
+ if replay_file is not None and replay_file.startswith("http"):
+ self.tmpdir = tempfile.TemporaryDirectory()
+ target = pathlib.Path(self.tmpdir.name, "recording.zip")
+ self.info("Downloading %s" % replay_file)
+ download_file(replay_file, target)
+ replay_file = target
+
+ self.info("Setting up the proxy")
+
+ command = [
+ self.mach_cmd.virtualenv_manager.python_path,
+ "-m",
+ "mozproxy.driver",
+ "--topsrcdir=" + self.mach_cmd.topsrcdir,
+ "--objdir=" + self.mach_cmd.topobjdir,
+ "--profiledir=" + self.get_arg("profile-directory"),
+ ]
+
+ if not ON_TRY:
+ command.extend(["--local"])
+
+ if metadata.flavor == "mobile-browser":
+ command.extend(["--tool=%s" % "mitmproxy-android"])
+ command.extend(["--binary=android"])
+ else:
+ command.extend(["--tool=%s" % "mitmproxy"])
+ # XXX See bug 1712337, we need a single point where we can get the binary used from
+ # this is required to make it work localy
+ binary = self.get_arg("browsertime-binary")
+ if binary is None:
+ binary = self.mach_cmd.get_binary_path()
+ command.extend(["--binary=%s" % binary])
+
+ if self.get_arg("mode") == "record":
+ output = self.get_arg("output")
+ if output is None:
+ output = pathlib.Path(self.mach_cmd.topsrcdir, "artifacts")
+ results_dir = get_output_dir(output)
+
+ command.extend(["--mode", "record"])
+ command.append(str(pathlib.Path(results_dir, replay_file)))
+ elif self.get_arg("mode") == "playback":
+ command.extend(["--mode", "playback"])
+ command.append(str(replay_file))
+ else:
+ raise ValueError("Proxy mode not provided please provide proxy mode")
+
+ inject_deterministic = self.get_arg("deterministic")
+ if inject_deterministic:
+ command.extend(["--deterministic"])
+
+ print(" ".join(command))
+ self.output_handler = OutputHandler()
+ self.proxy = ProcessHandler(
+ command,
+ processOutputLine=self.output_handler,
+ onFinish=self.output_handler.finished,
+ )
+ self.output_handler.proc = self.proxy
+ self.proxy.run()
+
+ # Wait until we've retrieved the proxy server's port number so we can
+ # configure the browser properly.
+ port = self.output_handler.wait_for_port()
+ if port is None:
+ raise ValueError("Unable to retrieve the port number from mozproxy")
+ self.info("Received port number %s from mozproxy" % port)
+
+ prefs = {
+ "network.proxy.type": 1,
+ "network.proxy.http": "127.0.0.1",
+ "network.proxy.http_port": port,
+ "network.proxy.ssl": "127.0.0.1",
+ "network.proxy.ssl_port": port,
+ "network.proxy.no_proxies_on": "127.0.0.1",
+ }
+ browser_prefs = metadata.get_options("browser_prefs")
+ browser_prefs.update(prefs)
+
+ if metadata.flavor == "mobile-browser":
+ self.info("Setting reverse port fw for android device")
+ device = ADBDevice()
+ device.create_socket_connection("reverse", "tcp:%s" % port, "tcp:%s" % port)
+
+ return metadata
+
+ def teardown(self):
+ err = None
+ if self.proxy is not None:
+ returncode = self.proxy.wait(0)
+ if returncode is not None:
+ err = ValueError(
+ "mozproxy terminated early with return code %d" % returncode
+ )
+ else:
+ kill_signal = getattr(signal, "CTRL_BREAK_EVENT", signal.SIGINT)
+ os.kill(self.proxy.pid, kill_signal)
+ self.proxy.wait()
+ self.proxy = None
+ if self.tmpdir is not None:
+ self.tmpdir.cleanup()
+ self.tmpdir = None
+
+ if err:
+ raise err