mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-07 21:43:24 +00:00
d8f842d8c1
This changes the default so that extra GC poisoning is off in nightly opt builds unless enabled by an environment variable. This should give us more useful telemetry on nightly performance and make it easier for people trying to benchmark our browser. The poisoning is still enabled in debug builds. The variable is renamed from JSGC_DISABLE_POISONING to JSGC_EXTRA_POISONING. Differential Revision: https://phabricator.services.mozilla.com/D108419
427 lines
13 KiB
Python
427 lines
13 KiB
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/.
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
import six
|
|
import json
|
|
import os
|
|
import re
|
|
import shutil
|
|
import sys
|
|
from abc import ABCMeta, abstractmethod, abstractproperty
|
|
from argparse import ArgumentParser
|
|
from collections import defaultdict
|
|
|
|
from mozbuild.base import MozbuildObject, BuildEnvironmentNotFoundException
|
|
from mozprocess import ProcessHandler
|
|
|
|
here = os.path.abspath(os.path.dirname(__file__))
|
|
build = MozbuildObject.from_environment(cwd=here)
|
|
|
|
JSSHELL_NOT_FOUND = """
|
|
Could not detect a JS shell. Either make sure you have a non-artifact build
|
|
with `ac_add_options --enable-js-shell` or specify it with `--binary`.
|
|
""".strip()
|
|
|
|
|
|
@six.add_metaclass(ABCMeta)
|
|
class Benchmark(object):
|
|
lower_is_better = True
|
|
should_alert = True
|
|
|
|
def __init__(self, shell, args=None, shell_name=None):
|
|
self.shell = shell
|
|
self.args = args
|
|
self.shell_name = shell_name
|
|
|
|
@abstractproperty
|
|
def unit(self):
|
|
"""Returns the unit of measurement of the benchmark."""
|
|
|
|
@abstractproperty
|
|
def name(self):
|
|
"""Returns the string name of the benchmark."""
|
|
|
|
@abstractproperty
|
|
def path(self):
|
|
"""Return the path to the benchmark relative to topsrcdir."""
|
|
|
|
@abstractmethod
|
|
def process_line(self, line):
|
|
"""Process a line of stdout from the benchmark."""
|
|
|
|
@abstractmethod
|
|
def collect_results(self):
|
|
"""Build the result after the process has finished."""
|
|
|
|
@property
|
|
def command(self):
|
|
"""Returns the command to run as a list."""
|
|
cmd = [self.shell]
|
|
if self.args:
|
|
cmd += self.args
|
|
return cmd
|
|
|
|
@property
|
|
def version(self):
|
|
if self._version:
|
|
return self._version
|
|
|
|
with open(os.path.join(self.path, "VERSION"), "r") as fh:
|
|
self._version = fh.read().strip("\r\n\r\n \t")
|
|
return self._version
|
|
|
|
def reset(self):
|
|
"""Resets state between runs."""
|
|
name = self.name
|
|
if self.shell_name:
|
|
name = "{}-{}".format(name, self.shell_name)
|
|
|
|
self.perfherder_data = {
|
|
"framework": {
|
|
"name": "js-bench",
|
|
},
|
|
"suites": [
|
|
{
|
|
"lowerIsBetter": self.lower_is_better,
|
|
"name": name,
|
|
"shouldAlert": self.should_alert,
|
|
"subtests": [],
|
|
"unit": self.unit,
|
|
"value": None,
|
|
},
|
|
],
|
|
}
|
|
self.suite = self.perfherder_data["suites"][0]
|
|
|
|
def _provision_benchmark_script(self):
|
|
if os.path.isdir(self.path):
|
|
return
|
|
|
|
# Some benchmarks may have been downloaded from a fetch task, make
|
|
# sure they get copied over.
|
|
fetches_dir = os.environ.get("MOZ_FETCHES_DIR")
|
|
if fetches_dir and os.path.isdir(fetches_dir):
|
|
fetchdir = os.path.join(fetches_dir, self.name)
|
|
if os.path.isdir(fetchdir):
|
|
shutil.copytree(fetchdir, self.path)
|
|
|
|
def run(self):
|
|
self.reset()
|
|
|
|
# Update the environment variables
|
|
env = os.environ.copy()
|
|
|
|
process_args = {
|
|
"cmd": self.command,
|
|
"cwd": self.path,
|
|
"onFinish": self.collect_results,
|
|
"processOutputLine": self.process_line,
|
|
"stream": sys.stdout,
|
|
"env": env,
|
|
"universal_newlines": True,
|
|
}
|
|
proc = ProcessHandler(**process_args)
|
|
proc.run()
|
|
return proc.wait()
|
|
|
|
|
|
class RunOnceBenchmark(Benchmark):
|
|
def collect_results(self):
|
|
bench_total = 0
|
|
# NOTE: for this benchmark we run the test once, so we have a single value array
|
|
for bench, scores in self.scores.items():
|
|
for score, values in scores.items():
|
|
test_name = "{}-{}".format(self.name, score)
|
|
# pylint --py3k W1619
|
|
mean = sum(values) / len(values)
|
|
self.suite["subtests"].append({"name": test_name, "value": mean})
|
|
bench_total += int(sum(values))
|
|
self.suite["value"] = bench_total
|
|
|
|
|
|
class Ares6(Benchmark):
|
|
name = "ares6"
|
|
path = os.path.join("third_party", "webkit", "PerformanceTests", "ARES-6")
|
|
unit = "ms"
|
|
|
|
@property
|
|
def command(self):
|
|
cmd = super(Ares6, self).command
|
|
return cmd + ["cli.js"]
|
|
|
|
def reset(self):
|
|
super(Ares6, self).reset()
|
|
|
|
self.bench_name = None
|
|
self.last_summary = None
|
|
# Scores are of the form:
|
|
# {<bench_name>: {<score_name>: [<values>]}}
|
|
self.scores = defaultdict(lambda: defaultdict(list))
|
|
|
|
def _try_find_score(self, score_name, line):
|
|
m = re.search(score_name + ":\s*(\d+\.?\d*?) (\+-)?.+", line)
|
|
if not m:
|
|
return False
|
|
|
|
score = m.group(1)
|
|
self.scores[self.bench_name][score_name].append(float(score))
|
|
return True
|
|
|
|
def process_line(self, line):
|
|
m = re.search("Running... (.+) \(.+\)", line)
|
|
if m:
|
|
self.bench_name = m.group(1)
|
|
return
|
|
|
|
if self._try_find_score("firstIteration", line):
|
|
return
|
|
|
|
if self._try_find_score("averageWorstCase", line):
|
|
return
|
|
|
|
if self._try_find_score("steadyState", line):
|
|
return
|
|
|
|
m = re.search("summary:\s*(\d+\.?\d*?) (\+-)?.+", line)
|
|
if m:
|
|
self.last_summary = float(m.group(1))
|
|
|
|
def collect_results(self):
|
|
for bench, scores in self.scores.items():
|
|
for score, values in scores.items():
|
|
# pylint --py3k W1619
|
|
mean = sum(values) / len(values)
|
|
test_name = "{}-{}".format(bench, score)
|
|
self.suite["subtests"].append({"name": test_name, "value": mean})
|
|
|
|
if self.last_summary:
|
|
self.suite["value"] = self.last_summary
|
|
|
|
|
|
class SixSpeed(RunOnceBenchmark):
|
|
name = "six-speed"
|
|
path = os.path.join("third_party", "webkit", "PerformanceTests", "six-speed")
|
|
unit = "ms"
|
|
|
|
@property
|
|
def command(self):
|
|
cmd = super(SixSpeed, self).command
|
|
return cmd + ["test.js"]
|
|
|
|
def reset(self):
|
|
super(SixSpeed, self).reset()
|
|
|
|
# Scores are of the form:
|
|
# {<bench_name>: {<score_name>: [<values>]}}
|
|
self.scores = defaultdict(lambda: defaultdict(list))
|
|
|
|
def process_line(self, output):
|
|
m = re.search("(.+): (\d+)", output)
|
|
if not m:
|
|
return
|
|
subtest = m.group(1)
|
|
score = m.group(2)
|
|
if subtest not in self.scores[self.name]:
|
|
self.scores[self.name][subtest] = []
|
|
self.scores[self.name][subtest].append(int(score))
|
|
|
|
|
|
class SunSpider(RunOnceBenchmark):
|
|
name = "sunspider"
|
|
path = os.path.join(
|
|
"third_party", "webkit", "PerformanceTests", "SunSpider", "sunspider-0.9.1"
|
|
)
|
|
unit = "ms"
|
|
|
|
@property
|
|
def command(self):
|
|
cmd = super(SunSpider, self).command
|
|
return cmd + ["sunspider-standalone-driver.js"]
|
|
|
|
def reset(self):
|
|
super(SunSpider, self).reset()
|
|
|
|
# Scores are of the form:
|
|
# {<bench_name>: {<score_name>: [<values>]}}
|
|
self.scores = defaultdict(lambda: defaultdict(list))
|
|
|
|
def process_line(self, output):
|
|
m = re.search("(.+): (\d+)", output)
|
|
if not m:
|
|
return
|
|
subtest = m.group(1)
|
|
score = m.group(2)
|
|
if subtest not in self.scores[self.name]:
|
|
self.scores[self.name][subtest] = []
|
|
self.scores[self.name][subtest].append(int(score))
|
|
|
|
|
|
class WebToolingBenchmark(Benchmark):
|
|
name = "web-tooling-benchmark"
|
|
path = os.path.join(
|
|
"third_party", "webkit", "PerformanceTests", "web-tooling-benchmark"
|
|
)
|
|
main_js = "cli.js"
|
|
unit = "score"
|
|
lower_is_better = False
|
|
subtests_lower_is_better = False
|
|
|
|
@property
|
|
def command(self):
|
|
cmd = super(WebToolingBenchmark, self).command
|
|
return cmd + [self.main_js]
|
|
|
|
def reset(self):
|
|
super(WebToolingBenchmark, self).reset()
|
|
|
|
# Scores are of the form:
|
|
# {<bench_name>: {<score_name>: [<values>]}}
|
|
self.scores = defaultdict(lambda: defaultdict(list))
|
|
|
|
def process_line(self, output):
|
|
m = re.search(" +([a-zA-Z].+): +([.0-9]+) +runs/sec", output)
|
|
if not m:
|
|
return
|
|
subtest = m.group(1)
|
|
score = m.group(2)
|
|
if subtest not in self.scores[self.name]:
|
|
self.scores[self.name][subtest] = []
|
|
self.scores[self.name][subtest].append(float(score))
|
|
|
|
def collect_results(self):
|
|
# NOTE: for this benchmark we run the test once, so we have a single value array
|
|
for bench, scores in self.scores.items():
|
|
for score_name, values in scores.items():
|
|
test_name = "{}-{}".format(self.name, score_name)
|
|
# pylint --py3k W1619
|
|
mean = sum(values) / len(values)
|
|
self.suite["subtests"].append(
|
|
{
|
|
"lowerIsBetter": self.subtests_lower_is_better,
|
|
"name": test_name,
|
|
"value": mean,
|
|
}
|
|
)
|
|
if score_name == "mean":
|
|
bench_mean = mean
|
|
self.suite["value"] = bench_mean
|
|
|
|
def run(self):
|
|
self._provision_benchmark_script()
|
|
return super(WebToolingBenchmark, self).run()
|
|
|
|
|
|
class Octane(RunOnceBenchmark):
|
|
name = "octane"
|
|
path = os.path.join("third_party", "webkit", "PerformanceTests", "octane")
|
|
unit = "score"
|
|
lower_is_better = False
|
|
|
|
@property
|
|
def command(self):
|
|
cmd = super(Octane, self).command
|
|
return cmd + ["run.js"]
|
|
|
|
def reset(self):
|
|
super(Octane, self).reset()
|
|
|
|
# Scores are of the form:
|
|
# {<bench_name>: {<score_name>: [<values>]}}
|
|
self.scores = defaultdict(lambda: defaultdict(list))
|
|
|
|
def process_line(self, output):
|
|
m = re.search("(.+): (\d+)", output)
|
|
if not m:
|
|
return
|
|
subtest = m.group(1)
|
|
score = m.group(2)
|
|
if subtest.startswith("Score"):
|
|
subtest = "score"
|
|
if subtest not in self.scores[self.name]:
|
|
self.scores[self.name][subtest] = []
|
|
self.scores[self.name][subtest].append(int(score))
|
|
|
|
def collect_results(self):
|
|
# NOTE: for this benchmark we run the test once, so we have a single value array
|
|
for bench, scores in self.scores.items():
|
|
for score_name, values in scores.items():
|
|
test_name = "{}-{}".format(self.name, score_name)
|
|
# pylint --py3k W1619
|
|
mean = sum(values) / len(values)
|
|
self.suite["subtests"].append({"name": test_name, "value": mean})
|
|
if score_name == "score":
|
|
bench_score = mean
|
|
self.suite["value"] = bench_score
|
|
|
|
def run(self):
|
|
self._provision_benchmark_script()
|
|
return super(Octane, self).run()
|
|
|
|
|
|
all_benchmarks = {
|
|
"ares6": Ares6,
|
|
"six-speed": SixSpeed,
|
|
"sunspider": SunSpider,
|
|
"web-tooling-benchmark": WebToolingBenchmark,
|
|
"octane": Octane,
|
|
}
|
|
|
|
|
|
def run(benchmark, binary=None, extra_args=None, perfherder=None):
|
|
if not binary:
|
|
try:
|
|
binary = os.path.join(build.bindir, "js" + build.substs["BIN_SUFFIX"])
|
|
except BuildEnvironmentNotFoundException:
|
|
binary = None
|
|
|
|
if not binary or not os.path.isfile(binary):
|
|
print(JSSHELL_NOT_FOUND)
|
|
return 1
|
|
|
|
bench = all_benchmarks.get(benchmark)(
|
|
binary, args=extra_args, shell_name=perfherder
|
|
)
|
|
res = bench.run()
|
|
|
|
if perfherder:
|
|
print("PERFHERDER_DATA: {}".format(json.dumps(bench.perfherder_data)))
|
|
return res
|
|
|
|
|
|
def get_parser():
|
|
parser = ArgumentParser()
|
|
parser.add_argument(
|
|
"benchmark",
|
|
choices=list(all_benchmarks),
|
|
help="The name of the benchmark to run.",
|
|
)
|
|
parser.add_argument(
|
|
"-b", "--binary", default=None, help="Path to the JS shell binary to use."
|
|
)
|
|
parser.add_argument(
|
|
"--arg",
|
|
dest="extra_args",
|
|
action="append",
|
|
default=None,
|
|
help="Extra arguments to pass to the JS shell.",
|
|
)
|
|
parser.add_argument(
|
|
"--perfherder",
|
|
default=None,
|
|
help="Log PERFHERDER_DATA to stdout using the given suite name.",
|
|
)
|
|
return parser
|
|
|
|
|
|
def cli(args=sys.argv[1:]):
|
|
parser = get_parser()
|
|
args = parser.parser_args(args)
|
|
return run(**vars(args))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(cli())
|