Get rid of options member variable in Benchmark.
[WebKit-https.git] / PerformanceTests / Animometer / tests / resources / main.js
1 Controller = Utilities.createClass(
2     function(testLength, benchmark, options)
3     {
4         // Initialize timestamps relative to the start of the benchmark
5         // In start() the timestamps are offset by the start timestamp
6         this._startTimestamp = 0;
7         this._endTimestamp = testLength;
8         // Default data series: timestamp, complexity, estimatedFrameLength
9         this._sampler = new Sampler(options["series-count"] || 3, 60 * testLength, this);
10         this._estimator = new SimpleKalmanEstimator(options["kalman-process-error"], options["kalman-measurement-error"]);
11
12         this.initialComplexity = 0;
13     }, {
14
15     start: function(stage, startTimestamp)
16     {
17         this._startTimestamp = startTimestamp;
18         this._endTimestamp += startTimestamp;
19         this.recordFirstSample(stage, startTimestamp);
20     },
21
22     recordFirstSample: function(stage, startTimestamp)
23     {
24         this._sampler.record(startTimestamp, stage.complexity(), -1);
25         this._sampler.mark(Strings.json.samplingTimeOffset, { time: 0 });
26     },
27
28     update: function(stage, timestamp)
29     {
30         this._estimator.sample(timestamp - this._sampler.samples[0][this._sampler.sampleCount - 1]);
31         this._sampler.record(timestamp, stage.complexity(), 1000 / this._estimator.estimate);
32     },
33
34     shouldStop: function(timestamp)
35     {
36         return timestamp > this._endTimestamp;
37     },
38
39     results: function()
40     {
41         return this._sampler.process();
42     },
43
44     processSamples: function(results)
45     {
46         var complexityExperiment = new Experiment;
47         var smoothedFPSExperiment = new Experiment;
48
49         var samples = this._sampler.samples;
50
51         var samplingIndex = 0;
52         var samplingMark = this._sampler.marks[Strings.json.samplingTimeOffset];
53         if (samplingMark) {
54             samplingIndex = samplingMark.index;
55             results[Strings.json.samplingTimeOffset] = samplingMark.time;
56         }
57
58         results[Strings.json.samples] = samples[0].map(function(timestamp, i) {
59             var result = {
60                 // Represent time in seconds
61                 time: timestamp - this._startTimestamp,
62                 complexity: samples[1][i]
63             };
64
65             // time offsets represented as FPS
66             if (i == 0)
67                 result.fps = 60;
68             else
69                 result.fps = 1000 / (timestamp - samples[0][i - 1]);
70
71             if (samples[2][i] != -1)
72                 result.smoothedFPS = samples[2][i];
73
74             // Don't start adding data to the experiments until we reach the sampling timestamp
75             if (i >= samplingIndex) {
76                 complexityExperiment.sample(result.complexity);
77                 if (result.smoothedFPS && result.smoothedFPS != -1)
78                     smoothedFPSExperiment.sample(result.smoothedFPS);
79             }
80
81             return result;
82         }, this);
83
84         results[Strings.json.score] = complexityExperiment.score(Experiment.defaults.CONCERN);
85
86         var complexityResults = {};
87         results[Strings.json.experiments.complexity] = complexityResults;
88         complexityResults[Strings.json.measurements.average] = complexityExperiment.mean();
89         complexityResults[Strings.json.measurements.concern] = complexityExperiment.concern(Experiment.defaults.CONCERN);
90         complexityResults[Strings.json.measurements.stdev] = complexityExperiment.standardDeviation();
91         complexityResults[Strings.json.measurements.percent] = complexityExperiment.percentage();
92
93         var smoothedFPSResults = {};
94         results[Strings.json.experiments.frameRate] = smoothedFPSResults;
95         smoothedFPSResults[Strings.json.measurements.average] = smoothedFPSExperiment.mean();
96         smoothedFPSResults[Strings.json.measurements.concern] = smoothedFPSExperiment.concern(Experiment.defaults.CONCERN);
97         smoothedFPSResults[Strings.json.measurements.stdev] = smoothedFPSExperiment.standardDeviation();
98         smoothedFPSResults[Strings.json.measurements.percent] = smoothedFPSExperiment.percentage();
99     }
100 });
101
102 FixedComplexityController = Utilities.createSubclass(Controller,
103     function(testInterval, benchmark, options)
104     {
105         Controller.call(this, testInterval, benchmark, options);
106         this.initialComplexity = options["complexity"];
107     }
108 );
109
110 AdaptiveController = Utilities.createSubclass(Controller,
111     function(testInterval, benchmark, options)
112     {
113         // Data series: timestamp, complexity, estimatedIntervalFrameLength
114         Controller.call(this, testInterval, benchmark, options);
115
116         // All tests start at 0, so we expect to see 60 fps quickly.
117         this._samplingTimestamp = testInterval / 2;
118         this._startedSampling = false;
119         this._targetFrameRate = options["frame-rate"];
120         this._pid = new PIDController(this._targetFrameRate);
121
122         this._intervalFrameCount = 0;
123         this._numberOfFramesToMeasurePerInterval = 4;
124     }, {
125
126     start: function(stage, startTimestamp)
127     {
128         Controller.prototype.start.call(this, stage, startTimestamp);
129
130         this._samplingTimestamp += startTimestamp;
131         this._intervalTimestamp = startTimestamp;
132     },
133
134     recordFirstSample: function(stage, startTimestamp)
135     {
136         this._sampler.record(startTimestamp, stage.complexity(), -1);
137     },
138
139     update: function(stage, timestamp)
140     {
141         if (!this._startedSampling && timestamp > this._samplingTimestamp) {
142             this._startedSampling = true;
143             this._sampler.mark(Strings.json.samplingTimeOffset, {
144                 time: this._samplingTimestamp - this._startTimestamp
145             });
146         }
147
148         // Start the work for the next frame.
149         ++this._intervalFrameCount;
150
151         if (this._intervalFrameCount < this._numberOfFramesToMeasurePerInterval) {
152             this._sampler.record(timestamp, stage.complexity(), -1);
153             return;
154         }
155
156         // Adjust the test to reach the desired FPS.
157         var intervalLength = timestamp - this._intervalTimestamp;
158         this._estimator.sample(intervalLength / this._numberOfFramesToMeasurePerInterval);
159         var intervalEstimatedFrameRate = 1000 / this._estimator.estimate;
160         var tuneValue = -this._pid.tune(timestamp - this._startTimestamp, intervalLength, intervalEstimatedFrameRate);
161         tuneValue = tuneValue > 0 ? Math.floor(tuneValue) : Math.ceil(tuneValue);
162         stage.tune(tuneValue);
163
164         this._sampler.record(timestamp, stage.complexity(), intervalEstimatedFrameRate);
165
166         // Start the next interval.
167         this._intervalFrameCount = 0;
168         this._intervalTimestamp = timestamp;
169     },
170
171     processSamples: function(results)
172     {
173         Controller.prototype.processSamples.call(this, results);
174         results[Strings.json.targetFPS] = this._targetFrameRate;
175     }
176 });
177
178 Stage = Utilities.createClass(
179     function()
180     {
181     }, {
182
183     initialize: function(benchmark)
184     {
185         this._benchmark = benchmark;
186         this._element = document.getElementById("stage");
187         this._element.setAttribute("width", document.body.offsetWidth);
188         this._element.setAttribute("height", document.body.offsetHeight);
189         this._size = Point.elementClientSize(this._element).subtract(Insets.elementPadding(this._element).size);
190     },
191
192     get element()
193     {
194         return this._element;
195     },
196
197     get size()
198     {
199         return this._size;
200     },
201
202     complexity: function()
203     {
204         return 0;
205     },
206
207     tune: function()
208     {
209         throw "Not implemented";
210     },
211
212     animate: function()
213     {
214         throw "Not implemented";
215     },
216
217     clear: function()
218     {
219         return this.tune(-this.tune(0));
220     }
221 });
222
223 Utilities.extendObject(Stage, {
224     random: function(min, max)
225     {
226         return (Math.random() * (max - min)) + min;
227     },
228
229     randomBool: function()
230     {
231         return !!Math.round(this.random(0, 1));
232     },
233
234     randomInt: function(min, max)
235     {
236         return Math.round(this.random(min, max));
237     },
238
239     randomPosition: function(maxPosition)
240     {
241         return new Point(this.randomInt(0, maxPosition.x), this.randomInt(0, maxPosition.y));
242     },
243
244     randomSquareSize: function(min, max)
245     {
246         var side = this.random(min, max);
247         return new Point(side, side);
248     },
249
250     randomVelocity: function(maxVelocity)
251     {
252         return this.random(maxVelocity / 8, maxVelocity);
253     },
254
255     randomAngle: function()
256     {
257         return this.random(0, Math.PI * 2);
258     },
259
260     randomColor: function()
261     {
262         var min = 32;
263         var max = 256 - 32;
264         return "#"
265             + this.randomInt(min, max).toString(16)
266             + this.randomInt(min, max).toString(16)
267             + this.randomInt(min, max).toString(16);
268     },
269
270     randomRotater: function()
271     {
272         return new Rotater(this.random(1000, 10000));
273     }
274 });
275
276 Rotater = Utilities.createClass(
277     function(rotateInterval)
278     {
279         this._timeDelta = 0;
280         this._rotateInterval = rotateInterval;
281         this._isSampling = false;
282     }, {
283
284     get interval()
285     {
286         return this._rotateInterval;
287     },
288
289     next: function(timeDelta)
290     {
291         this._timeDelta = (this._timeDelta + timeDelta) % this._rotateInterval;
292     },
293
294     degree: function()
295     {
296         return (360 * this._timeDelta) / this._rotateInterval;
297     },
298
299     rotateZ: function()
300     {
301         return "rotateZ(" + Math.floor(this.degree()) + "deg)";
302     },
303
304     rotate: function(center)
305     {
306         return "rotate(" + Math.floor(this.degree()) + ", " + center.x + "," + center.y + ")";
307     }
308 });
309
310 Benchmark = Utilities.createClass(
311     function(stage, options)
312     {
313         this._animateLoop = this._animateLoop.bind(this);
314
315         this._stage = stage;
316         this._stage.initialize(this, options);
317
318         var testIntervalMilliseconds = options["test-interval"] * 1000;
319         switch (options["adjustment"])
320         {
321         case "fixed":
322             this._controller = new FixedComplexityController(testIntervalMilliseconds, this, options);
323             break;
324         case "adaptive":
325         default:
326             this._controller = new AdaptiveController(testIntervalMilliseconds, this, options);
327             break;
328         }
329     }, {
330
331     get stage()
332     {
333         return this._stage;
334     },
335
336     run: function()
337     {
338         return this.waitUntilReady().then(function() {
339             this._finishPromise = new SimplePromise;
340             this._previousTimestamp = performance.now();
341             this._didWarmUp = false;
342             this._stage.tune(this._controller.initialComplexity - this._stage.complexity());
343             this._animateLoop();
344             return this._finishPromise;
345         }.bind(this));
346     },
347
348     // Subclasses should override this if they have setup to do prior to commencing.
349     waitUntilReady: function()
350     {
351         var promise = new SimplePromise;
352         promise.resolve();
353         return promise;
354     },
355
356     _animateLoop: function()
357     {
358         this._currentTimestamp = performance.now();
359
360         if (!this._didWarmUp) {
361             if (this._currentTimestamp - this._previousTimestamp >= 100) {
362                 this._didWarmUp = true;
363                 this._controller.start(this._stage, this._currentTimestamp);
364                 this._previousTimestamp = this._currentTimestamp;
365             }
366
367             this._stage.animate(0);
368             requestAnimationFrame(this._animateLoop);
369             return;
370         }
371
372         this._controller.update(this._stage, this._currentTimestamp);
373         if (this._controller.shouldStop(this._currentTimestamp)) {
374             this._finishPromise.resolve(this._controller.results());
375             return;
376         }
377
378         this._stage.animate(this._currentTimestamp - this._previousTimestamp);
379         this._previousTimestamp = this._currentTimestamp;
380         requestAnimationFrame(this._animateLoop);
381     }
382 });