blob: bc39541d0c1e2fda3f5a14d3e213494e91e5c1ee [file] [log] [blame]
function BenchmarkState(testInterval)
{
this._currentTimeOffset = 0;
this._stageInterval = testInterval / BenchmarkState.stages.FINISHED;
}
// The enum values and the messages should be in the same order
BenchmarkState.stages = {
WARMING: 0,
SAMPLING: 1,
FINISHED: 2,
messages: [
Strings.text.runningState.warming,
Strings.text.runningState.sampling,
Strings.text.runningState.finished
]
}
BenchmarkState.prototype =
{
_timeOffset: function(stage)
{
return stage * this._stageInterval;
},
_message: function(stage, timeOffset)
{
if (stage == BenchmarkState.stages.FINISHED)
return BenchmarkState.stages.messages[stage];
return BenchmarkState.stages.messages[stage] + "... ("
+ Math.floor((timeOffset - this._timeOffset(stage)) / 1000) + "/"
+ Math.floor((this._timeOffset(stage + 1) - this._timeOffset(stage)) / 1000) + ")";
},
update: function(currentTimeOffset)
{
this._currentTimeOffset = currentTimeOffset;
},
samplingTimeOffset: function()
{
return this._timeOffset(BenchmarkState.stages.SAMPLING);
},
currentStage: function()
{
for (var stage = BenchmarkState.stages.WARMING; stage < BenchmarkState.stages.FINISHED; ++stage) {
if (this._currentTimeOffset < this._timeOffset(stage + 1))
return stage;
}
return BenchmarkState.stages.FINISHED;
},
currentMessage: function()
{
return this._message(this.currentStage(), this._currentTimeOffset);
},
currentProgress: function()
{
return this._currentTimeOffset / this._timeOffset(BenchmarkState.stages.FINISHED);
}
}
function Animator(benchmark, options)
{
this._benchmark = benchmark;
this._options = options;
this._frameCount = 0;
this._dropFrameCount = 1;
this._measureFrameCount = 3;
this._referenceTime = 0;
this._currentTimeOffset = 0;
this._estimator = new KalmanEstimator(60);
}
Animator.prototype =
{
timeDelta: function()
{
return this._currentTimeOffset - this._startTimeOffset;
},
animate: function()
{
var currentTime = performance.now();
if (!this._referenceTime)
this._referenceTime = currentTime;
else
this._currentTimeOffset = currentTime - this._referenceTime;
if (!this._frameCount)
this._startTimeOffset = this._currentTimeOffset;
++this._frameCount;
// Start measuring after dropping _dropFrameCount frames.
if (this._frameCount == this._dropFrameCount)
this._measureTimeOffset = this._currentTimeOffset;
// Drop _dropFrameCount frames and measure the average of _measureFrameCount frames.
if (this._frameCount < this._dropFrameCount + this._measureFrameCount)
return true;
// Get the average FPS of _measureFrameCount frames over measureTimeDelta.
var measureTimeDelta = this._currentTimeOffset - this._measureTimeOffset;
var currentFrameRate = Math.floor(1000 / (measureTimeDelta / this._measureFrameCount));
// Use Kalman filter to get a more non-fluctuating frame rate.
if (this._options["estimated-frame-rate"])
currentFrameRate = this._estimator.estimate(currentFrameRate);
// Adjust the test to reach the desired FPS.
var result = this._benchmark.update(this._currentTimeOffset, this.timeDelta(), currentFrameRate);
// Start the next drop/measure cycle.
this._frameCount = 0;
// If result == 0, no more requestAnimationFrame() will be invoked.
return result;
},
animateLoop: function(timestamp)
{
if (this.animate())
requestAnimationFrame(this.animateLoop.bind(this));
}
}
function Benchmark(options)
{
this._options = options;
this._recordInterval = 200;
this._isSampling = false;
this._controller = new PIDController(this._options["frame-rate"]);
this._sampler = new Sampler(2);
this._state = new BenchmarkState(this._options["test-interval"] * 1000);
}
Benchmark.prototype =
{
// Called from the load event listener or from this.run().
start: function()
{
this._animator.animateLoop();
},
// Called from the animator to adjust the complexity of the test.
update: function(currentTimeOffset, timeDelta, currentFrameRate)
{
this._state.update(currentTimeOffset);
var stage = this._state.currentStage();
if (stage == BenchmarkState.stages.FINISHED) {
this.clear();
return false;
}
if (stage == BenchmarkState.stages.SAMPLING && !this._isSampling) {
this._sampler.startSampling(this._state.samplingTimeOffset());
this._isSampling = true;
}
var tuneValue = 0;
if (this._options["adjustment"] == "fixed") {
if (this._options["complexity"]) {
// this.tune(0) returns the current complexity of the test.
tuneValue = this._options["complexity"] - this.tune(0);
}
}
else if (!(this._isSampling && this._options["adjustment"] == "fixed-after-warmup")) {
// The relationship between frameRate and test complexity is inverse-proportional so we
// need to use the negative of PIDController.tune() to change the complexity of the test.
tuneValue = -this._controller.tune(currentTimeOffset, timeDelta, currentFrameRate);
tuneValue = tuneValue > 0 ? Math.floor(tuneValue) : Math.ceil(tuneValue);
}
var currentComplexity = this.tune(tuneValue);
this.record(currentTimeOffset, currentComplexity, currentFrameRate);
return true;
},
record: function(currentTimeOffset, currentComplexity, currentFrameRate)
{
this._sampler.sample(currentTimeOffset, [currentComplexity, currentFrameRate]);
if (typeof this._recordTimeOffset == "undefined")
this._recordTimeOffset = currentTimeOffset;
var stage = this._state.currentStage();
if (stage != BenchmarkState.stages.FINISHED && currentTimeOffset < this._recordTimeOffset + this._recordInterval)
return;
this.showResults(this._state.currentMessage(), this._state.currentProgress());
this._recordTimeOffset = currentTimeOffset;
},
run: function()
{
this.start();
var promise = new SimplePromise;
var self = this;
function resolveWhenFinished() {
if (typeof self._state != "undefined" && (self._state.currentStage() == BenchmarkState.stages.FINISHED))
return promise.resolve(self._sampler);
setTimeout(resolveWhenFinished.bind(self), 50);
}
resolveWhenFinished();
return promise;
}
}
window.benchmarkClient = {};
// This event listener runs the test if it is loaded outside the benchmark runner.
window.addEventListener("load", function()
{
if (window.self !== window.top)
return;
window.benchmark = window.benchmarkClient.create(null, null, 30000, 50, null, null);
window.benchmark.start();
});
// This function is called from the suite controller run-callback when running the benchmark runner.
window.runBenchmark = function(suite, test, options, recordTable, progressBar)
{
var benchmarkOptions = { complexity: test.complexity };
benchmarkOptions = Utilities.mergeObjects(benchmarkOptions, options);
benchmarkOptions = Utilities.mergeObjects(benchmarkOptions, Utilities.parseParameters());
window.benchmark = window.benchmarkClient.create(suite, test, benchmarkOptions, recordTable, progressBar);
return window.benchmark.run();
}