1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
12 from mozdevice
import ADBDevice
13 from mozlog
import get_proxy_logger
14 from mozprocess
import ProcessHandler
16 from mozperftest
.layers
import Layer
17 from mozperftest
.utils
import (
25 LOG
= get_proxy_logger(component
="proxy")
26 HERE
= os
.path
.dirname(__file__
)
29 class OutputHandler(object):
33 self
.port_event
= threading
.Event()
35 def __call__(self
, line
):
39 line
= line
.decode("utf-8", errors
="replace")
41 data
= json
.loads(line
)
43 self
.process_output(line
)
46 if isinstance(data
, dict) and "action" in data
:
47 # Retrieve the port number for the proxy server from the logs of
49 m
= re
.match(r
"Proxy running on port (\d+)", data
.get("message", ""))
51 self
.port
= int(m
.group(1))
55 self
.process_output(json
.dumps(data
))
60 def process_output(self
, line
):
62 LOG
.process_output(line
)
64 LOG
.process_output(self
.proc
.pid
, line
)
66 def wait_for_port(self
):
67 self
.port_event
.wait()
71 class ProxyRunner(Layer
):
80 "choices": ["record", "playback"],
81 "help": "Proxy server mode. Use `playback` to replay from the provided file(s). "
82 "Use `record` to generate a new recording at the path specified by `--file`. "
83 "playback - replay from provided file. "
84 "record - generate a new recording at the specified path.",
89 "help": "The playback files to replay, or the file that a recording will be saved to. "
90 "For playback, it can be any combination of the following: zip file, manifest file, "
91 "or a URL to zip/manifest file. "
92 "For recording, it's a zip fle.",
97 "help": "This option can be used to specify a single test to record rather than "
98 "having to continuously modify the pageload_sites.json. This flag should only be "
99 "used by the perftest team and selects items from "
100 "`testing/performance/pageload_sites.json` based on the name field. Note that "
101 "the login fields won't be checked with a request such as this (i.e. it overrides "
105 "action": "store_true",
107 "help": "If set, the deterministic JS script will be injected into the pages.",
111 def __init__(self
, env
, mach_cmd
):
112 super(ProxyRunner
, self
).__init
__(env
, mach_cmd
)
118 import mozproxy
# noqa: F401
120 # Install mozproxy and its vendored deps.
121 mozbase
= pathlib
.Path(self
.mach_cmd
.topsrcdir
, "testing", "mozbase")
122 mozproxy_deps
= ["mozinfo", "mozlog", "mozproxy"]
123 for i
in mozproxy_deps
:
125 self
.mach_cmd
.virtualenv_manager
, pathlib
.Path(mozbase
, i
)
128 # set MOZ_HOST_BIN to find cerutil. Required to set certifcates on android
129 os
.environ
["MOZ_HOST_BIN"] = self
.mach_cmd
.bindir
131 def run(self
, metadata
):
132 self
.metadata
= metadata
133 replay_file
= self
.get_arg("file")
135 # Check if we have a replay file
136 if replay_file
is None:
137 raise ValueError("Proxy file not provided!!")
139 if replay_file
is not None and replay_file
.startswith("http"):
140 self
.tmpdir
= tempfile
.TemporaryDirectory()
141 target
= pathlib
.Path(self
.tmpdir
.name
, "recording.zip")
142 self
.info("Downloading %s" % replay_file
)
143 download_file(replay_file
, target
)
146 self
.info("Setting up the proxy")
149 self
.mach_cmd
.virtualenv_manager
.python_path
,
152 "--topsrcdir=" + self
.mach_cmd
.topsrcdir
,
153 "--objdir=" + self
.mach_cmd
.topobjdir
,
154 "--profiledir=" + self
.get_arg("profile-directory"),
158 command
.extend(["--local"])
160 if metadata
.flavor
== "mobile-browser":
161 command
.extend(["--tool=%s" % "mitmproxy-android"])
162 command
.extend(["--binary=android"])
164 [f
"--app={get_pretty_app_name(self.get_arg('android-app-name'))}"]
167 command
.extend(["--tool=%s" % "mitmproxy"])
168 # XXX See bug 1712337, we need a single point where we can get the binary used from
169 # this is required to make it work localy
170 binary
= self
.get_arg("browsertime-binary")
172 binary
= self
.mach_cmd
.get_binary_path()
173 command
.extend(["--binary=%s" % binary
])
175 if self
.get_arg("mode") == "record":
176 output
= self
.get_arg("output")
178 output
= pathlib
.Path(self
.mach_cmd
.topsrcdir
, "artifacts")
179 results_dir
= get_output_dir(output
)
181 command
.extend(["--mode", "record"])
182 command
.append(str(pathlib
.Path(results_dir
, replay_file
)))
183 elif self
.get_arg("mode") == "playback":
184 command
.extend(["--mode", "playback"])
185 command
.append(str(replay_file
))
187 raise ValueError("Proxy mode not provided please provide proxy mode")
189 inject_deterministic
= self
.get_arg("deterministic")
190 if inject_deterministic
:
191 command
.extend(["--deterministic"])
193 print(" ".join(command
))
194 self
.output_handler
= OutputHandler()
195 self
.proxy
= ProcessHandler(
197 processOutputLine
=self
.output_handler
,
198 onFinish
=self
.output_handler
.finished
,
200 self
.output_handler
.proc
= self
.proxy
203 # Wait until we've retrieved the proxy server's port number so we can
204 # configure the browser properly.
205 port
= self
.output_handler
.wait_for_port()
207 raise ValueError("Unable to retrieve the port number from mozproxy")
208 self
.info("Received port number %s from mozproxy" % port
)
211 "network.proxy.type": 1,
212 "network.proxy.http": "127.0.0.1",
213 "network.proxy.http_port": port
,
214 "network.proxy.ssl": "127.0.0.1",
215 "network.proxy.ssl_port": port
,
216 "network.proxy.no_proxies_on": "127.0.0.1",
218 browser_prefs
= metadata
.get_options("browser_prefs")
219 browser_prefs
.update(prefs
)
221 if metadata
.flavor
== "mobile-browser":
222 self
.info("Setting reverse port fw for android device")
224 device
.create_socket_connection("reverse", "tcp:%s" % port
, "tcp:%s" % port
)
230 if self
.proxy
is not None:
231 returncode
= self
.proxy
.wait(0)
232 if returncode
is not None:
234 "mozproxy terminated early with return code %d" % returncode
237 kill_signal
= getattr(signal
, "CTRL_BREAK_EVENT", signal
.SIGINT
)
238 os
.kill(self
.proxy
.pid
, kill_signal
)
241 if self
.tmpdir
is not None:
242 self
.tmpdir
.cleanup()