Update how the benchmark is run
[WebKit-https.git] / PerformanceTests / Animometer / tests / resources / main.js
1
2 Controller = Utilities.createClass(
3     function(testLength, benchmark, seriesCount)
4     {
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"]);
12
13         this.initialComplexity = 0;
14     }, {
15
16     start: function(stage, startTimestamp)
17     {
18         this._startTimestamp = startTimestamp;
19         this._endTimestamp += startTimestamp;
20         this.recordFirstSample(stage, startTimestamp);
21     },
22
23     recordFirstSample: function(stage, startTimestamp)
24     {
25         this._sampler.record(startTimestamp, stage.complexity(), -1);
26         this._sampler.mark(Strings.json.samplingTimeOffset, { time: 0 });
27     },
28
29     update: function(stage, timestamp)
30     {
31         this._estimator.sample(timestamp - this._sampler.samples[0][this._sampler.sampleCount - 1]);
32         this._sampler.record(timestamp, stage.complexity(), 1000 / this._estimator.estimate);
33     },
34
35     shouldStop: function(timestamp)
36     {
37         return timestamp > this._endTimestamp;
38     },
39
40     results: function()
41     {
42         return this._sampler.process();
43     },
44
45     processSamples: function(results)
46     {
47         var complexityExperiment = new Experiment;
48         var smoothedFPSExperiment = new Experiment;
49
50         var samples = this._sampler.samples;
51
52         var samplingIndex = 0;
53         var samplingMark = this._sampler.marks[Strings.json.samplingTimeOffset];
54         if (samplingMark) {
55             samplingIndex = samplingMark.index;
56             results[Strings.json.samplingTimeOffset] = samplingMark.time;
57         }
58
59         results[Strings.json.samples] = samples[0].map(function(timestamp, i) {
60             var result = {
61                 // Represent time in seconds
62                 time: timestamp - this._startTimestamp,
63                 complexity: samples[1][i]
64             };
65
66             // time offsets represented as FPS
67             if (i == 0)
68                 result.fps = 60;
69             else
70                 result.fps = 1000 / (timestamp - samples[0][i - 1]);
71
72             if (samples[2][i] != -1)
73                 result.smoothedFPS = samples[2][i];
74
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);
80             }
81
82             return result;
83         }, this);
84
85         results[Strings.json.score] = complexityExperiment.score(Experiment.defaults.CONCERN);
86
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();
93
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();
100     }
101 });
102
103 FixedComplexityController = Utilities.createSubclass(Controller,
104     function(testInterval, benchmark)
105     {
106         Controller.call(this, testInterval, benchmark);
107         this.initialComplexity = benchmark.options["complexity"];
108     }
109 );
110
111 AdaptiveController = Utilities.createSubclass(Controller,
112     function(testInterval, benchmark)
113     {
114         // Data series: timestamp, complexity, estimatedIntervalFrameLength
115         Controller.call(this, testInterval, benchmark);
116
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);
122
123         this._intervalFrameCount = 0;
124         this._numberOfFramesToMeasurePerInterval = 4;
125     }, {
126
127     start: function(stage, startTimestamp)
128     {
129         Controller.prototype.start.call(this, stage, startTimestamp);
130
131         this._samplingTimestamp += startTimestamp;
132         this._intervalTimestamp = startTimestamp;
133     },
134
135     recordFirstSample: function(stage, startTimestamp)
136     {
137         this._sampler.record(startTimestamp, stage.complexity(), -1);
138     },
139
140     update: function(stage, timestamp)
141     {
142         if (!this._startedSampling && timestamp > this._samplingTimestamp) {
143             this._startedSampling = true;
144             this._sampler.mark(Strings.json.samplingTimeOffset, {
145                 time: this._samplingTimestamp - this._startTimestamp
146             });
147         }
148
149         // Start the work for the next frame.
150         ++this._intervalFrameCount;
151
152         if (this._intervalFrameCount < this._numberOfFramesToMeasurePerInterval) {
153             this._sampler.record(timestamp, stage.complexity(), -1);
154             return;
155         }
156
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);
164
165         this._sampler.record(timestamp, stage.complexity(), intervalEstimatedFrameRate);
166
167         // Start the next interval.
168         this._intervalFrameCount = 0;
169         this._intervalTimestamp = timestamp;
170     },
171
172     processSamples: function(results)
173     {
174         Controller.prototype.processSamples.call(this, results);
175         results[Strings.json.targetFPS] = this._targetFrameRate;
176     }
177 });
178
179 Stage = Utilities.createClass(
180     function()
181     {
182     }, {
183
184     initialize: function(benchmark)
185     {
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);
191     },
192
193     get element()
194     {
195         return this._element;
196     },
197
198     get size()
199     {
200         return this._size;
201     },
202
203     complexity: function()
204     {
205         return 0;
206     },
207
208     tune: function()
209     {
210         throw "Not implemented";
211     },
212
213     animate: function()
214     {
215         throw "Not implemented";
216     },
217
218     clear: function()
219     {
220         return this.tune(-this.tune(0));
221     }
222 });
223
224 Utilities.extendObject(Stage, {
225     random: function(min, max)
226     {
227         return (Math.random() * (max - min)) + min;
228     },
229
230     randomBool: function()
231     {
232         return !!Math.round(this.random(0, 1));
233     },
234
235     randomInt: function(min, max)
236     {
237         return Math.round(this.random(min, max));
238     },
239
240     randomPosition: function(maxPosition)
241     {
242         return new Point(this.randomInt(0, maxPosition.x), this.randomInt(0, maxPosition.y));
243     },
244
245     randomSquareSize: function(min, max)
246     {
247         var side = this.random(min, max);
248         return new Point(side, side);
249     },
250
251     randomVelocity: function(maxVelocity)
252     {
253         return this.random(maxVelocity / 8, maxVelocity);
254     },
255
256     randomAngle: function()
257     {
258         return this.random(0, Math.PI * 2);
259     },
260
261     randomColor: function()
262     {
263         var min = 32;
264         var max = 256 - 32;
265         return "#"
266             + this.randomInt(min, max).toString(16)
267             + this.randomInt(min, max).toString(16)
268             + this.randomInt(min, max).toString(16);
269     },
270
271     randomRotater: function()
272     {
273         return new Rotater(this.random(1000, 10000));
274     }
275 });
276
277 Rotater = Utilities.createClass(
278     function(rotateInterval)
279     {
280         this._timeDelta = 0;
281         this._rotateInterval = rotateInterval;
282         this._isSampling = false;
283     }, {
284
285     get interval()
286     {
287         return this._rotateInterval;
288     },
289
290     next: function(timeDelta)
291     {
292         this._timeDelta = (this._timeDelta + timeDelta) % this._rotateInterval;
293     },
294
295     degree: function()
296     {
297         return (360 * this._timeDelta) / this._rotateInterval;
298     },
299
300     rotateZ: function()
301     {
302         return "rotateZ(" + Math.floor(this.degree()) + "deg)";
303     },
304
305     rotate: function(center)
306     {
307         return "rotate(" + Math.floor(this.degree()) + ", " + center.x + "," + center.y + ")";
308     }
309 });
310
311 Benchmark = Utilities.createClass(
312     function(stage, options)
313     {
314         this._options = options;
315         this._animateLoop = this._animateLoop.bind(this);
316
317         this._stage = stage;
318         this._stage.initialize(this);
319
320         var testIntervalMilliseconds = options["test-interval"] * 1000;
321         switch (options["adjustment"])
322         {
323         case "fixed":
324             this._controller = new FixedComplexityController(testIntervalMilliseconds, this);
325             break;
326         case "adaptive":
327         default:
328             this._controller = new AdaptiveController(testIntervalMilliseconds, this);
329             break;
330         }
331     }, {
332
333     get options()
334     {
335         return this._options;
336     },
337
338     get stage()
339     {
340         return this._stage;
341     },
342
343     run: function()
344     {
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());
350             this._animateLoop();
351             return this._finishPromise;
352         }.bind(this));
353     },
354
355     // Subclasses should override this if they have setup to do prior to commencing.
356     waitUntilReady: function()
357     {
358         var promise = new SimplePromise;
359         promise.resolve();
360         return promise;
361     },
362
363     _animateLoop: function()
364     {
365         this._currentTimestamp = performance.now();
366
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;
372             }
373
374             this._stage.animate(0);
375             requestAnimationFrame(this._animateLoop);
376             return;
377         }
378
379         this._controller.update(this._stage, this._currentTimestamp);
380         if (this._controller.shouldStop(this._currentTimestamp)) {
381             this._finishPromise.resolve(this._controller.results());
382             return;
383         }
384
385         this._stage.animate(this._currentTimestamp - this._previousTimestamp);
386         this._previousTimestamp = this._currentTimestamp;
387         requestAnimationFrame(this._animateLoop);
388     }
389 });