// There are tests for computeStatistics() located in LayoutTests/fast/harness/perftests
if (window.testRunner) {
testRunner.waitUntilDone();
testRunner.dumpAsText();
}
(function () {
var logLines = null;
var completedIterations = -1;
var callsPerIteration = 1;
var currentTest = null;
var results = [];
var jsHeapResults = [];
var iterationCount = undefined;
var PerfTestRunner = {};
// To make the benchmark results predictable, we replace Math.random with a
// 100% deterministic alternative.
PerfTestRunner.randomSeed = PerfTestRunner.initialRandomSeed = 49734321;
PerfTestRunner.resetRandomSeed = function() {
PerfTestRunner.randomSeed = PerfTestRunner.initialRandomSeed
}
PerfTestRunner.random = Math.random = function() {
// Robert Jenkins' 32 bit integer hash function.
var randomSeed = PerfTestRunner.randomSeed;
randomSeed = ((randomSeed + 0x7ed55d16) + (randomSeed << 12)) & 0xffffffff;
randomSeed = ((randomSeed ^ 0xc761c23c) ^ (randomSeed >>> 19)) & 0xffffffff;
randomSeed = ((randomSeed + 0x165667b1) + (randomSeed << 5)) & 0xffffffff;
randomSeed = ((randomSeed + 0xd3a2646c) ^ (randomSeed << 9)) & 0xffffffff;
randomSeed = ((randomSeed + 0xfd7046c5) + (randomSeed << 3)) & 0xffffffff;
randomSeed = ((randomSeed ^ 0xb55a4f09) ^ (randomSeed >>> 16)) & 0xffffffff;
PerfTestRunner.randomSeed = randomSeed;
return (randomSeed & 0xfffffff) / 0x10000000;
};
PerfTestRunner.now = window.performance && window.performance.now ? function () { return window.performance.now(); } : Date.now;
PerfTestRunner.logInfo = function (text) {
if (!window.testRunner)
this.log(text);
}
PerfTestRunner.loadFile = function (path) {
var xhr = new XMLHttpRequest();
xhr.open("GET", path, false);
xhr.send(null);
return xhr.responseText;
}
PerfTestRunner.computeStatistics = function (times, unit) {
var data = times.slice();
// Add values from the smallest to the largest to avoid the loss of significance
data.sort(function(a,b){return a-b;});
var middle = Math.floor(data.length / 2);
var result = {
min: data[0],
max: data[data.length - 1],
median: data.length % 2 ? data[middle] : (data[middle - 1] + data[middle]) / 2,
};
// Compute the mean and variance using Knuth's online algorithm (has good numerical stability).
var squareSum = 0;
result.values = times;
result.mean = 0;
for (var i = 0; i < data.length; ++i) {
var x = data[i];
var delta = x - result.mean;
var sweep = i + 1.0;
result.mean += delta / sweep;
squareSum += delta * (x - result.mean);
}
result.variance = data.length <= 1 ? 0 : squareSum / (data.length - 1);
result.stdev = Math.sqrt(result.variance);
result.unit = unit || "ms";
return result;
}
PerfTestRunner.logStatistics = function (values, unit, title) {
var statistics = this.computeStatistics(values, unit);
this.log("");
this.log(title);
if (statistics.values)
this.log("values " + statistics.values.join(", ") + " " + statistics.unit);
this.log("avg " + statistics.mean + " " + statistics.unit);
this.log("median " + statistics.median + " " + statistics.unit);
this.log("stdev " + statistics.stdev + " " + statistics.unit);
this.log("min " + statistics.min + " " + statistics.unit);
this.log("max " + statistics.max + " " + statistics.unit);
}
function getUsedJSHeap() {
return console.memory.usedJSHeapSize;
}
PerfTestRunner.gc = function () {
if (window.GCController)
window.GCController.collectAll();
else {
function gcRec(n) {
if (n < 1)
return {};
var temp = {i: "ab" + i + (i / 100000)};
temp += "foo";
gcRec(n-1);
}
for (var i = 0; i < 1000; i++)
gcRec(10);
}
};
function logInDocument(text) {
if (!document.getElementById("log")) {
var pre = document.createElement("pre");
pre.id = "log";
document.body.appendChild(pre);
}
document.getElementById("log").innerHTML += text + "\n";
}
PerfTestRunner.log = function (text) {
if (logLines)
logLines.push(text);
else
logInDocument(text);
}
PerfTestRunner.logFatalError = function (text) {
PerfTestRunner.log("FATAL: " + text);
finish();
}
PerfTestRunner.assert_true = function (cond,text) {
if (cond)
return;
PerfTestRunner.logFatalError(text);
}
PerfTestRunner.assert_false = function (cond,text) {
PerfTestRunner.assert_true(!cond,text);
}
PerfTestRunner.formatException = function (text, exception) {
return "Got an exception while " + text +
" with name=" + exception.name +
", message=" + exception.message +
"\n" + exception.stack;
}
PerfTestRunner.logException = function (text, exception) {
PerfTestRunner.logFatalError(PerfTestRunner.formatException(text, exception));
}
PerfTestRunner.forceLayout = function(doc) {
doc = doc || document;
if (doc.body)
doc.body.offsetHeight;
else if (doc.documentElement)
doc.documentElement.offsetHeight;
};
function start(test, scheduler, runner) {
if (!test || !runner) {
PerfTestRunner.logFatalError("Got a bad test object.");
return;
}
currentTest = test;
if (test.tracingCategories && !test.traceEventsToMeasure) {
PerfTestRunner.logFatalError("test's tracingCategories is " +
"specified but test's traceEventsToMeasure is empty");
return;
}
if (test.traceEventsToMeasure && !test.tracingCategories) {
PerfTestRunner.logFatalError("test's traceEventsToMeasure is " +
"specified but test's tracingCategories is empty");
return;
}
iterationCount = test.iterationCount || (window.testRunner ? 5 : 20);
if (test.warmUpCount && test.warmUpCount > 0)
completedIterations = -test.warmUpCount;
logLines = PerfTestRunner.bufferedLog || window.testRunner ? [] : null;
// Tests that run in workers are not impacted by the iteration control.
if (!currentTest.runInWorker) {
PerfTestRunner.log("Running " + iterationCount + " times");
}
if (test.doNotIgnoreInitialRun)
completedIterations++;
if (window.testRunner && window.testRunner.telemetryIsRunning) {
testRunner.waitForTelemetry(test.tracingCategories, function() {
scheduleNextRun(scheduler, runner);
});
return;
}
if (test.tracingCategories) {
PerfTestRunner.log("Tracing based metrics are specified but " +
"tracing is not supported on this platform. To get those " +
"metrics from this test, you can run the test using " +
"tools/perf/run_benchmarks script.");
}
scheduleNextRun(scheduler, runner);
}
function scheduleNextRun(scheduler, runner) {
if (!scheduler) {
// This is an async measurement test which has its own scheduler.
try {
runner();
} catch (exception) {
PerfTestRunner.logException("running test.run", exception);
}
return;
}
scheduler(function () {
// This will be used by tools/perf/benchmarks/blink_perf.py to find
// traces during the measured runs.
if (completedIterations >= 0)
console.time("blink_perf");
try {
if (currentTest.setup)
currentTest.setup();
var measuredValue = runner();
if (currentTest.teardown)
currentTest.teardown();
} catch (exception) {
PerfTestRunner.logException("running test.run", exception);
return;
}
completedIterations++;
try {
ignoreWarmUpAndLog(measuredValue);
} catch (exception) {
PerfTestRunner.logException("logging the result", exception);
return;
}
if (completedIterations < iterationCount)
scheduleNextRun(scheduler, runner);
else
finish();
});
}
function ignoreWarmUpAndLog(measuredValue) {
var labeledResult = measuredValue + " " + PerfTestRunner.unit;
// Tests that run in workers are not impacted by the iteration control.
if (!currentTest.runInWorker && completedIterations <= 0)
PerfTestRunner.log("Ignoring warm-up run (" + labeledResult + ")");
else {
results.push(measuredValue);
if (window.internals && !currentTest.doNotMeasureMemoryUsage) {
jsHeapResults.push(getUsedJSHeap());
}
PerfTestRunner.log(labeledResult);
}
}
function finish() {
try {
// The blink_perf timer is only started for non-worker test.
if (!currentTest.runInWorker)
console.timeEnd("blink_perf");
if (currentTest.description)
PerfTestRunner.log("Description: " + currentTest.description);
PerfTestRunner.logStatistics(results, PerfTestRunner.unit, "Time:");
if (jsHeapResults.length) {
PerfTestRunner.logStatistics(jsHeapResults, "bytes", "JS Heap:");
}
if (logLines)
logLines.forEach(logInDocument);
window.scrollTo(0, document.body.offsetHeight);
if (currentTest.done)
currentTest.done();
} catch (exception) {
logInDocument(PerfTestRunner.formatException("finalizing the test", exception));
}
if (window.testRunner) {
if (currentTest.traceEventsToMeasure &&
testRunner.telemetryIsRunning) {
testRunner.stopTracingAndMeasure(
currentTest.traceEventsToMeasure, function() {
testRunner.notifyDone();
});
} else {
testRunner.notifyDone();
}
}
}
PerfTestRunner.startMeasureValuesAsync = function (test) {
PerfTestRunner.unit = test.unit;
start(test, undefined, function() { test.run() });
}
PerfTestRunner.measureValueAsync = function (measuredValue) {
completedIterations++;
try {
ignoreWarmUpAndLog(measuredValue);
} catch (exception) {
PerfTestRunner.logFatalError("Got an exception while logging the result with name=" + exception.name + ", message=" + exception.message);
return;
}
if (completedIterations >= iterationCount)
finish();
}
PerfTestRunner.addRunTestStartMarker = function () {
if (!window.testRunner || !window.testRunner.telemetryIsRunning)
return;
if (completedIterations < 0)
console.time('blink_perf.runTest.warmup');
else
console.time('blink_perf.runTest');
};
PerfTestRunner.addRunTestEndMarker = function () {
if (!window.testRunner || !window.testRunner.telemetryIsRunning)
return;
if (completedIterations < 0)
console.timeEnd('blink_perf.runTest.warmup');
else
console.timeEnd('blink_perf.runTest');
};
PerfTestRunner.measureFrameTime = function (test) {
PerfTestRunner.unit = "ms";
PerfTestRunner.bufferedLog = true;
test.warmUpCount = test.warmUpCount || 5;
test.iterationCount = test.iterationCount || 10;
// Force gc before starting the test to avoid the measured time from
// being affected by gc performance. See crbug.com/667811#c16.
PerfTestRunner.gc();
start(test, requestAnimationFrame, measureFrameTimeOnce);
}
PerfTestRunner.measureInnerRAFTime = function (test) {
PerfTestRunner.unit = "ms";
PerfTestRunner.bufferedLog = true;
test.warmUpCount = test.warmUpCount || 5;
test.iterationCount = test.iterationCount || 10;
// Force gc before starting the test to avoid the measured time from
// being affected by gc performance. See crbug.com/667811#c16.
PerfTestRunner.gc();
start(test, requestAnimationFrame, measureTimeOnce);
}
var lastFrameTime = -1;
function measureFrameTimeOnce() {
var now = PerfTestRunner.now();
var result = lastFrameTime == -1 ? -1 : now - lastFrameTime;
lastFrameTime = now;
PerfTestRunner.addRunTestStartMarker();
var returnValue = currentTest.run();
requestAnimationFrame(function() {
PerfTestRunner.addRunTestEndMarker();
});
if (returnValue - 0 === returnValue) {
if (returnValue < 0)
PerfTestRunner.log("runFunction returned a negative value: " + returnValue);
return returnValue;
}
return result;
}
PerfTestRunner.measureTime = function (test) {
PerfTestRunner.unit = "ms";
PerfTestRunner.bufferedLog = true;
start(test, zeroTimeoutScheduler, measureTimeOnce);
}
PerfTestRunner.measureValue = function (test) {
PerfTestRunner.unit = test.unit;
start(test, zeroTimeoutScheduler, measureTimeOnce);
}
function zeroTimeoutScheduler(task) {
setTimeout(task, 0);
}
function measureTimeOnce() {
// Force gc before measuring time to avoid interference between tests.
PerfTestRunner.gc();
PerfTestRunner.addRunTestStartMarker();
var start = PerfTestRunner.now();
var returnValue = currentTest.run();
var end = PerfTestRunner.now();
PerfTestRunner.addRunTestEndMarker();
if (returnValue - 0 === returnValue) {
if (returnValue < 0)
PerfTestRunner.log("runFunction returned a negative value: " + returnValue);
return returnValue;
}
return end - start;
}
PerfTestRunner.measureRunsPerSecond = function (test) {
PerfTestRunner.unit = "runs/s";
start(test, zeroTimeoutScheduler, measureRunsPerSecondOnce);
}
function measureRunsPerSecondOnce() {
var timeToRun = 750;
var totalTime = 0;
var numberOfRuns = 0;
while (totalTime < timeToRun) {
totalTime += callRunAndMeasureTime(callsPerIteration);
numberOfRuns += callsPerIteration;
if (completedIterations < 0 && totalTime < 100)
callsPerIteration = Math.max(10, 2 * callsPerIteration);
}
return numberOfRuns * 1000 / totalTime;
}
function callRunAndMeasureTime(callsPerIteration) {
// Force gc before measuring time to avoid interference between tests.
PerfTestRunner.gc();
var startTime = PerfTestRunner.now();
for (var i = 0; i < callsPerIteration; i++)
currentTest.run();
return PerfTestRunner.now() - startTime;
}
PerfTestRunner.measurePageLoadTime = function(test) {
var file = PerfTestRunner.loadFile(test.path);
test.run = function() {
if (!test.chunkSize)
this.chunkSize = 50000;
var chunks = [];
// The smaller the chunks the more style resolves we do.
// Smaller chunk sizes will show more samples in style resolution.
// Larger chunk sizes will show more samples in line layout.
// Smaller chunk sizes run slower overall, as the per-chunk overhead is high.
var chunkCount = Math.ceil(file.length / this.chunkSize);
for (var chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++) {
var chunk = file.substr(chunkIndex * this.chunkSize, this.chunkSize);
chunks.push(chunk);
}
PerfTestRunner.logInfo("Testing " + file.length + " byte document in " + chunkCount + " " + this.chunkSize + " byte chunks.");
var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
iframe.sandbox = ''; // Prevent external loads which could cause write() to return before completing the parse.
iframe.style.width = "600px"; // Have a reasonable size so we're not line-breaking on every character.
iframe.style.height = "800px";
iframe.contentDocument.open();
for (var chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
iframe.contentDocument.write(chunks[chunkIndex]);
PerfTestRunner.forceLayout(iframe.contentDocument);
}
iframe.contentDocument.close();
document.body.removeChild(iframe);
};
PerfTestRunner.measureTime(test);
}
// Used for tests that run in workers.
// 1. Call this method to trigger the test. It should be used together
// with |WorkerTestHelper.measureRunsPerSecond()| which is defined in
// src/third_party/blink/perf_tests/resources/worker-test-helper.js.
// 2. The iteration control parameters (test.iterationCount,
// test.doNotIgnoreInitialRun, and test.warmUpCount) are ignored.
// Use parameters of |measureRunsPerSecond()| to control iteration.
// 3. Test result should be sent to the page where the test is triggered.
// Then the result should be recorded by |recordResultFromWorker()| to
// finish the test.
PerfTestRunner.startMeasureValuesInWorker = function (test) {
PerfTestRunner.unit = test.unit;
test.runInWorker = true;
start(test, undefined, function() { test.run(); });
}
// Used for tests that run in workers.
// This method records the result posted from worker thread and finishes the test.
PerfTestRunner.recordResultFromWorker = function(result) {
if (result.error) {
PerfTestRunner.logFatalError(result.error);
return;
}
PerfTestRunner.log("Running " + result.values.length + " times");
try {
result.values.forEach((value) => {
ignoreWarmUpAndLog(value);
});
} catch (exception) {
PerfTestRunner.logFatalError("Got an exception while logging the result with name=" + exception.name + ", message=" + exception.message);
return;
}
finish();
}
window.PerfTestRunner = PerfTestRunner;
})();