Use the media queries to dynamically set the stage for the graphics benchmark
[WebKit-https.git] / PerformanceTests / Animometer / resources / extensions.js
1 function Point(x, y)
2 {
3     this.x = x;
4     this.y = y;
5 }
6
7 Point.pointOnCircle = function(angle, radius)
8 {
9     return new Point(radius * Math.cos(angle), radius * Math.sin(angle));
10 }
11
12 Point.pointOnEllipse = function(angle, radiuses)
13 {
14     return new Point(radiuses.x * Math.cos(angle), radiuses.y * Math.sin(angle));
15 }
16
17 Point.elementClientSize = function(element)
18 {
19     return new Point(element.clientWidth, element.clientHeight);
20 }
21
22 Point.prototype =
23 {
24     // Used when the point object is used as a size object.
25     get width()
26     {
27         return this.x;
28     },
29     
30     // Used when the point object is used as a size object.
31     get height()
32     {
33         return this.y;
34     },
35     
36     // Used when the point object is used as a size object.
37     get center()
38     {
39         return new Point(this.x / 2, this.y / 2);
40     },
41     
42     add: function(other)
43     {
44         return new Point(this.x + other.x, this.y + other.y);
45     },
46     
47     subtract: function(other)
48     {
49         return new Point(this.x - other.x, this.y - other.y);
50     },
51     
52     multiply: function(other)
53     {
54         return new Point(this.x * other.x, this.y * other.y);
55     },
56     
57     move: function(angle, velocity, timeDelta)
58     {
59         return this.add(Point.pointOnCircle(angle, velocity * (timeDelta / 1000)));
60     }
61 }
62
63 function Insets(top, right, bottom, left)
64 {
65     this.top = top;
66     this.right = right;
67     this.bottom = bottom;
68     this.left = left;
69 }
70
71 Insets.elementPadding = function(element)
72 {
73     var styles = window.getComputedStyle(element);
74     return new Insets(
75         parseFloat(styles.paddingTop),
76         parseFloat(styles.paddingRight),
77         parseFloat(styles.paddingBottom),
78         parseFloat(styles.paddingTop));
79 }
80
81 Insets.prototype =
82 {
83     get width()
84     {
85         return this.left + this.right;
86     },
87
88     get height()
89     {
90         return this.top + this.bottom;
91     },
92     
93     get size()
94     {
95         return new Point(this.width, this.height);
96     }
97 }
98
99 function SimplePromise()
100 {
101     this._chainedPromise = null;
102     this._callback = null;
103 }
104
105 SimplePromise.prototype.then = function (callback)
106 {
107     if (this._callback)
108         throw "SimplePromise doesn't support multiple calls to then";
109         
110     this._callback = callback;
111     this._chainedPromise = new SimplePromise;
112     
113     if (this._resolved)
114         this.resolve(this._resolvedValue);
115
116     return this._chainedPromise;
117 }
118
119 SimplePromise.prototype.resolve = function (value)
120 {
121     if (!this._callback) {
122         this._resolved = true;
123         this._resolvedValue = value;
124         return;
125     }
126
127     var result = this._callback(value);
128     if (result instanceof SimplePromise) {
129         var chainedPromise = this._chainedPromise;
130         result.then(function (result) { chainedPromise.resolve(result); });
131     } else
132         this._chainedPromise.resolve(result);
133 }
134
135 window.DocumentExtension =
136 {
137     createElement : function(name, attrs, parentElement)
138     {
139         var element = document.createElement(name);
140
141         for (var key in attrs)
142             element.setAttribute(key, attrs[key]);
143
144         parentElement.appendChild(element);
145         return element;
146     },
147
148     createSvgElement: function(name, attrs, xlinkAttrs, parentElement)
149     {
150         const svgNamespace = "http://www.w3.org/2000/svg";
151         const xlinkNamespace = "http://www.w3.org/1999/xlink";
152
153         var element = document.createElementNS(svgNamespace, name);
154         
155         for (var key in attrs)
156             element.setAttribute(key, attrs[key]);
157             
158         for (var key in xlinkAttrs)
159             element.setAttributeNS(xlinkNamespace, key, xlinkAttrs[key]);
160             
161         parentElement.appendChild(element);
162         return element;
163     }
164 }
165
166 function ProgressBar(element, ranges)
167 {
168     this.element = element;
169     this.ranges = ranges;
170     this.currentRange = 0;
171 }
172
173 ProgressBar.prototype =
174 {
175     _progressToPercent: function(progress)
176     {
177         return progress * (100 / this.ranges);
178     },
179     
180     incRange: function()
181     {
182         ++this.currentRange;
183     },
184     
185     setPos: function(progress)
186     {
187         this.element.style.width = this._progressToPercent(this.currentRange + progress) + "%";
188     }
189 }
190
191 function ResultsDashboard()
192 {
193     this._iterationsSamplers = [];
194 }
195
196 ResultsDashboard.prototype =
197 {
198     push: function(suitesSamplers)
199     {
200         this._iterationsSamplers.push(suitesSamplers);        
201     },
202     
203     toJSON: function(statistics, graph)
204     {
205         var iterationsResults = [];
206         var iterationsScores = [];
207         
208         this._iterationsSamplers.forEach(function(iterationSamplers, index) {
209             var suitesResults = {};
210             var suitesScores = [];
211         
212             for (var suiteName in iterationSamplers) {
213                 var suite = suiteFromName(suiteName);
214                 var suiteSamplers = iterationSamplers[suiteName];
215
216                 var testsResults = {};
217                 var testsScores = [];
218                 
219                 for (var testName in suiteSamplers) {
220                     var sampler = suiteSamplers[testName];
221                     testsResults[testName] = sampler.toJSON(statistics, graph);
222                     testsScores.push(testsResults[testName][Strings["JSON_SCORE"]]);
223                 }
224
225                 suitesResults[suiteName] =  {};
226                 suitesResults[suiteName][Strings["JSON_SCORE"]] = Statistics.geometricMean(testsScores);
227                 suitesResults[suiteName][Strings["JSON_RESULTS"][2]] = testsResults;
228                 suitesScores.push(suitesResults[suiteName][Strings["JSON_SCORE"]]);
229             }
230             
231             iterationsResults[index] = {};
232             iterationsResults[index][Strings["JSON_SCORE"]] = Statistics.geometricMean(suitesScores);
233             iterationsResults[index][Strings["JSON_RESULTS"][1]] = suitesResults;
234             iterationsScores.push(iterationsResults[index][Strings["JSON_SCORE"]]);
235         });
236
237         var json = {};
238         json[Strings["JSON_SCORE"]] = Statistics.sampleMean(iterationsScores.length, iterationsScores.reduce(function(a, b) { return a * b; }));
239         json[Strings["JSON_RESULTS"][0]] = iterationsResults;
240         return json;
241     }
242 }
243
244 function ResultsTable(element, headers)
245 {
246     this.element = element;
247     this._headers = headers;
248     this.clear();
249 }
250
251 ResultsTable.prototype =
252 {
253     clear: function()
254     {
255         this.element.innerHTML = "";
256     },
257
258     _showHeaderRow: function(row, queue, headers, message)
259     {
260         headers.forEach(function (header) {
261             var th = DocumentExtension.createElement("th", {}, row);
262             th.textContent = header.text;
263             if (typeof message != "undefined" && message.length) {
264                 th.innerHTML += "<br>" + '[' + message +']';
265                 message = "";
266             }
267             if ("width" in header)
268                 th.width = header.width + "%";
269             queue.push({element: th, headers: header.children });
270         });
271     },
272
273     _showHeader: function(message)
274     {
275         var thead = DocumentExtension.createElement("thead", {}, this.element);
276         var row = DocumentExtension.createElement("tr", {}, thead);
277
278         var queue = [];
279         this._showHeaderRow(row, queue, this._headers, message);
280
281         while (queue.length) {
282             var row = null;
283             var entries = [];
284
285             for (var i = 0, len = queue.length; i < len; ++i) {
286                 var entry = queue.shift();
287
288                 if (!entry.headers.length) {
289                     entries.push(entry.element);
290                     continue;
291                 }
292
293                 if (!row)
294                     row = DocumentExtension.createElement("tr", {}, thead);
295
296                 this._showHeaderRow(row, queue, entry.headers, "");
297                 entry.element.colSpan = entry.headers.length;
298             }
299
300             if (row) {
301                 entries.forEach(function(entry) {
302                     ++entry.rowSpan;
303                 });
304             }
305         }
306     },
307     
308     _showEmptyCell: function(row, className)
309     {
310         return DocumentExtension.createElement("td", { class: className }, row);
311     },
312
313     _showText: function(row, text, className)
314     {
315         var td = DocumentExtension.createElement("td", { class: className }, row);
316         td.textContent = text;
317     },
318
319     _showFixedNumber: function(row, value, digits, className)
320     {
321         var td = DocumentExtension.createElement("td", { class: className }, row);
322         td.textContent = value.toFixed(digits || 2);
323     },
324     
325     _showGraph: function(row, testName, testResults)
326     {
327         var data = testResults[Strings["JSON_SAMPLES"][0]];
328         if (!data) {
329             this._showEmptyCell(row, "");
330             return;
331         }
332         
333         var td = DocumentExtension.createElement("td", {}, row);
334         var button = DocumentExtension.createElement("button", { class: "small-button" }, td);
335
336         button.addEventListener("click", function() {
337             var samples = data[Strings["JSON_GRAPH"][0]];
338             var samplingTimeOffset = data[Strings["JSON_GRAPH"][1]];
339             var axes = Strings["TEXT_EXPERIMENTS"];
340             benchmarkController.showTestGraph(testName, axes, samples, samplingTimeOffset);
341         });
342             
343         button.textContent = Strings["TEXT_RESULTS"][1] + "...";
344     },
345
346     _showJSON: function(row, testName, testResults)
347     {
348         var data = testResults[Strings["JSON_SAMPLES"][0]];
349         if (!data) {
350             this._showEmptyCell(row, "");
351             return;
352         }
353
354         var td = DocumentExtension.createElement("td", {}, row);
355         var button = DocumentExtension.createElement("button", { class: "small-button" }, td);
356
357         button.addEventListener("click", function() {
358             benchmarkController.showTestJSON(testName, testResults);
359         });
360             
361         button.textContent = Strings["TEXT_RESULTS"][2] + "...";
362     },
363     
364     _isNoisyMeasurement: function(index, data, measurement, options)
365     {
366         const percentThreshold = 10;
367         const averageThreshold = 2;
368          
369         if (measurement == Strings["JSON_MEASUREMENTS"][3])
370             return data[Strings["JSON_MEASUREMENTS"][3]] >= percentThreshold;
371             
372         if (index == 1 && measurement == Strings["JSON_MEASUREMENTS"][0])
373             return Math.abs(data[Strings["JSON_MEASUREMENTS"][0]] - options["frame-rate"]) >= averageThreshold;
374
375         return false;
376     },
377
378     _isNoisyTest: function(testResults, options)
379     {
380         for (var index = 0; index < 2; ++index) {
381             var data = testResults[Strings["JSON_EXPERIMENTS"][index]];
382             for (var measurement in data) {
383                 if (this._isNoisyMeasurement(index, data, measurement, options))
384                     return true;
385             }
386         }
387         return false;
388     },
389
390     _showEmptyCells: function(row, headers)
391     {
392         for (var index = 0; index < headers.length; ++index) {
393             if (!headers[index].children.length)
394                 this._showEmptyCell(row, "suites-separator");
395             else
396                 this._showEmptyCells(row, headers[index].children);
397         }
398     },
399
400     _showEmptyRow: function()
401     {
402         var row = DocumentExtension.createElement("tr", {}, this.element);
403         this._showEmptyCells(row, this._headers);
404     },
405
406     _showTest: function(testName, testResults, options)
407     {
408         var row = DocumentExtension.createElement("tr", {}, this.element);
409         var className = this._isNoisyTest(testResults, options) ? "noisy-results" : "";
410         
411         for (var index = 0; index < this._headers.length; ++index) {
412
413             switch (index) {
414             case 0:
415                 this._showText(row, testName, className);
416                 break;
417
418             case 1:
419                 var data = testResults[Strings["JSON_SCORE"][0]];
420                 this._showFixedNumber(row, data, 2);
421                 break;
422
423             case 2:
424             case 3:
425                 var data = testResults[Strings["JSON_EXPERIMENTS"][index - 2]];
426                 for (var measurement in data)
427                     this._showFixedNumber(row, data[measurement], 2, this._isNoisyMeasurement(index - 2, data, measurement, options) ? className : "");
428                 break;
429                 
430             case 4:
431                 this._showGraph(row, testName, testResults);
432                 this._showJSON(row, testName, testResults);
433                 break;
434             }
435         }
436     },
437
438     _showSuite: function(suiteName, suiteResults, options)
439     {
440         for (var testName in suiteResults[Strings["JSON_RESULTS"][2]]) {
441             this._showTest(testName, suiteResults[Strings["JSON_RESULTS"][2]][testName], options);
442         }
443     },
444     
445     _showIteration : function(iterationResults, options)
446     {
447         for (var suiteName in iterationResults[Strings["JSON_RESULTS"][1]]) {
448             if (suiteName != Object.keys(iterationResults[Strings["JSON_RESULTS"][1]])[0])
449                 this._showEmptyRow();
450             this._showSuite(suiteName, iterationResults[Strings["JSON_RESULTS"][1]][suiteName], options);
451         }
452     },
453     
454     showRecord: function(testName, message, testResults, options)
455     {
456         this.clear();
457         this._showHeader(message);
458         this._showTest(testName, testResults, options);
459     },
460
461     showIterations: function(iterationsResults, options)
462     {
463         this.clear();
464         this._showHeader("");
465         
466         iterationsResults.forEach(function(iterationResults) {
467             this._showIteration(iterationResults, options);
468         }, this);
469     }
470 }
471