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