1920f9b070ef7df47fbaf02ad3cdc57ce5372558
[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)
63 {
64     this._benchmark = benchmark;
65     this._frameCount = 0;
66     this._dropFrameCount = 1;
67     this._measureFrameCount = 3; 
68     this._referenceTime = 0;
69     this._currentTimeOffset = 0;
70     this._estimator = new KalmanEstimator();
71 }
72
73 Animator.prototype =
74 {
75     start: function()
76     {
77         this._intervalId = setInterval(this.animate.bind(this), 1);
78     },
79     
80     timeDelta: function()
81     {
82         return this._currentTimeOffset - this._startTimeOffset;
83     },
84     
85     animate: function()
86     {
87         var currentTime = performance.now();
88         
89         if (!this._referenceTime)
90             this._referenceTime = currentTime;
91         else
92             this._currentTimeOffset = currentTime - this._referenceTime;
93
94         if (!this._frameCount)
95             this._startTimeOffset = this._currentTimeOffset;
96
97         // Start measuring after dropping _dropFrameCount frames.
98         if (this._frameCount == this._dropFrameCount)
99             this._measureTimeOffset = this._currentTimeOffset;
100
101         ++this._frameCount;
102
103         // Drop _dropFrameCount frames and measure the average of _measureFrameCount frames.
104         if (this._frameCount < this._dropFrameCount + this._measureFrameCount)
105             return true;
106
107         // Get the average FPS of _measureFrameCount frames over measureTimeDelta.
108         var measureTimeDelta = this._currentTimeOffset - this._measureTimeOffset;
109         var currentFrameRate = Math.floor(1000 / (measureTimeDelta / this._measureFrameCount));
110          
111         // Use Kalman filter to get a more non-fluctuating frame rate.
112         if (this._benchmark.options.estimatedFrameRate)
113             currentFrameRate = this._estimator.estimate(measureTimeDelta, currentFrameRate);
114         
115         // Adjust the test to reach the desired FPS.
116         var result = this._benchmark.update(this._currentTimeOffset, this.timeDelta(), currentFrameRate);
117         
118         // Stop the animator if the benchmark has finished.
119         if (!result && typeof this._intervalId != "undefined")
120             clearInterval(this._intervalId);
121
122         // Start the next drop/measure cycle.
123         this._frameCount = 0;
124         
125         // result may stop the animator if requestAnimationFrame() has been used.
126         return result;
127     },
128     
129     animateLoop: function(timestamp)
130     {
131         if (this.animate())
132             requestAnimationFrame(this.animateLoop.bind(this));
133     }
134 }
135
136 function Benchmark(options)
137 {
138     this._options = options;
139     this._method = this._options["method"] || "requestAnimationFrame";
140
141     this.options = options;
142     this._recordInterval = 200;    
143     this._isSampling = false;
144
145     var gain = parseInt(this._options["gain"]) || 1;
146     var lowValue = -parseInt(this._options["addLimit"]) || 1;
147     var highValue = parseInt(this._options["removeLimit"]) || 1;
148     
149     this._controller = new PIDController(gain, options.frameRate, lowValue, highValue);
150     this._sampler = new Sampler(2);
151     this._state = new BenchmarkState(this.options.testInterval);    
152 }
153
154 Benchmark.prototype =
155 {
156     // Called from the load event listener or from this.run().
157     start: function()
158     {
159         if (this._method == "setInterval")
160             this._animator.start();
161         else
162             this._animator.animateLoop();
163     },
164     
165     // Called from the animator to adjust the complexity of the test.
166     update: function(currentTimeOffset, timeDelta, currentFrameRate)
167     {
168         this._state.update(currentTimeOffset);
169         
170         var stage = this._state.currentStage();
171         if (stage == BenchmarkState.stages.FINISHED) {
172             this.clear();
173             return false;
174         }
175
176         if (stage == BenchmarkState.stages.SAMPLING && !this._isSampling) {
177             this._sampler.startSampling(this._state.samplingTimeOffset());
178             this._isSampling = true;
179         }
180
181         var tuneValue = 0;
182         if (!(this._isSampling && this.options.fixTestComplexity)) {
183             // The relationship between frameRate and test complexity is inverse-proportional so we
184             // need to use the negative of PIDController.tune() to change the complexity of the test.
185             tuneValue = -this._controller.tune(currentFrameRate, timeDelta / 1000);
186         }
187
188         var currentComplexity = this.tune(tuneValue);
189         this.record(currentTimeOffset, currentComplexity, currentFrameRate);
190         return true;
191     },
192     
193     record: function(currentTimeOffset, currentComplexity, currentFrameRate)
194     {
195         this._sampler.sample(currentTimeOffset, [currentComplexity, currentFrameRate]);
196         
197         if (typeof this._recordTimeOffset == "undefined")
198             this._recordTimeOffset = currentTimeOffset;
199
200         var stage = this._state.currentStage();
201         if (stage != BenchmarkState.stages.FINISHED && currentTimeOffset < this._recordTimeOffset + this._recordInterval)
202             return;
203
204         this.showResults(this._state.currentMessage(), this._state.currentProgress());
205         this._recordTimeOffset = currentTimeOffset;
206     },
207     
208     run: function()
209     {
210         this.start();
211
212         var promise = new SimplePromise;
213         var self = this;
214         function resolveWhenFinished() {
215             if (typeof self._state != "undefined" && (self._state.currentStage() == BenchmarkState.stages.FINISHED))
216                 return promise.resolve(self._sampler);
217             setTimeout(resolveWhenFinished.bind(self), 50);
218         }
219         
220         resolveWhenFinished();
221         return promise;
222     }
223 }
224
225 window.benchmarkClient = {};
226
227 // This event listener runs the test if it is loaded outside the benchmark runner.
228 window.addEventListener("load", function()
229 {
230     if (window.self !== window.top)
231         return;
232     window.benchmark = window.benchmarkClient.create(null, null, 30000, 50, null, null);
233     window.benchmark.start();
234 });
235
236 // This function is called from the suite controller run-callback when running the benchmark runner.
237 window.runBenchmark = function(suite, test, options, recordTable, progressBar)
238 {
239     var mergedOptions = Utilities.mergeObjects(options, Utilities.parseParameters());
240     window.benchmark = window.benchmarkClient.create(suite, test, mergedOptions, recordTable, progressBar);
241     return window.benchmark.run();
242 }