Make StyleBench compatible with run-benchmark and run-perf-tests
[WebKit-https.git] / PerformanceTests / StyleBench / 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     frame.setAttribute('scrolling', 'no');
75
76     var marginLeft = parseInt(getComputedStyle(document.body).marginLeft);
77     var marginTop = parseInt(getComputedStyle(document.body).marginTop);
78     if (window.innerWidth > 800 + marginLeft && window.innerHeight > 600 + marginTop) {
79         frame.style.left = marginLeft + 'px';
80         frame.style.top = marginTop + 'px';
81     } else {
82         frame.style.left = '0px';
83         frame.style.top = '0px';
84     }
85
86     if (this._client && this._client.willAddTestFrame)
87         this._client.willAddTestFrame(frame);
88
89     document.body.insertBefore(frame, document.body.firstChild);
90     this._frame = frame;
91     return frame;
92 }
93
94 BenchmarkRunner.prototype._waitAndWarmUp = function () {
95     var startTime = Date.now();
96
97     function Fibonacci(n) {
98         if (Date.now() - startTime > 100)
99             return;
100         if (n <= 0)
101             return 0;
102         else if (n == 1)
103             return 1;
104         return Fibonacci(n - 2) + Fibonacci(n - 1);
105     }
106
107     var promise = new SimplePromise;
108     setTimeout(function () {
109         Fibonacci(100);
110         promise.resolve();
111     }, 200);
112     return promise;
113 }
114
115 BenchmarkRunner.prototype._writeMark = function(name) {
116     if (window.performance && window.performance.mark)
117         window.performance.mark(name);
118 }
119
120 // This function ought be as simple as possible. Don't even use SimplePromise.
121 BenchmarkRunner.prototype._runTest = function(suite, test, prepareReturnValue, callback)
122 {
123     var self = this;
124     var now = window.performance && window.performance.now ? function () { return window.performance.now(); } : Date.now;
125
126     var contentWindow = self._frame.contentWindow;
127     var contentDocument = self._frame.contentDocument;
128
129     self._writeMark(suite.name + '.' + test.name + '-start');
130     var startTime = now();
131     test.run(prepareReturnValue, contentWindow, contentDocument);
132     var endTime = now();
133     self._writeMark(suite.name + '.' + test.name + '-sync-end');
134
135     var syncTime = endTime - startTime;
136
137     var startTime = now();
138     setTimeout(function () {
139         // Some browsers don't immediately update the layout for paint.
140         // Force the layout here to ensure we're measuring the layout time.
141         var height = self._frame.contentDocument.body.getBoundingClientRect().height;
142         var endTime = now();
143         self._frame.contentWindow._unusedHeightValue = height; // Prevent dead code elimination.
144         self._writeMark(suite.name + '.' + test.name + '-async-end');
145         callback(syncTime, endTime - startTime, height);
146     }, 0);
147 }
148
149 function BenchmarkState(suites) {
150     this._suites = suites;
151     this._suiteIndex = -1;
152     this._testIndex = 0;
153     this.next();
154 }
155
156 BenchmarkState.prototype.currentSuite = function() {
157     return this._suites[this._suiteIndex];
158 }
159
160 BenchmarkState.prototype.currentTest = function () {
161     var suite = this.currentSuite();
162     return suite ? suite.tests[this._testIndex] : null;
163 }
164
165 BenchmarkState.prototype.next = function () {
166     this._testIndex++;
167
168     var suite = this._suites[this._suiteIndex];
169     if (suite && this._testIndex < suite.tests.length)
170         return this;
171
172     this._testIndex = 0;
173     do {
174         this._suiteIndex++;
175     } while (this._suiteIndex < this._suites.length && this._suites[this._suiteIndex].disabled);
176
177     return this;
178 }
179
180 BenchmarkState.prototype.isFirstTest = function () {
181     return !this._testIndex;
182 }
183
184 BenchmarkState.prototype.prepareCurrentSuite = function (runner, frame) {
185     var suite = this.currentSuite();
186     var promise = new SimplePromise;
187     frame.onload = function () {
188         suite.prepare(runner, frame.contentWindow, frame.contentDocument).then(function (result) { promise.resolve(result); });
189     }
190     frame.src = 'resources/' + suite.url;
191     return promise;
192 }
193
194 BenchmarkRunner.prototype.step = function (state) {
195     if (!state) {
196         state = new BenchmarkState(this._suites);
197         this._measuredValues = {tests: {}, total: 0, mean: NaN, geomean: NaN, score: NaN};
198     }
199
200     var suite = state.currentSuite();
201     if (!suite) {
202         this._finalize();
203         var promise = new SimplePromise;
204         promise.resolve();
205         return promise;
206     }
207
208     if (state.isFirstTest()) {
209         this._removeFrame();
210         var self = this;
211         return state.prepareCurrentSuite(this, this._appendFrame()).then(function (prepareReturnValue) {
212             self._prepareReturnValue = prepareReturnValue;
213             return self._runTestAndRecordResults(state);
214         });
215     }
216
217     return this._runTestAndRecordResults(state);
218 }
219
220 BenchmarkRunner.prototype.runAllSteps = function (startingState) {
221     var nextCallee = this.runAllSteps.bind(this);
222     this.step(startingState).then(function (nextState) {
223         if (nextState)
224             nextCallee(nextState);
225     });
226 }
227
228 BenchmarkRunner.prototype.runMultipleIterations = function (iterationCount) {
229     var self = this;
230     var currentIteration = 0;
231
232     this._runNextIteration = function () {
233         currentIteration++;
234         if (currentIteration < iterationCount)
235             self.runAllSteps();
236         else if (this._client && this._client.didFinishLastIteration)
237             this._client.didFinishLastIteration();
238     }
239
240     if (this._client && this._client.willStartFirstIteration)
241         this._client.willStartFirstIteration(iterationCount);
242
243     self.runAllSteps();
244 }
245
246 BenchmarkRunner.prototype._runTestAndRecordResults = function (state) {
247     var promise = new SimplePromise;
248     var suite = state.currentSuite();
249     var test = state.currentTest();
250
251     if (this._client && this._client.willRunTest)
252         this._client.willRunTest(suite, test);
253
254     var self = this;
255     setTimeout(function () {
256         self._runTest(suite, test, self._prepareReturnValue, function (syncTime, asyncTime) {
257             var suiteResults = self._measuredValues.tests[suite.name] || {tests:{}, total: 0};
258             var total = syncTime + asyncTime;
259             self._measuredValues.tests[suite.name] = suiteResults;
260             suiteResults.tests[test.name] = {tests: {'Sync': syncTime, 'Async': asyncTime}, total: total};
261             suiteResults.total += total;
262
263             if (self._client && self._client.didRunTest)
264                 self._client.didRunTest(suite, test);
265
266             state.next();
267             promise.resolve(state);
268         });
269     }, 0);
270     return promise;
271 }
272
273 BenchmarkRunner.prototype._finalize = function () {
274     this._removeFrame();
275
276     if (this._client && this._client.didRunSuites) {
277         var product = 1;
278         var values = [];
279         for (var suiteName in this._measuredValues.tests) {
280             var suiteTotal = this._measuredValues.tests[suiteName].total;
281             product *= suiteTotal;
282             values.push(suiteTotal);
283         }
284
285         values.sort(function (a, b) { return a - b }); // Avoid the loss of significance for the sum.
286         var total = values.reduce(function (a, b) { return a + b });
287         var geomean = Math.pow(product, 1 / values.length);
288
289         var correctionFactor = 8; // This factor makes the test score look reasonably fit within 0 to 140.
290         this._measuredValues.total = total;
291         this._measuredValues.mean = total / values.length;
292         this._measuredValues.geomean = geomean;
293         this._measuredValues.score = 60 * 1000 / geomean / correctionFactor;
294         this._client.didRunSuites(this._measuredValues);
295     }
296
297     if (this._runNextIteration)
298         this._runNextIteration();
299 }