2 Controller = Utilities.createClass(
3 function(testLength, benchmark, seriesCount)
5 // Initialize timestamps relative to the start of the benchmark
6 // In start() the timestamps are offset by the start timestamp
7 this._startTimestamp = 0;
8 this._endTimestamp = testLength;
9 // Default data series: timestamp, complexity, estimatedFrameLength
10 this._sampler = new Sampler(seriesCount || 3, 60 * testLength, this);
11 this._estimator = new SimpleKalmanEstimator(benchmark.options["kalman-process-error"], benchmark.options["kalman-measurement-error"]);
13 this.initialComplexity = 0;
16 start: function(stage, startTimestamp)
18 this._startTimestamp = startTimestamp;
19 this._endTimestamp += startTimestamp;
20 this.recordFirstSample(stage, startTimestamp);
23 recordFirstSample: function(stage, startTimestamp)
25 this._sampler.record(startTimestamp, stage.complexity(), -1);
26 this._sampler.mark(Strings.json.samplingTimeOffset, { time: 0 });
29 update: function(stage, timestamp)
31 this._estimator.sample(timestamp - this._sampler.samples[0][this._sampler.sampleCount - 1]);
32 this._sampler.record(timestamp, stage.complexity(), 1000 / this._estimator.estimate);
35 shouldStop: function(timestamp)
37 return timestamp > this._endTimestamp;
42 return this._sampler.process();
45 processSamples: function(results)
47 var complexityExperiment = new Experiment;
48 var smoothedFPSExperiment = new Experiment;
50 var samples = this._sampler.samples;
52 var samplingIndex = 0;
53 var samplingMark = this._sampler.marks[Strings.json.samplingTimeOffset];
55 samplingIndex = samplingMark.index;
56 results[Strings.json.samplingTimeOffset] = samplingMark.time;
59 results[Strings.json.samples] = samples[0].map(function(timestamp, i) {
61 // Represent time in seconds
62 time: timestamp - this._startTimestamp,
63 complexity: samples[1][i]
66 // time offsets represented as FPS
70 result.fps = 1000 / (timestamp - samples[0][i - 1]);
72 if (samples[2][i] != -1)
73 result.smoothedFPS = samples[2][i];
75 // Don't start adding data to the experiments until we reach the sampling timestamp
76 if (i >= samplingIndex) {
77 complexityExperiment.sample(result.complexity);
78 if (result.smoothedFPS && result.smoothedFPS != -1)
79 smoothedFPSExperiment.sample(result.smoothedFPS);
85 results[Strings.json.score] = complexityExperiment.score(Experiment.defaults.CONCERN);
87 var complexityResults = {};
88 results[Strings.json.experiments.complexity] = complexityResults;
89 complexityResults[Strings.json.measurements.average] = complexityExperiment.mean();
90 complexityResults[Strings.json.measurements.concern] = complexityExperiment.concern(Experiment.defaults.CONCERN);
91 complexityResults[Strings.json.measurements.stdev] = complexityExperiment.standardDeviation();
92 complexityResults[Strings.json.measurements.percent] = complexityExperiment.percentage();
94 var smoothedFPSResults = {};
95 results[Strings.json.experiments.frameRate] = smoothedFPSResults;
96 smoothedFPSResults[Strings.json.measurements.average] = smoothedFPSExperiment.mean();
97 smoothedFPSResults[Strings.json.measurements.concern] = smoothedFPSExperiment.concern(Experiment.defaults.CONCERN);
98 smoothedFPSResults[Strings.json.measurements.stdev] = smoothedFPSExperiment.standardDeviation();
99 smoothedFPSResults[Strings.json.measurements.percent] = smoothedFPSExperiment.percentage();
103 FixedComplexityController = Utilities.createSubclass(Controller,
104 function(testInterval, benchmark)
106 Controller.call(this, testInterval, benchmark);
107 this.initialComplexity = benchmark.options["complexity"];
111 AdaptiveController = Utilities.createSubclass(Controller,
112 function(testInterval, benchmark)
114 // Data series: timestamp, complexity, estimatedIntervalFrameLength
115 Controller.call(this, testInterval, benchmark);
117 // All tests start at 0, so we expect to see 60 fps quickly.
118 this._samplingTimestamp = testInterval / 2;
119 this._startedSampling = false;
120 this._targetFrameRate = benchmark.options["frame-rate"];
121 this._pid = new PIDController(this._targetFrameRate);
123 this._intervalFrameCount = 0;
124 this._numberOfFramesToMeasurePerInterval = 4;
127 start: function(stage, startTimestamp)
129 Controller.prototype.start.call(this, stage, startTimestamp);
131 this._samplingTimestamp += startTimestamp;
132 this._intervalTimestamp = startTimestamp;
135 recordFirstSample: function(stage, startTimestamp)
137 this._sampler.record(startTimestamp, stage.complexity(), -1);
140 update: function(stage, timestamp)
142 if (!this._startedSampling && timestamp > this._samplingTimestamp) {
143 this._startedSampling = true;
144 this._sampler.mark(Strings.json.samplingTimeOffset, {
145 time: this._samplingTimestamp - this._startTimestamp
149 // Start the work for the next frame.
150 ++this._intervalFrameCount;
152 if (this._intervalFrameCount < this._numberOfFramesToMeasurePerInterval) {
153 this._sampler.record(timestamp, stage.complexity(), -1);
157 // Adjust the test to reach the desired FPS.
158 var intervalLength = timestamp - this._intervalTimestamp;
159 this._estimator.sample(intervalLength / this._numberOfFramesToMeasurePerInterval);
160 var intervalEstimatedFrameRate = 1000 / this._estimator.estimate;
161 var tuneValue = -this._pid.tune(timestamp - this._startTimestamp, intervalLength, intervalEstimatedFrameRate);
162 tuneValue = tuneValue > 0 ? Math.floor(tuneValue) : Math.ceil(tuneValue);
163 stage.tune(tuneValue);
165 this._sampler.record(timestamp, stage.complexity(), intervalEstimatedFrameRate);
167 // Start the next interval.
168 this._intervalFrameCount = 0;
169 this._intervalTimestamp = timestamp;
172 processSamples: function(results)
174 Controller.prototype.processSamples.call(this, results);
175 results[Strings.json.targetFPS] = this._targetFrameRate;
179 Stage = Utilities.createClass(
184 initialize: function(benchmark)
186 this._benchmark = benchmark;
187 this._element = document.getElementById("stage");
188 this._element.setAttribute("width", document.body.offsetWidth);
189 this._element.setAttribute("height", document.body.offsetHeight);
190 this._size = Point.elementClientSize(this._element).subtract(Insets.elementPadding(this._element).size);
195 return this._element;
203 complexity: function()
210 throw "Not implemented";
215 throw "Not implemented";
220 return this.tune(-this.tune(0));
224 Utilities.extendObject(Stage, {
225 random: function(min, max)
227 return (Math.random() * (max - min)) + min;
230 randomBool: function()
232 return !!Math.round(this.random(0, 1));
235 randomInt: function(min, max)
237 return Math.round(this.random(min, max));
240 randomPosition: function(maxPosition)
242 return new Point(this.randomInt(0, maxPosition.x), this.randomInt(0, maxPosition.y));
245 randomSquareSize: function(min, max)
247 var side = this.random(min, max);
248 return new Point(side, side);
251 randomVelocity: function(maxVelocity)
253 return this.random(maxVelocity / 8, maxVelocity);
256 randomAngle: function()
258 return this.random(0, Math.PI * 2);
261 randomColor: function()
266 + this.randomInt(min, max).toString(16)
267 + this.randomInt(min, max).toString(16)
268 + this.randomInt(min, max).toString(16);
271 randomRotater: function()
273 return new Rotater(this.random(1000, 10000));
277 Rotater = Utilities.createClass(
278 function(rotateInterval)
281 this._rotateInterval = rotateInterval;
282 this._isSampling = false;
287 return this._rotateInterval;
290 next: function(timeDelta)
292 this._timeDelta = (this._timeDelta + timeDelta) % this._rotateInterval;
297 return (360 * this._timeDelta) / this._rotateInterval;
302 return "rotateZ(" + Math.floor(this.degree()) + "deg)";
305 rotate: function(center)
307 return "rotate(" + Math.floor(this.degree()) + ", " + center.x + "," + center.y + ")";
311 Benchmark = Utilities.createClass(
312 function(stage, options)
314 this._options = options;
315 this._animateLoop = this._animateLoop.bind(this);
318 this._stage.initialize(this);
320 var testIntervalMilliseconds = options["test-interval"] * 1000;
321 switch (options["adjustment"])
324 this._controller = new FixedComplexityController(testIntervalMilliseconds, this);
328 this._controller = new AdaptiveController(testIntervalMilliseconds, this);
335 return this._options;
345 return this.waitUntilReady().then(function() {
346 this._finishPromise = new SimplePromise;
347 this._previousTimestamp = performance.now();
348 this._didWarmUp = false;
349 this._stage.tune(this._controller.initialComplexity - this._stage.complexity());
351 return this._finishPromise;
355 // Subclasses should override this if they have setup to do prior to commencing.
356 waitUntilReady: function()
358 var promise = new SimplePromise;
363 _animateLoop: function()
365 this._currentTimestamp = performance.now();
367 if (!this._didWarmUp) {
368 if (this._currentTimestamp - this._previousTimestamp >= 100) {
369 this._didWarmUp = true;
370 this._controller.start(this._stage, this._currentTimestamp);
371 this._previousTimestamp = this._currentTimestamp;
374 this._stage.animate(0);
375 requestAnimationFrame(this._animateLoop);
379 this._controller.update(this._stage, this._currentTimestamp);
380 if (this._controller.shouldStop(this._currentTimestamp)) {
381 this._finishPromise.resolve(this._controller.results());
385 this._stage.animate(this._currentTimestamp - this._previousTimestamp);
386 this._previousTimestamp = this._currentTimestamp;
387 requestAnimationFrame(this._animateLoop);