Bug 1656235 - support visual metrics r=acreskey,sparky

Differential Revision: https://phabricator.services.mozilla.com/D85561
This commit is contained in:
Tarek Ziadé 2020-08-08 12:17:48 +00:00
Родитель 5a38472369
Коммит abb4969c3e
11 изменённых файлов: 1331 добавлений и 219 удалений

Просмотреть файл

@ -5,11 +5,18 @@ from mozperftest.layers import Layers
from mozperftest.metrics.perfherder import Perfherder
from mozperftest.metrics.consoleoutput import ConsoleOutput
from mozperftest.metrics.notebookupload import Notebook
from mozperftest.metrics.visualmetrics import VisualMetrics
def get_layers():
return Perfherder, ConsoleOutput, Notebook
return VisualMetrics, Perfherder, ConsoleOutput, Notebook
def pick_metrics(env, flavor, mach_cmd):
return Layers(env, mach_cmd, get_layers())
if flavor in ("desktop-browser", "mobile-browser"):
layers = get_layers()
else:
# we don't need VisualMetrics for xpcshell
layers = Perfherder, ConsoleOutput, Notebook
return Layers(env, mach_cmd, layers)

Просмотреть файл

@ -0,0 +1,203 @@
# 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 subprocess
import json
import os
import sys
import errno
from pathlib import Path
from mozfile import which
from mozperftest.layers import Layer
from mozperftest.utils import silence
METRICS_FIELDS = (
"SpeedIndex",
"FirstVisualChange",
"LastVisualChange",
"VisualProgress",
"videoRecordingStart",
)
class VisualData:
def open_data(self, data):
res = {
"name": "visualmetrics",
"subtest": data["name"],
"data": [
{"file": "visualmetrics", "value": value, "xaxis": xaxis}
for xaxis, value in enumerate(data["values"])
],
}
return res
def transform(self, data):
return data
merge = transform
class VisualMetrics(Layer):
"""Wrapper around Browsertime's visualmetrics.py script
"""
name = "visualmetrics"
activated = False
arguments = {}
def setup(self):
self.metrics = []
self.metrics_fields = []
# making sure we have ffmpeg and imagemagick available
for tool in ("ffmpeg", "magick"):
if sys.platform in ("win32", "msys"):
tool += ".exe"
path = which(tool)
if not path:
raise OSError(errno.ENOENT, f"Could not find {tool}")
def run(self, metadata):
if "VISUALMETRICS_PY" not in os.environ:
raise OSError(
"The VISUALMETRICS_PY environment variable is not set."
"Make sure you run the browsertime layer"
)
path = Path(os.environ["VISUALMETRICS_PY"])
if not path.exists():
raise FileNotFoundError(str(path))
self.visualmetrics = path
treated = 0
for result in metadata.get_results():
result_dir = result.get("results")
if result_dir is None:
continue
result_dir = Path(result_dir)
if not result_dir.is_dir():
continue
browsertime_json = Path(result_dir, "browsertime.json")
if not browsertime_json.exists():
continue
treated += self.run_visual_metrics(browsertime_json)
self.info(f"Treated {treated} videos.")
if len(self.metrics) > 0:
metadata.add_result(
{
"name": "visualmetrics",
"framework": {"name": "mozperftest"},
"transformer": "mozperftest.metrics.visualmetrics:VisualData",
"results": self.metrics,
}
)
# we also extend --perfherder-metrics and --console-metrics if they
# are activated
def add_to_option(name):
existing = self.get_arg(name, [])
for field in self.metrics_fields:
existing.append({"name": field, "unit": "ms"})
self.env.set_arg(name, existing)
if self.get_arg("perfherder"):
add_to_option("perfherder-metrics")
if self.get_arg("console"):
add_to_option("console-metrics")
else:
self.warning("No video was treated.")
return metadata
def run_command(self, *cmd):
cmd = list(cmd)
self.debug(f"Running command {' '.join(cmd)}")
env = dict(os.environ)
path = [
p
for p in env["PATH"].split(os.pathsep)
if p != self.mach_cmd.virtualenv_manager.bin_path
]
env["PATH"] = os.pathsep.join(path)
try:
res = subprocess.check_output(cmd, universal_newlines=True, env=env)
self.debug("Command succeeded", result=res)
return 0, res
except subprocess.CalledProcessError as e:
self.debug("Command failed", cmd=cmd, status=e.returncode, output=e.output)
return e.returncode, e.output
def run_visual_metrics(self, browsertime_json):
verbose = self.get_arg("verbose")
self.info(f"Looking at {browsertime_json}")
class _display:
def __enter__(self, *args, **kw):
return self
__exit__ = __enter__
may_silence = not verbose and silence or _display
with browsertime_json.open() as f:
browsertime_json_data = json.loads(f.read())
videos = 0
for site in browsertime_json_data:
for video in site["files"]["video"]:
videos += 1
video_path = browsertime_json.parent / video
res = "[]"
with may_silence():
code, res = self.run_command(
str(self.visualmetrics), "--video", str(video_path), "--json"
)
if code != 0:
raise IOError(str(res))
try:
res = json.loads(res)
except json.JSONDecodeError:
self.error(
"Could not read the json output from" " visualmetrics.py"
)
raise
for name, value in res.items():
if name in ("VisualProgress",):
self._expand_visual_progress(name, value)
else:
self.append_metrics(name, value)
return videos
def _expand_visual_progress(self, name, value):
def _split_percent(val):
# value is of the form "567=94%"
val = val.split("=")
value, percent = val[0].strip(), val[1].strip()
if percent.endswith("%"):
percent = percent[:-1]
return int(percent), int(value)
percents = [_split_percent(elmt) for elmt in value.split(",")]
# we want to keep the first added value for each percent
# so the trick here is to create a dict() with the reversed list
percents = dict(reversed(percents))
# we are keeping the last 5 percents
percents = list(percents.items())
percents.sort()
for percent, value in percents[:5]:
self.append_metrics(f"{name}{percent}", value)
def append_metrics(self, name, value):
if name not in self.metrics_fields:
self.metrics_fields.append(name)
self.metrics.append(
{"name": name, "values": [value], "lowerIsBetter": True, "unit": "ms"}
)

