Source code

Revision control

Copy as Markdown

Other Tools

#!/usr/bin/env python
# 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 glob
import json
import math
import os
import sys
import six
AWSY_PATH = os.path.dirname(os.path.realpath(__file__))
if AWSY_PATH not in sys.path:
sys.path.append(AWSY_PATH)
import parse_about_memory
# A description of each checkpoint and the root path to it.
CHECKPOINTS = [
{"name": "Fresh start", "path": "memory-report-Start-0.json.gz"},
{"name": "Fresh start [+30s]", "path": "memory-report-StartSettled-0.json.gz"},
{"name": "After tabs open", "path": "memory-report-TabsOpen-4.json.gz"},
{
"name": "After tabs open [+30s]",
"path": "memory-report-TabsOpenSettled-4.json.gz",
},
{
"name": "After tabs open [+30s, forced GC]",
"path": "memory-report-TabsOpenForceGC-4.json.gz",
},
{
"name": "Tabs closed extra processes",
"path": "memory-report-TabsClosedExtraProcesses-4.json.gz",
},
{"name": "Tabs closed", "path": "memory-report-TabsClosed-4.json.gz"},
{"name": "Tabs closed [+30s]", "path": "memory-report-TabsClosedSettled-4.json.gz"},
{
"name": "Tabs closed [+30s, forced GC]",
"path": "memory-report-TabsClosedForceGC-4.json.gz",
},
]
# A description of each perfherder suite and the path to its values.
PERF_SUITES = [
{"name": "Resident Memory", "node": "resident"},
{"name": "Explicit Memory", "node": "explicit/"},
{"name": "Heap Unclassified", "node": "explicit/heap-unclassified"},
{"name": "JS", "node": "js-main-runtime/"},
{"name": "Images", "node": "explicit/images/"},
]
def median(values):
sorted_ = sorted(values)
# pylint --py3k W1619
med = int(len(sorted_) / 2)
if len(sorted_) % 2:
return sorted_[med]
# pylint --py3k W1619
return (sorted_[med - 1] + sorted_[med]) / 2
def update_checkpoint_paths(checkpoint_files, checkpoints):
"""
Updates checkpoints with memory report file fetched in data_path
:param checkpoint_files: list of files in data_path
:param checkpoints: The checkpoints to update the path of.
"""
target_path = [
["Start-", 0],
["StartSettled-", 0],
["TabsOpen-", -1],
["TabsOpenSettled-", -1],
["TabsOpenForceGC-", -1],
["TabsClosedExtraProcesses-", -1],
["TabsClosed-", -1],
["TabsClosedSettled-", -1],
["TabsClosedForceGC-", -1],
]
for i in range(len(target_path)):
(name, idx) = target_path[i]
paths = sorted([x for x in checkpoint_files if name in x])
if paths:
indices = [i for i, x in enumerate(checkpoints) if name in x["path"]]
if indices:
checkpoints[indices[0]]["path"] = paths[idx]
else:
print("found files but couldn't find {}".format(name))
def create_suite(
name, node, data_path, checkpoints=CHECKPOINTS, alertThreshold=None, extra_opts=None
):
"""
Creates a suite suitable for adding to a perfherder blob. Calculates the
geometric mean of the checkpoint values and adds that to the suite as
well.
:param name: The name of the suite.
:param node: The path of the data node to extract data from.
:param data_path: The directory to retrieve data from.
:param checkpoints: Which checkpoints to include.
:param alertThreshold: The percentage of change that triggers an alert.
"""
suite = {"name": name, "subtests": [], "lowerIsBetter": True, "unit": "bytes"}
if alertThreshold:
suite["alertThreshold"] = alertThreshold
opts = []
if extra_opts:
opts.extend(extra_opts)
if "DMD" in os.environ and os.environ["DMD"]:
opts.append("dmd")
if len(opts) > 0:
suite["extraOptions"] = opts
update_checkpoint_paths(
glob.glob(os.path.join(data_path, "memory-report*")), checkpoints
)
total = 0
for checkpoint in checkpoints:
memory_report_path = os.path.join(data_path, checkpoint["path"])
name_filter = checkpoint.get("name_filter", None)
if checkpoint.get("median"):
process = median
else:
process = sum
if node != "resident":
totals = parse_about_memory.calculate_memory_report_values(
memory_report_path, node, name_filter
)
value = process(totals.values())
else:
# For "resident" we really want RSS of the chrome ("Main") process
# and USS of the child processes. We'll still call it resident
# for simplicity (it's nice to be able to compare RSS of non-e10s
# with RSS + USS of e10s).
totals_rss = parse_about_memory.calculate_memory_report_values(
memory_report_path, node, ["Main"]
)
totals_uss = parse_about_memory.calculate_memory_report_values(
memory_report_path, "resident-unique"
)
value = list(totals_rss.values())[0] + sum(
[v for k, v in six.iteritems(totals_uss) if "Main" not in k]
)
subtest = {
"name": checkpoint["name"],
"value": value,
"lowerIsBetter": True,
"unit": "bytes",
}
suite["subtests"].append(subtest)
total += math.log(subtest["value"])
# Add the geometric mean. For more details on the calculation see:
# pylint --py3k W1619
suite["value"] = math.exp(total / len(checkpoints))
return suite
def create_perf_data(
data_path, perf_suites=PERF_SUITES, checkpoints=CHECKPOINTS, extra_opts=None
):
"""
Builds up a performance data blob suitable for submitting to perfherder.
"""
if ("GCOV_PREFIX" in os.environ) or ("JS_CODE_COVERAGE_OUTPUT_DIR" in os.environ):
print(
"Code coverage is being collected, performance data will not be gathered."
)
return {}
perf_blob = {"framework": {"name": "awsy"}, "suites": []}
for suite in perf_suites:
perf_blob["suites"].append(
create_suite(
suite["name"],
suite["node"],
data_path,
checkpoints,
suite.get("alertThreshold"),
extra_opts,
)
)
return perf_blob
if __name__ == "__main__":
args = sys.argv[1:]
if not args:
print("Usage: process_perf_data.py data_path")
sys.exit(1)
# Determine which revisions we need to process.
data_path = args[0]
perf_blob = create_perf_data(data_path)
print("PERFHERDER_DATA: {}".format(json.dumps(perf_blob)))
sys.exit(0)