Developing in mozperftest
Architecture overview
mozperftest implements a mach command that is a thin wrapper on the top of runner.py, which allows us to run the tool without having to go through a mach call. Command arguments are prepared in argparser.py and then made available for the runner.
The runner creates a MachEnvironment instance (see environment.py) and a Metadata instance (see metadata.py). These two objects are shared during the whole test and used to share data across all parts.
The runner then calls MachEnvironment.run, which is in charge of running the test. The MachEnvironment instance runs a sequence of layers.
Layers are classes responsible for one single aspect of a performance test. They are organized in three categories:
system: anything that sets up and tears down some resources or services on the system. Existing system layers: android, proxy
test: layers that are in charge of running a test to collect metrics. Existing test layers: browsertime and androidlog
metrics: all layers that process the metrics to turn them into usable metrics. Existing system layers: perfherder and console
The MachEnvironment instance collects a series of layers for each category and runs them sequentially.
The goal of this organization is to allow adding new performance tests runners that will be based on a specific combination of layers. To avoid messy code, we need to make sure that each layer represents a single aspect of the process and that is completely independent of other layers (besides sharing the data through the common environment.)
For instance, we could use perftest to run a C++ benchmark by implementing a new test layer.
Layer
A layer is a class that inherits from mozperftest.layers.Layer and implements a few methods and class variables.
List of methods and variables:
name: name of the layer (class variable, mandatory)
activated: boolean to activate by default the layer (class variable, False)
user_exception: will trigger the on_exception hook when an exception occurs
arguments: dict containing arguments. Each argument is following the argparser standard
run(self, metadata): called to execute the layer
setup(self): called when the layer is about to be executed
teardown(self): called when the layer is exiting
Example:
class EmailSender(Layer):
"""Sends an email with the results.
"""
name = "email"
activated = False
arguments = {
"recipient": {
"type": str,
"default": "tarek@mozilla.com",
"help": "Recipient",
},
}
def setup(self):
self.server = smtplib.SMTP(smtp_server,port)
def teardown(self):
self.server.quit()
def __call__(self, metadata):
self.server.send_email(self.get_arg("recipient"), metadata.results())
It can then be added to one of the top functions that are used to create a list of layers for each category:
mozperftest.metrics.pick_metrics for the metrics category
mozperftest.system.pick_system for the system category
mozperftest.test.pick_browser for the test category
And also added in each get_layers function in each of those categories. The get_layers functions are invoked when building the argument parser.
In our example, adding the EmailSender layer will add two new options:
–email a flag to activate the layer
–email-recipient
Important layers
mozperftest can be used to run performance tests against browsers using the browsertime test layer. It leverages the browsertime.js framework and provides a full integration into Mozilla’s build and CI systems.
Browsertime uses the Selenium Webdriver client to drive the browser, and provides some metrics to measure performance during a user journey.
Coding style
For the coding style, we want to:
Follow PEP 257 for docstrings
Avoid complexity as much as possible
Use modern Python 3 code (for instance pathlib instead of os.path)
Avoid dependencies on Mozilla build projects and frameworks as much as possible (mozharness, mozbuild, etc), or make sure they are isolated and documented
Landing patches
Warning
It is mandatory for each patch to have a test. Any change without a test will be rejected.
Before landing a patch for mozperftest, make sure you run perftest-test:
% ./mach perftest-test
=> black [OK]
=> flake8 [OK]
=> remove old coverage data [OK]
=> running tests [OK]
=> coverage
Name Stmts Miss Cover Missing
------------------------------------------------------------------------------------------
mozperftest/metrics/notebook/analyzer.py 29 20 31% 26-36, 39-42, 45-51
...
mozperftest/system/proxy.py 37 0 100%
------------------------------------------------------------------------------------------
TOTAL 1614 240 85%
[OK]
The command will run black, flake8 and also make sure that the test coverage has not regressed.
You can use the -s option to bypass flake8/black to speed up your workflow, but make sure you do a full tests run. You can also pass the name of one single test module.