4913a2e6dd714ba1e49110409fe63ecef24cc0f4
[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: 0,
10     SAMPLING: 1,
11     FINISHED: 2,
12     messages: [ 
13         Strings.text.runningState.warming,
14         Strings.text.runningState.sampling,
15         Strings.text.runningState.finished
16     ]
17 }
18
19 BenchmarkState.prototype =
20 {
21     _timeOffset: function(stage)
22     {
23         return stage * this._stageInterval;
24     },
25     
26     _message: function(stage, timeOffset)
27     {
28         if (stage == BenchmarkState.stages.FINISHED)
29             return BenchmarkState.stages.messages[stage];
30
31         return BenchmarkState.stages.messages[stage] + "... ("
32             + Math.floor((timeOffset - this._timeOffset(stage)) / 1000) + "/"
33             + Math.floor((this._timeOffset(stage + 1) - this._timeOffset(stage)) / 1000) + ")";
34     },
35     
36     update: function(currentTimeOffset)
37     {
38         this._currentTimeOffset = currentTimeOffset;
39     },
40     
41     samplingTimeOffset: function()
42     {
43         return this._timeOffset(BenchmarkState.stages.SAMPLING);
44     },
45     
46     currentStage: function()
47     {
48         for (var stage = BenchmarkState.stages.WARMING; stage < BenchmarkState.stages.FINISHED; ++stage) {
49             if (this._currentTimeOffset < this._timeOffset(stage + 1))
50                 return stage;
51         }
52         return BenchmarkState.stages.FINISHED;
53     },
54     
55     currentMessage: function()
56     {
57         return this._message(this.currentStage(), this._currentTimeOffset);
58     },
59     
60     currentProgress: function()
61     {
62         return this._currentTimeOffset / this._timeOffset(BenchmarkState.stages.FINISHED);
63     }
64 }
65
66 function Animator(benchmark, options)
67 {
68     this._benchmark = benchmark;
69     this._options = options;
70     
71     this._frameCount = 0;
72     this._dropFrameCount = 1;
73     this._measureFrameCount = 3; 
74     this._referenceTime = 0;
75     this._currentTimeOffset = 0;
76     this._estimator = new KalmanEstimator(60);
77 }
78
79 Animator.prototype =
80 {
81     timeDelta: function()
82     {
83         return this._currentTimeOffset - this._startTimeOffset;
84     },
85     
86     animate: function()
87     {
88         var currentTime = performance.now();
89         
90         if (!this._referenceTime)
91             this._referenceTime = currentTime;
92         else
93             this._currentTimeOffset = currentTime - this._referenceTime;
94
95         if (!this._frameCount)
96             this._startTimeOffset = this._currentTimeOffset;
97
98         ++this._frameCount;
99
100         // Start measuring after dropping _dropFrameCount frames.
101         if (this._frameCount == this._dropFrameCount)
102             this._measureTimeOffset = this._currentTimeOffset;
103
104         // Drop _dropFrameCount frames and measure the average of _measureFrameCount frames.
105         if (this._frameCount < this._dropFrameCount + this._measureFrameCount)
106             return true;
107
108         // Get the average FPS of _measureFrameCount frames over measureTimeDelta.
109         var measureTimeDelta = this._currentTimeOffset - this._measureTimeOffset;
110         var currentFrameRate = Math.floor(1000 / (measureTimeDelta / this._measureFrameCount));
111          
112         // Use Kalman filter to get a more non-fluctuating frame rate.
113         if (this._options["estimated-frame-rate"])
114             currentFrameRate = this._estimator.estimate(currentFrameRate);
115         
116         // Adjust the test to reach the desired FPS.
117         var result = this._benchmark.update(this._currentTimeOffset, this.timeDelta(), currentFrameRate);
118         
119         // Start the next drop/measure cycle.
120         this._frameCount = 0;
121         
122         // If result == 0, no more requestAnimationFrame() will be invoked.
123         return result;
124     },
125     
126     animateLoop: function(timestamp)
127     {
128         if (this.animate())
129             requestAnimationFrame(this.animateLoop.bind(this));
130     }
131 }
132
133 function Benchmark(options)
134 {
135     this._options = options;
136     this._recordInterval = 200;    
137     this._isSampling = false;
138
139     this._controller = new PIDController(this._options["frame-rate"]);
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["adjustment"] == "fixed") {
170             if (this._options["complexity"]) {
171                 // this.tune(0) returns the current complexity of the test.
172                 tuneValue = this._options["complexity"] - this.tune(0);
173             }
174         }
175         else if (!(this._isSampling && this._options["adjustment"] == "fixed-after-warmup")) {
176             // The relationship between frameRate and test complexity is inverse-proportional so we
177             // need to use the negative of PIDController.tune() to change the complexity of the test.
178             tuneValue = -this._controller.tune(currentTimeOffset, timeDelta, currentFrameRate);
179             tuneValue = tuneValue > 0 ? Math.floor(tuneValue) : Math.ceil(tuneValue);
180         }
181
182         var currentComplexity = this.tune(tuneValue);
183         this.record(currentTimeOffset, currentComplexity, currentFrameRate);
184         return true;
185     },
186     
187     record: function(currentTimeOffset, currentComplexity, currentFrameRate)
188     {
189         this._sampler.sample(currentTimeOffset, [currentComplexity, currentFrameRate]);
190         
191         if (typeof this._recordTimeOffset == "undefined")
192             this._recordTimeOffset = currentTimeOffset;
193
194         var stage = this._state.currentStage();
195         if (stage != BenchmarkState.stages.FINISHED && currentTimeOffset < this._recordTimeOffset + this._recordInterval)
196             return;
197
198         this.showResults(this._state.currentProgress(), this._state.currentMessage());
199         this._recordTimeOffset = currentTimeOffset;
200     },
201     
202     run: function()
203     {
204         this.start();
205
206         var promise = new SimplePromise;
207         var self = this;
208         function resolveWhenFinished() {
209             if (typeof self._state != "undefined" && (self._state.currentStage() == BenchmarkState.stages.FINISHED))
210                 return promise.resolve(self._sampler);
211             setTimeout(resolveWhenFinished.bind(self), 50);
212         }
213         
214         resolveWhenFinished();
215         return promise;
216     }
217 }
218
219 window.benchmarkClient = {};
220
221 // This event listener runs the test if it is loaded outside the benchmark runner.
222 window.addEventListener("load", function()
223 {
224     if (window.self !== window.top)
225         return;
226     window.benchmark = window.benchmarkClient.create(null, null, 30000, 50, null, null);
227     window.benchmark.start();
228 });
229
230 // This function is called from the suite controller run-callback when running the benchmark runner.
231 window.runBenchmark = function(suite, test, options, progressBar)
232 {
233     var benchmarkOptions = { complexity: test.complexity };
234     benchmarkOptions = Utilities.mergeObjects(benchmarkOptions, options);
235     benchmarkOptions = Utilities.mergeObjects(benchmarkOptions, Utilities.parseParameters());
236     window.benchmark = window.benchmarkClient.create(suite, test, benchmarkOptions, progressBar);
237     return window.benchmark.run();
238 }