Clean referencing the options object in the graphics benchmark
[WebKit-https.git] / PerformanceTests / Animometer / tests / resources / main.js
1 function BenchmarkState(testInterval)
2 {
3     this._currentTimeOffset = 0;
4     this._stageInterval = testInterval / BenchmarkState.stages.FINISHED;
5 }
6
7 // The enum values and the messages should be in the same order
8 BenchmarkState.stages = {
9     WARMING_UP: 0,
10     SAMPLING: 1,
11     FINISHED: 2,
12     messages: [ "Warming up", "Sampling", "Finished" ]
13 }
14
15 BenchmarkState.prototype =
16 {
17     _timeOffset: function(stage)
18     {
19         return stage * this._stageInterval;
20     },
21     
22     _message: function(stage, timeOffset)
23     {
24         if (stage == BenchmarkState.stages.FINISHED)
25             return BenchmarkState.stages.messages[stage];
26
27         return BenchmarkState.stages.messages[stage] + "... ("
28             + Math.floor((timeOffset - this._timeOffset(stage)) / 1000) + "/"
29             + Math.floor((this._timeOffset(stage + 1) - this._timeOffset(stage)) / 1000) + ")";
30     },
31     
32     update: function(currentTimeOffset)
33     {
34         this._currentTimeOffset = currentTimeOffset;
35     },
36     
37     samplingTimeOffset: function()
38     {
39         return this._timeOffset(BenchmarkState.stages.SAMPLING);
40     },
41     
42     currentStage: function()
43     {
44         for (var stage = BenchmarkState.stages.WARMING_UP; stage < BenchmarkState.stages.FINISHED; ++stage) {
45             if (this._currentTimeOffset < this._timeOffset(stage + 1))
46                 return stage;
47         }
48         return BenchmarkState.stages.FINISHED;
49     },
50     
51     currentMessage: function()
52     {
53         return this._message(this.currentStage(), this._currentTimeOffset);
54     },
55     
56     currentProgress: function()
57     {
58         return this._currentTimeOffset / this._timeOffset(BenchmarkState.stages.FINISHED);
59     }
60 }
61
62 function Animator(benchmark, options)
63 {
64     this._benchmark = benchmark;
65     this._options = options;
66     
67     this._frameCount = 0;
68     this._dropFrameCount = 1;
69     this._measureFrameCount = 3; 
70     this._referenceTime = 0;
71     this._currentTimeOffset = 0;
72     this._estimator = new KalmanEstimator(60);
73 }
74
75 Animator.prototype =
76 {
77     start: function()
78     {
79         this._intervalId = setInterval(this.animate.bind(this), 1);
80     },
81     
82     timeDelta: function()
83     {
84         return this._currentTimeOffset - this._startTimeOffset;
85     },
86     
87     animate: function()
88     {
89         var currentTime = performance.now();
90         
91         if (!this._referenceTime)
92             this._referenceTime = currentTime;
93         else
94             this._currentTimeOffset = currentTime - this._referenceTime;
95
96         if (!this._frameCount)
97             this._startTimeOffset = this._currentTimeOffset;
98
99         ++this._frameCount;
100
101         // Start measuring after dropping _dropFrameCount frames.
102         if (this._frameCount == this._dropFrameCount)
103             this._measureTimeOffset = this._currentTimeOffset;
104
105         // Drop _dropFrameCount frames and measure the average of _measureFrameCount frames.
106         if (this._frameCount < this._dropFrameCount + this._measureFrameCount)
107             return true;
108
109         // Get the average FPS of _measureFrameCount frames over measureTimeDelta.
110         var measureTimeDelta = this._currentTimeOffset - this._measureTimeOffset;
111         var currentFrameRate = Math.floor(1000 / (measureTimeDelta / this._measureFrameCount));
112          
113         // Use Kalman filter to get a more non-fluctuating frame rate.
114         if (this._options["estimated-frame-rate"])
115             currentFrameRate = this._estimator.estimate(currentFrameRate);
116         
117         // Adjust the test to reach the desired FPS.
118         var result = this._benchmark.update(this._currentTimeOffset, this.timeDelta(), currentFrameRate);
119         
120         // Stop the animator if the benchmark has finished.
121         if (!result && typeof this._intervalId != "undefined")
122             clearInterval(this._intervalId);
123
124         // Start the next drop/measure cycle.
125         this._frameCount = 0;
126         
127         // result may stop the animator if requestAnimationFrame() has been used.
128         return result;
129     },
130     
131     animateLoop: function(timestamp)
132     {
133         if (this.animate())
134             requestAnimationFrame(this.animateLoop.bind(this));
135     }
136 }
137
138 function Benchmark(options)
139 {
140     this._options = options;
141     this._method = this._options["method"] || "requestAnimationFrame";
142
143     this._recordInterval = 200;    
144     this._isSampling = false;
145
146     var gain = parseInt(this._options["gain"]) || 1;
147     var lowValue = -parseInt(this._options["addLimit"]) || 1;
148     var highValue = parseInt(this._options["removeLimit"]) || 1;
149     
150     this._controller = new PIDController(gain, options["frame-rate"], lowValue, highValue);
151     this._sampler = new Sampler(2);
152     this._state = new BenchmarkState(this._options["test-interval"] * 1000);
153 }
154
155 Benchmark.prototype =
156 {
157     // Called from the load event listener or from this.run().
158     start: function()
159     {
160         if (this._method == "setInterval")
161             this._animator.start();
162         else
163             this._animator.animateLoop();
164     },
165     
166     // Called from the animator to adjust the complexity of the test.
167     update: function(currentTimeOffset, timeDelta, currentFrameRate)
168     {
169         this._state.update(currentTimeOffset);
170         
171         var stage = this._state.currentStage();
172         if (stage == BenchmarkState.stages.FINISHED) {
173             this.clear();
174             return false;
175         }
176
177         if (stage == BenchmarkState.stages.SAMPLING && !this._isSampling) {
178             this._sampler.startSampling(this._state.samplingTimeOffset());
179             this._isSampling = true;
180         }
181
182         var tuneValue = 0;
183         if (this._options["complexity"] && !this._options["adaptive-test"]) {
184             // this.tune(0) returns the current complexity of the test.
185             tuneValue = this._options["complexity"] - this.tune(0);
186         }
187         else if (!(this._isSampling && this._options["fix-test-complexity"])) {
188             // The relationship between frameRate and test complexity is inverse-proportional so we
189             // need to use the negative of PIDController.tune() to change the complexity of the test.
190             tuneValue = -this._controller.tune(currentFrameRate, timeDelta / 1000);
191             tuneValue = tuneValue > 0 ? Math.floor(tuneValue) : Math.ceil(tuneValue);
192         }
193
194         var currentComplexity = this.tune(tuneValue);
195         this.record(currentTimeOffset, currentComplexity, currentFrameRate);
196         return true;
197     },
198     
199     record: function(currentTimeOffset, currentComplexity, currentFrameRate)
200     {
201         this._sampler.sample(currentTimeOffset, [currentComplexity, currentFrameRate]);
202         
203         if (typeof this._recordTimeOffset == "undefined")
204             this._recordTimeOffset = currentTimeOffset;
205
206         var stage = this._state.currentStage();
207         if (stage != BenchmarkState.stages.FINISHED && currentTimeOffset < this._recordTimeOffset + this._recordInterval)
208             return;
209
210         this.showResults(this._state.currentMessage(), this._state.currentProgress());
211         this._recordTimeOffset = currentTimeOffset;
212     },
213     
214     run: function()
215     {
216         this.start();
217
218         var promise = new SimplePromise;
219         var self = this;
220         function resolveWhenFinished() {
221             if (typeof self._state != "undefined" && (self._state.currentStage() == BenchmarkState.stages.FINISHED))
222                 return promise.resolve(self._sampler);
223             setTimeout(resolveWhenFinished.bind(self), 50);
224         }
225         
226         resolveWhenFinished();
227         return promise;
228     }
229 }
230
231 window.benchmarkClient = {};
232
233 // This event listener runs the test if it is loaded outside the benchmark runner.
234 window.addEventListener("load", function()
235 {
236     if (window.self !== window.top)
237         return;
238     window.benchmark = window.benchmarkClient.create(null, null, 30000, 50, null, null);
239     window.benchmark.start();
240 });
241
242 // This function is called from the suite controller run-callback when running the benchmark runner.
243 window.runBenchmark = function(suite, test, options, recordTable, progressBar)
244 {
245     var benchmarkOptions = { complexity: test.complexity };
246     benchmarkOptions = Utilities.mergeObjects(benchmarkOptions, options);
247     benchmarkOptions = Utilities.mergeObjects(benchmarkOptions, Utilities.parseParameters());
248     window.benchmark = window.benchmarkClient.create(suite, test, benchmarkOptions, recordTable, progressBar);
249     return window.benchmark.run();
250 }