Просмотреть файл

@ -12,15 +12,11 @@ from pathlib import Path
from mozperftest.utils import install_package
from mozperftest.test.noderunner import NodeRunner
from mozperftest.test.browsertime.setup import (
system_prerequisites,
append_system_env,
)
from mozperftest.test.browsertime.script import ScriptInfo
BROWSERTIME_SRC_ROOT = Path(__file__).parent
PILLOW_VERSION = "6.2.1"
PILLOW_VERSION = "7.2.0"
PYSSIM_VERSION = "0.4"
@ -122,7 +118,20 @@ class BrowsertimeRunner(NodeRunner):
@property
def browsertime_js(self):
root = os.environ.get("BROWSERTIME", self.state_path)
return Path(root, "node_modules", "browsertime", "bin", "browsertime.js")
path = Path(root, "node_modules", "browsertime", "bin", "browsertime.js")
if path.exists():
os.environ["BROWSERTIME_JS"] = str(path)
return path
@property
def visualmetrics_py(self):
root = os.environ.get("BROWSERTIME", self.state_path)
path = Path(
root, "node_modules", "browsertime", "browsertime", "visualmetrics.py"
)
if path.exists():
os.environ["VISUALMETRICS_PY"] = str(path)
return path
def setup(self):
"""Install browsertime and visualmetrics.py prerequisites and the Node.js package.
@ -148,14 +157,13 @@ class BrowsertimeRunner(NodeRunner):
# check if the browsertime package has been deployed correctly
# for this we just check for the browsertime directory presence
if self.browsertime_js.exists() and not self.get_arg("clobber"):
if (
self.visualmetrics_py.exists()
and self.browsertime_js.exists()
and not self.get_arg("clobber")
):
return
if install_url is None:
system_prerequisites(
str(self.state_path), str(self.artifact_cache_path), self.log, self.info
)
# preparing ~/.mozbuild/browsertime
for file in ("package.json", "package-lock.json"):
src = BROWSERTIME_SRC_ROOT / file
@ -223,10 +231,6 @@ class BrowsertimeRunner(NodeRunner):
no_optional=install_url or automation,
)
def append_env(self, append_path=True):
env = super(BrowsertimeRunner, self).append_env(append_path)
return append_system_env(env, str(self.state_path), append_path)
def extra_default_args(self, args=[]):
# Add Mozilla-specific default arguments. This is tricky because browsertime is quite
# loose about arguments; repeat arguments are generally accepted but then produce
@ -332,6 +336,10 @@ class BrowsertimeRunner(NodeRunner):
if self.get_arg("verbose"):
args += ["-vvv"]
# if the visualmetrics layer is activated, we want to feed it
if self.get_arg("visualmetrics"):
args += ["--video", "true"]
extra_options = self.get_arg("extra-options")
if extra_options:
for option in extra_options.split(","):

Просмотреть файл

@ -1,200 +0,0 @@
# 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 os
import stat
try:
from shutil import which
except ImportError:
from shutil_which import which
from mozbuild.util import mkdir
import mozpack.path as mozpath
from mozbuild.action.tooltool import unpack_file
from mozbuild.artifact_cache import ArtifactCache
from mozperftest.utils import host_platform
AUTOMATION = "MOZ_AUTOMATION" in os.environ
GECKO_RELEASES = (
"https://github.com/ncalexan/geckodriver/releases/download/v0.24.0-android"
)
# Map from `host_platform()` to a `fetch`-like syntax.
host_fetches = {
"darwin": {
"ffmpeg": {
"type": "static-url",
"url": GECKO_RELEASES + "/ffmpeg-4.1.1-macos64-static.zip", # noqa
# An extension to `fetch` syntax.
"path": "ffmpeg-4.1.1-macos64-static",
}
},
"linux64": {
"ffmpeg": {
"type": "static-url",
"url": GECKO_RELEASES + "/ffmpeg-4.1.4-i686-static.tar.xz", # noqa
# An extension to `fetch` syntax.
"path": "ffmpeg-4.1.4-i686-static",
},
# TODO: install a static ImageMagick. All easily available binaries are
# not statically linked, so they will (mostly) fail at runtime due to
# missing dependencies. For now we require folks to install ImageMagick
# globally with their package manager of choice.
},
"win64": {
"ffmpeg": {
"type": "static-url",
"url": GECKO_RELEASES + "/ffmpeg-4.1.1-win64-static.zip", # noqa
# An extension to `fetch` syntax.
"path": "ffmpeg-4.1.1-win64-static",
},
"ImageMagick": {
"type": "static-url",
# 'url': 'https://imagemagick.org/download/binaries/ImageMagick-7.0.8-39-portable-Q16-x64.zip', # noqa
# imagemagick.org doesn't keep old versions; the mirror below does.
"url": "https://ftp.icm.edu.pl/packages/ImageMagick/binaries/ImageMagick-7.0.8-39-portable-Q16-x64.zip", # noqa
# An extension to `fetch` syntax.
"path": "ImageMagick-7.0.8",
},
},
}
def system_prerequisites(state_path, artifact_cache_path, log, info):
"""Install browsertime and visualmetrics.py prerequisites.
"""
if not AUTOMATION and host_platform().startswith("linux"):
# On Linux ImageMagick needs to be installed manually, and `mach bootstrap` doesn't
# do that (yet). Provide some guidance.
im_programs = ("compare", "convert", "mogrify")
for im_program in im_programs:
prog = which(im_program)
if not prog:
raise Exception(
"Error: On Linux, ImageMagick must be on the PATH. "
"Install ImageMagick manually and try again (or update PATH). "
"On Ubuntu and Debian, try `sudo apt-get install imagemagick`. "
"On Fedora, try `sudo dnf install imagemagick`. "
"On CentOS, try `sudo yum install imagemagick`."
)
# Download the visualmetrics.py requirements.
artifact_cache = ArtifactCache(artifact_cache_path, log=log, skip_cache=False)
fetches = host_fetches[host_platform()]
for tool, fetch in sorted(fetches.items()):
archive = artifact_cache.fetch(fetch["url"])
# TODO: assert type, verify sha256 (and size?).
if fetch.get("unpack", True):
cwd = os.getcwd()
try:
mkdir(state_path)
os.chdir(state_path)
info("Unpacking temporary location {path}", path=archive)
if "win64" in host_platform() and "imagemagick" in tool.lower():
# Windows archive does not contain a subfolder
# so we make one for it here
mkdir(fetch.get("path"))
os.chdir(os.path.join(state_path, fetch.get("path")))
unpack_file(archive)
os.chdir(state_path)
else:
unpack_file(archive)
# Make sure the expected path exists after extraction
path = os.path.join(state_path, fetch.get("path"))
if not os.path.exists(path):
raise Exception("Cannot find an extracted directory: %s" % path)
try:
# Some archives provide binaries that don't have the
# executable bit set so we need to set it here
for root, dirs, files in os.walk(path):
for edir in dirs:
loc_to_change = os.path.join(root, edir)
st = os.stat(loc_to_change)
os.chmod(loc_to_change, st.st_mode | stat.S_IEXEC)
for efile in files:
loc_to_change = os.path.join(root, efile)
st = os.stat(loc_to_change)
os.chmod(loc_to_change, st.st_mode | stat.S_IEXEC)
except Exception as e:
raise Exception(
"Could not set executable bit in %s, error: %s" % (path, str(e))
)
finally:
os.chdir(cwd)
def append_system_env(env, state_path, append_path=True):
fetches = host_fetches[host_platform()]
# Ensure that bare `ffmpeg` and ImageMagick commands
# {`convert`,`compare`,`mogrify`} are found. The `visualmetrics.py`
# script doesn't take these as configuration, so we do this (for now).
# We should update the script itself to accept this configuration.
path = env.get("PATH", "").split(os.pathsep)
path_to_ffmpeg = mozpath.join(state_path, fetches["ffmpeg"]["path"])
path_to_imagemagick = None
if "ImageMagick" in fetches:
path_to_imagemagick = mozpath.join(state_path, fetches["ImageMagick"]["path"])
if path_to_imagemagick:
# ImageMagick ships ffmpeg (on Windows, at least) so we
# want to ensure that our ffmpeg goes first, just in case.
path.insert(
0,
state_path
if host_platform().startswith("win")
else mozpath.join(path_to_imagemagick, "bin"),
) # noqa
path.insert(
0,
path_to_ffmpeg
if host_platform().startswith("linux")
else mozpath.join(path_to_ffmpeg, "bin"),
) # noqa
# On windows, we need to add the ImageMagick directory to the path
# otherwise compare won't be found, and the built-in OS convert
# method will be used instead of the ImageMagick one.
if "win64" in host_platform() and path_to_imagemagick:
# Bug 1596237 - In the windows ImageMagick distribution, the ffmpeg
# binary is directly located in the root directory, so here we
# insert in the 3rd position to avoid taking precedence over ffmpeg
path.insert(2, path_to_imagemagick)
# On macOs, we can't install our own ImageMagick because the
# System Integrity Protection (SIP) won't let us set DYLD_LIBRARY_PATH
# unless we deactivate SIP with "csrutil disable".
# So we're asking the user to install it.
#
# if ImageMagick was installed via brew, we want to make sure we
# include the PATH
if host_platform() == "darwin":
for p in os.environ["PATH"].split(os.pathsep):
p = p.strip()
if not p or p in path:
continue
path.append(p)
if path_to_imagemagick:
env.update(
{
# See https://imagemagick.org/script/download.php.
# Harmless on other platforms.
"LD_LIBRARY_PATH": mozpath.join(path_to_imagemagick, "lib"),
"DYLD_LIBRARY_PATH": mozpath.join(path_to_imagemagick, "lib"),
"MAGICK_HOME": path_to_imagemagick,
}
)
return env

Просмотреть файл

@ -0,0 +1,999 @@
[
{
"info": {
"browsertime": {
"version": "8.3.0"
},
"url": "https://www.bbc.com/news/world-middle-east-53598965",
"timestamp": "2020-08-03T11:18:37+02:00",
"connectivity": {
"engine": "external",
"profile": "native"
},
"extra": {},
"alias": "pageload"
},
"files": {
"video": [
"pages/www.bbc.com/news/world-middle-east-53598965/data/video/1.mp4"
],
"screenshot": [],
"timeline": [],
"consoleLog": [],
"netLog": [],
"perfLog": []
},
"cdp": {
"performance": []
},
"timestamps": [
"2020-08-03T11:18:12+02:00"
],
"browserScripts": [
{
"browser": {
"appConstants": {
"ACCESSIBILITY": true,
"ANDROID_PACKAGE_NAME": "org.mozilla.firefox",
"ASAN": false,
"ASAN_REPORTER": false,
"BROWSER_CHROME_URL": "chrome://browser/content/browser.xhtml",
"DEBUG": false,
"DEBUG_JS_MODULES": "",
"DLL_PREFIX": "lib",
"DLL_SUFFIX": ".dylib",
"EARLY_BETA_OR_EARLIER": true,
"ENABLE_REMOTE_AGENT": true,
"FENNEC_NIGHTLY": false,
"HAVE_SHELL_SERVICE": true,
"HAVE_USR_LIB64_DIR": false,
"MENUBAR_CAN_AUTOHIDE": false,
"MOZILLA_OFFICIAL": false,
"MOZ_ALLOW_ADDON_SIDELOAD": false,
"MOZ_ALLOW_LEGACY_EXTENSIONS": false,
"MOZ_ANDROID_HISTORY": false,
"MOZ_APP_BASENAME": "Firefox",
"MOZ_APP_NAME": "firefox",
"MOZ_APP_VERSION": "81.0a1",
"MOZ_APP_VERSION_DISPLAY": "81.0a1",
"MOZ_BING_API_CLIENTID": "no-bing-api-clientid",
"MOZ_BING_API_KEY": "no-bing-api-key",
"MOZ_BITS_DOWNLOAD": false,
"MOZ_BUILDID": "2020073017",
"MOZ_BUILD_APP": "browser",
"MOZ_CODE_COVERAGE": false,
"MOZ_CRASHREPORTER": true,
"MOZ_DATA_REPORTING": true,
"MOZ_DEV_EDITION": false,
"MOZ_GECKO_PROFILER": true,
"MOZ_GOOGLE_LOCATION_SERVICE_API_KEY": "no-google-location-service-api-key",
"MOZ_GOOGLE_SAFEBROWSING_API_KEY": "no-google-safebrowsing-api-key",
"MOZ_MACBUNDLE_NAME": "Nightly.app",
"MOZ_MAINTENANCE_SERVICE": false,
"MOZ_MOZILLA_API_KEY": "no-mozilla-api-key",
"MOZ_NEW_CERT_STORAGE": true,
"MOZ_NEW_NOTIFICATION_STORE": true,
"MOZ_NEW_XULSTORE": true,
"MOZ_NORMANDY": true,
"MOZ_OFFICIAL_BRANDING": false,
"MOZ_PLACES": true,
"MOZ_REQUIRE_SIGNING": false,
"MOZ_RUST_FXA_CLIENT": true,
"MOZ_SANDBOX": false,
"MOZ_SERVICES_HEALTHREPORT": true,
"MOZ_SERVICES_SYNC": false,
"MOZ_SWITCHBOARD": false,
"MOZ_SYSTEM_NSS": false,
"MOZ_TELEMETRY_ON_BY_DEFAULT": false,
"MOZ_TELEMETRY_REPORTING": false,
"MOZ_UNSIGNED_SCOPES": 0,
"MOZ_UPDATER": true,
"MOZ_UPDATE_AGENT": false,
"MOZ_UPDATE_CHANNEL": "default",
"MOZ_WEBRTC": true,
"MOZ_WIDGET_GTK": false,
"MOZ_WIDGET_TOOLKIT": "cocoa",
"NIGHTLY_BUILD": true,
"OMNIJAR_NAME": "omni.ja",
"RELEASE_OR_BETA": false,
"SOURCE_REVISION_URL": "",
"TELEMETRY_PING_FORMAT_VERSION": 4,
"TSAN": false,
"XP_UNIX": true,
"isPlatformAndVersionAtLeast": {},
"isPlatformAndVersionAtMost": {},
"platform": "macosx",
"unixstyle": "other"
},
"asyncAppConstants": {
"ACCESSIBILITY": true,
"ANDROID_PACKAGE_NAME": "org.mozilla.firefox",
"ASAN": false,
"ASAN_REPORTER": false,
"BROWSER_CHROME_URL": "chrome://browser/content/browser.xhtml",
"DEBUG": false,
"DEBUG_JS_MODULES": "",
"DLL_PREFIX": "lib",
"DLL_SUFFIX": ".dylib",
"EARLY_BETA_OR_EARLIER": true,
"ENABLE_REMOTE_AGENT": true,
"FENNEC_NIGHTLY": false,
"HAVE_SHELL_SERVICE": true,
"HAVE_USR_LIB64_DIR": false,
"MENUBAR_CAN_AUTOHIDE": false,
"MOZILLA_OFFICIAL": false,
"MOZ_ALLOW_ADDON_SIDELOAD": false,
"MOZ_ALLOW_LEGACY_EXTENSIONS": false,
"MOZ_ANDROID_HISTORY": false,
"MOZ_APP_BASENAME": "Firefox",
"MOZ_APP_NAME": "firefox",
"MOZ_APP_VERSION": "81.0a1",
"MOZ_APP_VERSION_DISPLAY": "81.0a1",
"MOZ_BING_API_CLIENTID": "no-bing-api-clientid",
"MOZ_BING_API_KEY": "no-bing-api-key",
"MOZ_BITS_DOWNLOAD": false,
"MOZ_BUILDID": "200730174322",
"MOZ_BUILD_APP": "browser",
"MOZ_CODE_COVERAGE": false,
"MOZ_CRASHREPORTER": true,
"MOZ_DATA_REPORTING": true,
"MOZ_DEV_EDITION": false,
"MOZ_GECKO_PROFILER": true,
"MOZ_GOOGLE_LOCATION_SERVICE_API_KEY": "no-google-location-service-api-key",
"MOZ_GOOGLE_SAFEBROWSING_API_KEY": "no-google-safebrowsing-api-key",
"MOZ_MACBUNDLE_NAME": "Nightly.app",
"MOZ_MAINTENANCE_SERVICE": false,
"MOZ_MOZILLA_API_KEY": "no-mozilla-api-key",
"MOZ_NEW_CERT_STORAGE": true,
"MOZ_NEW_NOTIFICATION_STORE": true,
"MOZ_NEW_XULSTORE": true,
"MOZ_NORMANDY": true,
"MOZ_OFFICIAL_BRANDING": false,
"MOZ_PLACES": true,
"MOZ_REQUIRE_SIGNING": false,
"MOZ_RUST_FXA_CLIENT": true,
"MOZ_SANDBOX": false,
"MOZ_SERVICES_HEALTHREPORT": true,
"MOZ_SERVICES_SYNC": false,
"MOZ_SWITCHBOARD": false,
"MOZ_SYSTEM_NSS": false,
"MOZ_TELEMETRY_ON_BY_DEFAULT": false,
"MOZ_TELEMETRY_REPORTING": false,
"MOZ_UNSIGNED_SCOPES": 0,
"MOZ_UPDATER": true,
"MOZ_UPDATE_AGENT": false,
"MOZ_UPDATE_CHANNEL": "default",
"MOZ_WEBRTC": true,
"MOZ_WIDGET_GTK": false,
"MOZ_WIDGET_TOOLKIT": "cocoa",
"NIGHTLY_BUILD": true,
"OMNIJAR_NAME": "omni.ja",
"RELEASE_OR_BETA": false,
"SOURCE_REVISION_URL": "",
"TELEMETRY_PING_FORMAT_VERSION": 4,
"TSAN": false,
"XP_UNIX": true,
"isPlatformAndVersionAtLeast": {},
"isPlatformAndVersionAtMost": {},
"platform": "macosx",
"unixstyle": "other"
},
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0",
"windowSize": "1366x768"
},
"pageinfo": {
"documentHeight": 8937,
"documentSize": {
"decodedBodySize": 270461,
"encodedBodySize": 67482,
"transferSize": 68337
},
"documentTitle": "Coronavirus: Iran cover-up of deaths revealed by data leak - BBC News",
"documentWidth": 1366,
"domElements": 1348,
"navigationStartTime": 1596629,
"nextHopProtocol": "h2",
"resources": {
"count": 91,
"duration": 26360.459999999992
},
"responsive": true,
"url": "https://www.bbc.com/news/world-middle-east-53598965",
"visualElements": {
"heroes": [
{
"filename": "_113766981_iranhospital.jpg",
"height": 363,
"name": "LargestImage",
"width": 646,
"x": 195,
"y": 403
},
{
"filename": null,
"height": 72,
"name": "Heading",
"width": 645,
"x": 195,
"y": 196
}
],
"viewport": {
"height": 694,
"width": 1366
}
}
},
"timings": {
"firstPaint": 1084,
"loadEventEnd": 8274,
"navigationTiming": {
"connectStart": 20,
"domComplete": 8238,
"domContentLoadedEventEnd": 4165,
"domContentLoadedEventStart": 4159,
"domInteractive": 1415,
"domainLookupEnd": 20,
"domainLookupStart": 20,
"duration": 8274,
"fetchStart": 20,
"loadEventEnd": 8274,
"loadEventStart": 8264,
"redirectEnd": 0,
"redirectStart": 0,
"requestStart": 29,
"responseEnd": 117,
"responseStart": 117,
"secureConnectionStart": 0,
"startTime": 0,
"unloadEventEnd": 124,
"unloadEventStart": 120,
"workerStart": 0
},
"pageTimings": {
"backEndTime": 117,
"domContentLoadedTime": 4159,
"domInteractiveTime": 1415,
"domainLookupTime": 0,
"frontEndTime": 8147,
"pageDownloadTime": 0,
"pageLoadTime": 8264,
"redirectionTime": 0,
"serverConnectionTime": 0,
"serverResponseTime": 88
},
"rumSpeedIndex": 5542,
"serverTimings": [],
"timeToContentfulPaint": 1124,
"timeToDomContentFlushed": 4158,
"timeToFirstInteractive": 11858,
"userTimings": {
"marks": [],
"measures": []
}
}
}
],
"visualMetrics": [],
"cpu": [],
"extras": [
{}
],
"fullyLoaded": [],
"errors": [
[]
],
"statistics": {
"browser": {
"appConstants": {
"MOZ_BUILDID": {
"median": 2020073017,
"mean": 2020073017,
"mdev": 0,
"stddev": 0,
"min": 2020073017,
"p10": 2020073017,
"p90": 2020073017,
"p99": 2020073017,
"max": 2020073017
},
"MOZ_UNSIGNED_SCOPES": {
"median": 0,
"mean": 0,
"mdev": 0,
"stddev": 0,
"min": 0,
"p10": 0,
"p90": 0,
"p99": 0,
"max": 0
},
"TELEMETRY_PING_FORMAT_VERSION": {
"median": 4,
"mean": 4,
"mdev": 0,
"stddev": 0,
"min": 4,
"p10": 4,
"p90": 4,
"p99": 4,
"max": 4
}
},
"asyncAppConstants": {
"MOZ_BUILDID": {
"median": 2020073017,
"mean": 2020073017,
"mdev": 0,
"stddev": 0,
"min": 2020073017,
"p10": 2020073017,
"p90": 2020073017,
"p99": 2020073017,
"max": 2020073017
},
"MOZ_UNSIGNED_SCOPES": {
"median": 0,
"mean": 0,
"mdev": 0,
"stddev": 0,
"min": 0,
"p10": 0,
"p90": 0,
"p99": 0,
"max": 0
},
"TELEMETRY_PING_FORMAT_VERSION": {
"median": 4,
"mean": 4,
"mdev": 0,
"stddev": 0,
"min": 4,
"p10": 4,
"p90": 4,
"p99": 4,
"max": 4
}
}
},
"pageinfo": {
"documentHeight": {
"median": 8937,
"mean": 8937,
"mdev": 0,
"stddev": 0,
"min": 8937,
"p10": 8937,
"p90": 8937,
"p99": 8937,
"max": 8937
},
"documentSize": {
"decodedBodySize": {
"median": 270461,
"mean": 270461,
"mdev": 0,
"stddev": 0,
"min": 270461,
"p10": 270461,
"p90": 270461,
"p99": 270461,
"max": 270461
},
"encodedBodySize": {
"median": 67482,
"mean": 67482,
"mdev": 0,
"stddev": 0,
"min": 67482,
"p10": 67482,
"p90": 67482,
"p99": 67482,
"max": 67482
},
"transferSize": {
"median": 68337,
"mean": 68337,
"mdev": 0,
"stddev": 0,
"min": 68337,
"p10": 68337,
"p90": 68337,
"p99": 68337,
"max": 68337
}
},
"documentWidth": {
"median": 1366,
"mean": 1366,
"mdev": 0,
"stddev": 0,
"min": 1366,
"p10": 1366,
"p90": 1366,
"p99": 1366,
"max": 1366
},
"domElements": {
"median": 1348,
"mean": 1348,
"mdev": 0,
"stddev": 0,
"min": 1348,
"p10": 1348,
"p90": 1348,
"p99": 1348,
"max": 1348
},
"navigationStartTime": {
"median": 1596629,
"mean": 1596629,
"mdev": 0,
"stddev": 0,
"min": 1596629,
"p10": 1596629,
"p90": 1596629,
"p99": 1596629,
"max": 1596629
},
"resources": {
"count": {
"median": 91,
"mean": 91,
"mdev": 0,
"stddev": 0,
"min": 91,
"p10": 91,
"p90": 91,
"p99": 91,
"max": 91
},
"duration": {
"median": 26360,
"mean": 26360,
"mdev": 0,
"stddev": 0,
"min": 26360,
"p10": 26360,
"p90": 26360,
"p99": 26360,
"max": 26360
}
},
"visualElements": {
"heroes": [
{
"height": {
"median": 363,
"mean": 363,
"mdev": 0,
"stddev": 0,
"min": 363,
"p10": 363,
"p90": 363,
"p99": 363,
"max": 363
},
"width": {
"median": 646,
"mean": 646,
"mdev": 0,
"stddev": 0,
"min": 646,
"p10": 646,
"p90": 646,
"p99": 646,
"max": 646
},
"x": {
"median": 195,
"mean": 195,
"mdev": 0,
"stddev": 0,
"min": 195,
"p10": 195,
"p90": 195,
"p99": 195,
"max": 195
},
"y": {
"median": 403,
"mean": 403,
"mdev": 0,
"stddev": 0,
"min": 403,
"p10": 403,
"p90": 403,
"p99": 403,
"max": 403
}
},
{
"height": {
"median": 72,
"mean": 72,
"mdev": 0,
"stddev": 0,
"min": 72,
"p10": 72,
"p90": 72,
"p99": 72,
"max": 72
},
"width": {
"median": 645,
"mean": 645,
"mdev": 0,
"stddev": 0,
"min": 645,
"p10": 645,
"p90": 645,
"p99": 645,
"max": 645
},
"x": {
"median": 195,
"mean": 195,
"mdev": 0,
"stddev": 0,
"min": 195,
"p10": 195,
"p90": 195,
"p99": 195,
"max": 195
},
"y": {
"median": 196,
"mean": 196,
"mdev": 0,
"stddev": 0,
"min": 196,
"p10": 196,
"p90": 196,
"p99": 196,
"max": 196
}
}
],
"viewport": {
"height": {
"median": 694,
"mean": 694,
"mdev": 0,
"stddev": 0,
"min": 694,
"p10": 694,
"p90": 694,
"p99": 694,
"max": 694
},
"width": {
"median": 1366,
"mean": 1366,
"mdev": 0,
"stddev": 0,
"min": 1366,
"p10": 1366,
"p90": 1366,
"p99": 1366,
"max": 1366
}
}
}
},
"timings": {
"firstPaint": {
"median": 1084,
"mean": 1084,
"mdev": 0,
"stddev": 0,
"min": 1084,
"p10": 1084,
"p90": 1084,
"p99": 1084,
"max": 1084
},
"loadEventEnd": {
"median": 8274,
"mean": 8274,
"mdev": 0,
"stddev": 0,
"min": 8274,
"p10": 8274,
"p90": 8274,
"p99": 8274,
"max": 8274
},
"navigationTiming": {
"connectStart": {
"median": 20,
"mean": 20,
"mdev": 0,
"stddev": 0,
"min": 20,
"p10": 20,
"p90": 20,
"p99": 20,
"max": 20
},
"domComplete": {
"median": 8238,
"mean": 8238,
"mdev": 0,
"stddev": 0,
"min": 8238,
"p10": 8238,
"p90": 8238,
"p99": 8238,
"max": 8238
},
"domContentLoadedEventEnd": {
"median": 4165,
"mean": 4165,
"mdev": 0,
"stddev": 0,
"min": 4165,
"p10": 4165,
"p90": 4165,
"p99": 4165,
"max": 4165
},
"domContentLoadedEventStart": {
"median": 4159,
"mean": 4159,
"mdev": 0,
"stddev": 0,
"min": 4159,
"p10": 4159,
"p90": 4159,
"p99": 4159,
"max": 4159
},
"domInteractive": {
"median": 1415,
"mean": 1415,
"mdev": 0,
"stddev": 0,
"min": 1415,
"p10": 1415,
"p90": 1415,
"p99": 1415,
"max": 1415
},
"domainLookupEnd": {
"median": 20,
"mean": 20,
"mdev": 0,
"stddev": 0,
"min": 20,
"p10": 20,
"p90": 20,
"p99": 20,
"max": 20
},
"domainLookupStart": {
"median": 20,
"mean": 20,
"mdev": 0,
"stddev": 0,
"min": 20,
"p10": 20,
"p90": 20,
"p99": 20,
"max": 20
},
"duration": {
"median": 8274,
"mean": 8274,
"mdev": 0,
"stddev": 0,
"min": 8274,
"p10": 8274,
"p90": 8274,
"p99": 8274,
"max": 8274
},
"fetchStart": {
"median": 20,
"mean": 20,
"mdev": 0,
"stddev": 0,
"min": 20,
"p10": 20,
"p90": 20,
"p99": 20,
"max": 20
},
"loadEventEnd": {
"median": 8274,
"mean": 8274,
"mdev": 0,
"stddev": 0,
"min": 8274,
"p10": 8274,
"p90": 8274,
"p99": 8274,
"max": 8274
},
"loadEventStart": {
"median": 8264,
"mean": 8264,
"mdev": 0,
"stddev": 0,
"min": 8264,
"p10": 8264,
"p90": 8264,
"p99": 8264,
"max": 8264
},
"redirectEnd": {
"median": 0,
"mean": 0,
"mdev": 0,
"stddev": 0,
"min": 0,
"p10": 0,
"p90": 0,
"p99": 0,
"max": 0
},
"redirectStart": {
"median": 0,
"mean": 0,
"mdev": 0,
"stddev": 0,
"min": 0,
"p10": 0,
"p90": 0,
"p99": 0,
"max": 0
},
"requestStart": {
"median": 29,
"mean": 29,
"mdev": 0,
"stddev": 0,
"min": 29,
"p10": 29,
"p90": 29,
"p99": 29,
"max": 29
},
"responseEnd": {
"median": 117,
"mean": 117,
"mdev": 0,
"stddev": 0,
"min": 117,
"p10": 117,
"p90": 117,
"p99": 117,
"max": 117
},
"responseStart": {
"median": 117,
"mean": 117,
"mdev": 0,
"stddev": 0,
"min": 117,
"p10": 117,
"p90": 117,
"p99": 117,
"max": 117
},
"secureConnectionStart": {
"median": 0,
"mean": 0,
"mdev": 0,
"stddev": 0,
"min": 0,
"p10": 0,
"p90": 0,
"p99": 0,
"max": 0
},
"startTime": {
"median": 0,
"mean": 0,
"mdev": 0,
"stddev": 0,
"min": 0,
"p10": 0,
"p90": 0,
"p99": 0,
"max": 0
},
"unloadEventEnd": {
"median": 124,
"mean": 124,
"mdev": 0,
"stddev": 0,
"min": 124,
"p10": 124,
"p90": 124,
"p99": 124,
"max": 124
},
"unloadEventStart": {
"median": 120,
"mean": 120,
"mdev": 0,
"stddev": 0,
"min": 120,
"p10": 120,
"p90": 120,
"p99": 120,
"max": 120
},
"workerStart": {
"median": 0,
"mean": 0,
"mdev": 0,
"stddev": 0,
"min": 0,
"p10": 0,
"p90": 0,
"p99": 0,
"max": 0
}
},
"pageTimings": {
"backEndTime": {
"median": 117,
"mean": 117,
"mdev": 0,
"stddev": 0,
"min": 117,
"p10": 117,
"p90": 117,
"p99": 117,
"max": 117
},
"domContentLoadedTime": {
"median": 4159,
"mean": 4159,
"mdev": 0,
"stddev": 0,
"min": 4159,
"p10": 4159,
"p90": 4159,
"p99": 4159,
"max": 4159
},
"domInteractiveTime": {
"median": 1415,
"mean": 1415,
"mdev": 0,
"stddev": 0,
"min": 1415,
"p10": 1415,
"p90": 1415,
"p99": 1415,
"max": 1415
},
"domainLookupTime": {
"median": 0,
"mean": 0,
"mdev": 0,
"stddev": 0,
"min": 0,
"p10": 0,
"p90": 0,
"p99": 0,
"max": 0
},
"frontEndTime": {
"median": 8147,
"mean": 8147,
"mdev": 0,
"stddev": 0,
"min": 8147,
"p10": 8147,
"p90": 8147,
"p99": 8147,
"max": 8147
},
"pageDownloadTime": {
"median": 0,
"mean": 0,
"mdev": 0,
"stddev": 0,
"min": 0,
"p10": 0,
"p90": 0,
"p99": 0,
"max": 0
},
"pageLoadTime": {
"median": 8264,
"mean": 8264,
"mdev": 0,
"stddev": 0,
"min": 8264,
"p10": 8264,
"p90": 8264,
"p99": 8264,
"max": 8264
},
"redirectionTime": {
"median": 0,
"mean": 0,
"mdev": 0,
"stddev": 0,
"min": 0,
"p10": 0,
"p90": 0,
"p99": 0,
"max": 0
},
"serverConnectionTime": {
"median": 0,
"mean": 0,
"mdev": 0,
"stddev": 0,
"min": 0,
"p10": 0,
"p90": 0,
"p99": 0,
"max": 0
},
"serverResponseTime": {
"median": 88,
"mean": 88,
"mdev": 0,
"stddev": 0,
"min": 88,
"p10": 88,
"p90": 88,
"p99": 88,
"max": 88
}
},
"rumSpeedIndex": {
"median": 5542,
"mean": 5542,
"mdev": 0,
"stddev": 0,
"min": 5542,
"p10": 5542,
"p90": 5542,
"p99": 5542,
"max": 5542
},
"timeToContentfulPaint": {
"median": 1124,
"mean": 1124,
"mdev": 0,
"stddev": 0,
"min": 1124,
"p10": 1124,
"p90": 1124,
"p99": 1124,
"max": 1124
},
"timeToDomContentFlushed": {
"median": 4158,
"mean": 4158,
"mdev": 0,
"stddev": 0,
"min": 4158,
"p10": 4158,
"p90": 4158,
"p99": 4158,
"max": 4158
},
"timeToFirstInteractive": {
"median": 11858,
"mean": 11858,
"mdev": 0,
"stddev": 0,
"min": 11858,
"p10": 11858,
"p90": 11858,
"p99": 11858,
"max": 11858
}
}
}
}
]

Двоичный файл не отображается.

Просмотреть файл

@ -15,6 +15,7 @@ EXAMPLE_TESTS_DIR = os.path.join(HERE, "data", "samples")
EXAMPLE_TEST = os.path.join(EXAMPLE_TESTS_DIR, "perftest_example.js")
EXAMPLE_XPCSHELL_TEST = Path(EXAMPLE_TESTS_DIR, "test_xpcshell.js")
BT_DATA = Path(HERE, "data", "browsertime-results", "browsertime.json")
BT_DATA_VIDEO = Path(HERE, "data", "browsertime-results-video", "browsertime.json")
DMG = Path(HERE, "data", "firefox.dmg")

Просмотреть файл

@ -0,0 +1,93 @@
#!/usr/bin/env python
import mozunit
from unittest import mock
import os
import json
import pytest
from mozperftest.tests.support import (
get_running_env,
EXAMPLE_TEST,
temp_file,
BT_DATA_VIDEO,
)
from mozperftest.environment import METRICS
VM_RES = {
"SpeedIndex": 1031,
"FirstVisualChange": 533,
"LastVisualChange": 3166,
"VisualProgress": (
"0=0, 533=63, 700=63, 733=63, 900=63, 933=63, 1233=54,"
"1333=54, 1366=56, 1500=56, 1633=96, 1800=96, 1933=96,"
"2133=96, 2200=96, 2366=96, 2533=96, 2566=96, 2600=96,"
"2733=96, 2833=96, 2933=96, 3000=96, 3133=96,3166=100"
),
"videoRecordingStart": 0,
}
def get_res(*args, **kw):
return json.dumps(VM_RES)
@mock.patch("mozperftest.test.browsertime.runner.install_package")
@mock.patch(
"mozperftest.test.noderunner.NodeRunner.verify_node_install", new=lambda x: True
)
@mock.patch(
"mozperftest.test.browsertime.runner.BrowsertimeRunner._setup_node_packages",
new=lambda x, y: None,
)
@mock.patch("mozperftest.metrics.visualmetrics.which", new=lambda path: "ok")
@mock.patch("subprocess.check_output", new=get_res)
def test_visual_metrics(device):
os.environ["VISUALMETRICS_PY"] = ""
mach_cmd, metadata, env = get_running_env(
visualmetrics=True, perfherder=True, verbose=True
)
metrics = env.layers[METRICS]
env.set_arg("tests", [str(EXAMPLE_TEST)])
metadata.add_result({"results": str(BT_DATA_VIDEO.parent), "name": "browsertime"})
with temp_file() as output:
env.set_arg("output", output)
with metrics as m:
metadata = m(metadata)
output_file = metadata.get_output()
with open(output_file) as f:
output = json.loads(f.read())
# Check some metadata
assert output["application"]["name"] == "firefox"
visual_metrics = [i["name"] for i in output["suites"][1]["subtests"]]
assert "VisualProgress96" in visual_metrics
@mock.patch("mozperftest.test.browsertime.runner.install_package")
@mock.patch(
"mozperftest.test.noderunner.NodeRunner.verify_node_install", new=lambda x: True
)
@mock.patch(
"mozperftest.test.browsertime.runner.BrowsertimeRunner._setup_node_packages",
new=lambda x, y: None,
)
@mock.patch("mozperftest.metrics.visualmetrics.which", new=lambda path: None)
def test_visual_metrics_no_ffmpeg(device):
os.environ["VISUALMETRICS_PY"] = ""
mach_cmd, metadata, env = get_running_env(
visualmetrics=True, perfherder=True, verbose=True
)
metrics = env.layers[METRICS]
env.set_arg("tests", [str(EXAMPLE_TEST)])
metadata.add_result({"results": str(BT_DATA_VIDEO.parent), "name": "browsertime"})
with pytest.raises(FileNotFoundError):
with metrics as m:
metadata = m(metadata)
if __name__ == "__main__":
mozunit.main()

Просмотреть файл

@ -9,7 +9,8 @@ from setuptools import setup
PACKAGE_NAME = "mozperftest"
PACKAGE_VERSION = "0.2"
deps = ["regex", "jsonschema", "mozlog >= 6.0", "mozdevice >= 4.0.0", "mozproxy", "mozinfo"]
deps = ["regex", "jsonschema", "mozlog >= 6.0", "mozdevice >= 4.0.0", "mozproxy",
"mozinfo", "mozfile"]
setup(
name=PACKAGE_NAME,