Host DoYouEvenBench on webkit.org
[WebKit-https.git] / PerformanceTests / DoYouEvenBench / resources / benchmark-runner.js
1 // FIXME: Use the real promise if available.
2 // FIXME: Make sure this interface is compatible with the real Promise.
3 function SimplePromise() {
4     this._chainedPromise = null;
5     this._callback = null;
6 }
7
8 SimplePromise.prototype.then = function (callback) {
9     if (this._callback)
10         throw "SimplePromise doesn't support multiple calls to then";
11     this._callback = callback;
12     this._chainedPromise = new SimplePromise;
13     
14     if (this._resolved)
15         this.resolve(this._resolvedValue);
16
17     return this._chainedPromise;
18 }
19
20 SimplePromise.prototype.resolve = function (value) {
21     if (!this._callback) {
22         this._resolved = true;
23         this._resolvedValue = value;
24         return;
25     }
26
27     var result = this._callback(value);
28     if (result instanceof SimplePromise) {
29         var chainedPromise = this._chainedPromise;
30         result.then(function (result) { chainedPromise.resolve(result); });
31     } else
32         this._chainedPromise.resolve(result);
33 }
34
35 function BenchmarkTestStep(testName, testFunction) {
36     this.name = testName;
37     this.run = testFunction;
38 }
39
40 function BenchmarkRunner(suites, client) {
41     this._suites = suites;
42     this._prepareReturnValue = null;
43     this._client = client;
44 }
45
46 BenchmarkRunner.prototype.waitForElement = function (selector) {
47     var promise = new SimplePromise;
48     var contentDocument = this._frame.contentDocument;
49
50     function resolveIfReady() {
51         var element = contentDocument.querySelector(selector);
52         if (element)
53             return promise.resolve(element);
54         setTimeout(resolveIfReady, 50);
55     }
56
57     resolveIfReady();
58     return promise;
59 }
60
61 BenchmarkRunner.prototype._removeFrame = function () {
62     if (this._frame) {
63         this._frame.parentNode.removeChild(this._frame);
64         this._frame = null;
65     }
66 }
67
68 BenchmarkRunner.prototype._appendFrame = function (src) {
69     var frame = document.createElement('iframe');
70     frame.style.width = '800px';
71     frame.style.height = '600px';
72     frame.style.border = '0px none';
73     frame.style.position = 'absolute';
74
75     var marginTop = document.body.style.marginTop;
76     var marginBottom = document.body.style.marginBottom;
77     if (window.innerWidth > 800 + marginTop && window.innerHeight > 600 + marginBottom) {
78         frame.style.left = marginTop + 'px';
79         frame.style.top = marginBottom + 'px';
80     } else {
81         frame.style.left = '0px';
82         frame.style.top = '0px';
83     }
84
85     document.body.insertBefore(frame, document.body.firstChild);
86     this._frame = frame;
87     return frame;
88 }
89
90 BenchmarkRunner.prototype._waitAndWarmUp = function () {
91     var startTime = Date.now();
92
93     function Fibonacci(n) {
94         if (Date.now() - startTime > 100)
95             return;
96         if (n <= 0)
97             return 0;
98         else if (n == 1)
99             return 1;
100         return Fibonacci(n - 2) + Fibonacci(n - 1);
101     }
102
103     var promise = new SimplePromise;
104     setTimeout(function () {
105         Fibonacci(100);
106         promise.resolve();
107     }, 200);
108     return promise;
109 }
110
111 // This function ought be as simple as possible. Don't even use SimplePromise.
112 BenchmarkRunner.prototype._runTest = function(suite, testFunction, prepareReturnValue, callback)
113 {
114     var now = window.performance && window.performance.now ? function () { return window.performance.now(); } : Date.now;
115
116     var contentWindow = this._frame.contentWindow;
117     var contentDocument = this._frame.contentDocument;
118
119     var startTime = now();
120     testFunction(prepareReturnValue, contentWindow, contentDocument);
121     var endTime = now();
122     var syncTime = endTime - startTime;
123
124     var startTime = now();
125     setTimeout(function () {
126         var endTime = now();
127         callback(syncTime, endTime - startTime);
128     }, 0);
129 }
130
131 function BenchmarkState(suites) {
132     this._suites = suites;
133     this._suiteIndex = -1;
134     this._testIndex = 0;
135     this.next();
136 }
137
138 BenchmarkState.prototype.currentSuite = function() {
139     return this._suites[this._suiteIndex];
140 }
141
142 BenchmarkState.prototype.currentTest = function () {
143     var suite = this.currentSuite();
144     return suite ? suite.tests[this._testIndex] : null;
145 }
146
147 BenchmarkState.prototype.next = function () {
148     this._testIndex++;
149
150     var suite = this._suites[this._suiteIndex];
151     if (suite && this._testIndex < suite.tests.length)
152         return this;
153
154     this._testIndex = 0;
155     do {
156         this._suiteIndex++;
157     } while (this._suiteIndex < this._suites.length && this._suites[this._suiteIndex].disabled);
158
159     return this;
160 }
161
162 BenchmarkState.prototype.isFirstTest = function () {
163     return !this._testIndex;
164 }
165
166 BenchmarkState._containingDirectory = (function () {
167     var src = document.currentScript.src;
168     return src.substring(0, src.lastIndexOf('/')) + '/';
169 })();
170
171 BenchmarkState.prototype.prepareCurrentSuite = function (runner, frame) {
172     var suite = this.currentSuite();
173     var promise = new SimplePromise;
174     frame.onload = function () {
175         suite.prepare(runner, frame.contentWindow, frame.contentDocument).then(function (result) { promise.resolve(result); });
176     }
177     frame.src = BenchmarkState._containingDirectory + suite.url;
178     return promise;
179 }
180
181 BenchmarkRunner.prototype.step = function (state) {
182     if (!state) {
183         state = new BenchmarkState(this._suites);
184         this._measuredValues = {tests: {}, total: 0};
185     }
186
187     var suite = state.currentSuite();
188     if (!suite) {
189         this._finalize();
190         var promise = new SimplePromise;
191         promise.resolve();
192         return promise;
193     }
194
195     if (state.isFirstTest()) {
196         this._masuredValuesForCurrentSuite = {};
197         var self = this;
198         return state.prepareCurrentSuite(this, this._appendFrame()).then(function (prepareReturnValue) {
199             self._prepareReturnValue = prepareReturnValue;
200             return self._runTestAndRecordResults(state);
201         });
202     }
203
204     return this._runTestAndRecordResults(state);
205 }
206
207 BenchmarkRunner.prototype.runAllSteps = function (startingState) {
208     var nextCallee = this.runAllSteps.bind(this);
209     this.step(startingState).then(function (nextState) {
210         if (nextState)
211             nextCallee(nextState);
212     });
213 }
214
215 BenchmarkRunner.prototype.runMultipleIterations = function (iterationCount) {
216     var self = this;
217     var currentIteration = 0;
218
219     this._runNextIteration = function () {
220         currentIteration++;
221         if (currentIteration < iterationCount)
222             self.runAllSteps();
223         else if (this._client && this._client.didFinishLastIteration)
224             this._client.didFinishLastIteration();
225     }
226
227     if (this._client && this._client.willStartFirstIteration)
228         this._client.willStartFirstIteration(iterationCount);
229
230     self.runAllSteps();
231 }
232
233 BenchmarkRunner.prototype._runTestAndRecordResults = function (state) {
234     var promise = new SimplePromise;
235     var suite = state.currentSuite();
236     var test = state.currentTest();
237
238     if (this._client && this._client.willRunTest)
239         this._client.willRunTest(suite, test);
240
241     var self = this;
242     setTimeout(function () {
243         self._runTest(suite, test.run, self._prepareReturnValue, function (syncTime, asyncTime) {
244             var suiteResults = self._measuredValues.tests[suite.name] || {tests:{}, total: 0};
245             var total = syncTime + asyncTime;
246             self._measuredValues.tests[suite.name] = suiteResults;
247             suiteResults.tests[test.name] = {tests: {'Sync': syncTime, 'Async': asyncTime}, total: total};
248             suiteResults.total += total;
249             self._measuredValues.total += total;
250
251             if (self._client && self._client.willRunTest)
252                 self._client.didRunTest(suite, test);
253
254             state.next();
255             if (state.currentSuite() != suite)
256                 self._removeFrame();
257             promise.resolve(state);
258         });
259     }, 0);
260     return promise;
261 }
262
263 BenchmarkRunner.prototype._finalize = function () {
264     this._removeFrame();
265
266     if (this._client && this._client.didRunSuites)
267         this._client.didRunSuites(this._measuredValues);
268
269     if (this._runNextIteration)
270         this._runNextIteration();
271 }