999ec9284d8573c58e192670f1ea5b6598b1a831
[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     insertCssRuleAfter: function(newRule, referenceRule)
166     {
167         var styleSheets = document.styleSheets;
168
169         for (var i = 0; i < styleSheets.length; ++i) {       
170             for (var j = 0; j < styleSheets[i].cssRules.length; ++j) {
171                 if (styleSheets[i].cssRules[j].selectorText == referenceRule) {
172                     styleSheets[i].insertRule(newRule, j + 1);
173                     return true;
174                 }
175             }
176         }
177         
178         return false;
179     }
180 }
181
182 function ProgressBar(element, ranges)
183 {
184     this.element = element;
185     this.ranges = ranges;
186     this.currentRange = 0;
187 }
188
189 ProgressBar.prototype =
190 {
191     _progressToPercent: function(progress)
192     {
193         return progress * (100 / this.ranges);
194     },
195     
196     incRange: function()
197     {
198         ++this.currentRange;
199     },
200     
201     setPos: function(progress)
202     {
203         this.element.style.width = this._progressToPercent(this.currentRange + progress) + "%";
204     }
205 }
206
207 function ResultsDashboard()
208 {
209     this._iterationsSamplers = [];
210 }
211
212 ResultsDashboard.prototype =
213 {
214     push: function(suitesSamplers)
215     {
216         this._iterationsSamplers.push(suitesSamplers);        
217     },
218     
219     toJSON: function(statistics, graph)
220     {
221         var iterationsResults = [];
222         var iterationsScores = [];
223         
224         this._iterationsSamplers.forEach(function(iterationSamplers, index) {
225             var suitesResults = {};
226             var suitesScores = [];
227         
228             for (var suiteName in iterationSamplers) {
229                 var suite = suiteFromName(suiteName);
230                 var suiteSamplers = iterationSamplers[suiteName];
231
232                 var testsResults = {};
233                 var testsScores = [];
234                 
235                 for (var testName in suiteSamplers) {
236                     var sampler = suiteSamplers[testName];
237                     testsResults[testName] = sampler.toJSON(statistics, graph);
238                     testsScores.push(testsResults[testName][Strings["JSON_SCORE"]]);
239                 }
240
241                 suitesResults[suiteName] =  {};
242                 suitesResults[suiteName][Strings["JSON_SCORE"]] = Statistics.geometricMean(testsScores);
243                 suitesResults[suiteName][Strings["JSON_RESULTS"][2]] = testsResults;
244                 suitesScores.push(suitesResults[suiteName][Strings["JSON_SCORE"]]);
245             }
246             
247             iterationsResults[index] = {};
248             iterationsResults[index][Strings["JSON_SCORE"]] = Statistics.geometricMean(suitesScores);
249             iterationsResults[index][Strings["JSON_RESULTS"][1]] = suitesResults;
250             iterationsScores.push(iterationsResults[index][Strings["JSON_SCORE"]]);
251         });
252
253         var json = {};
254         json[Strings["JSON_SCORE"]] = Statistics.sampleMean(iterationsScores.length, iterationsScores.reduce(function(a, b) { return a * b; }));
255         json[Strings["JSON_RESULTS"][0]] = iterationsResults;
256         return json;
257     }
258 }
259
260 function ResultsTable(element, headers)
261 {
262     this.element = element;
263     this._headers = headers;
264     this.clear();
265 }
266
267 ResultsTable.prototype =
268 {
269     clear: function()
270     {
271         this.element.innerHTML = "";
272     },
273
274     _showHeaderRow: function(row, queue, headers, message)
275     {
276         headers.forEach(function (header) {
277             var th = DocumentExtension.createElement("th", {}, row);
278             th.textContent = header.text;
279             if (typeof message != "undefined" && message.length) {
280                 th.innerHTML += "<br>" + '[' + message +']';
281                 message = "";
282             }
283             if ("width" in header)
284                 th.width = header.width + "%";
285             queue.push({element: th, headers: header.children });
286         });
287     },
288
289     _showHeader: function(message)
290     {
291         var thead = DocumentExtension.createElement("thead", {}, this.element);
292         var row = DocumentExtension.createElement("tr", {}, thead);
293
294         var queue = [];
295         this._showHeaderRow(row, queue, this._headers, message);
296
297         while (queue.length) {
298             var row = null;
299             var entries = [];
300
301             for (var i = 0, len = queue.length; i < len; ++i) {
302                 var entry = queue.shift();
303
304                 if (!entry.headers.length) {
305                     entries.push(entry.element);
306                     continue;
307                 }
308
309                 if (!row)
310                     row = DocumentExtension.createElement("tr", {}, thead);
311
312                 this._showHeaderRow(row, queue, entry.headers, "");
313                 entry.element.colSpan = entry.headers.length;
314             }
315
316             if (row) {
317                 entries.forEach(function(entry) {
318                     ++entry.rowSpan;
319                 });
320             }
321         }
322     },
323     
324     _showEmptyCell: function(row, className)
325     {
326         return DocumentExtension.createElement("td", { class: className }, row);
327     },
328
329     _showText: function(row, text, className)
330     {
331         var td = DocumentExtension.createElement("td", { class: className }, row);
332         td.textContent = text;
333     },
334
335     _showFixedNumber: function(row, value, digits, className)
336     {
337         var td = DocumentExtension.createElement("td", { class: className }, row);
338         td.textContent = value.toFixed(digits || 2);
339     },
340     
341     _showGraph: function(row, testName, testResults)
342     {
343         var data = testResults[Strings["JSON_SAMPLES"][0]];
344         if (!data) {
345             this._showEmptyCell(row, "");
346             return;
347         }
348         
349         var td = DocumentExtension.createElement("td", {}, row);
350         var button = DocumentExtension.createElement("button", { class: "small-button" }, td);
351
352         button.addEventListener("click", function() {
353             var samples = data[Strings["JSON_GRAPH"][0]];
354             var samplingTimeOffset = data[Strings["JSON_GRAPH"][1]];
355             var axes = Strings["TEXT_EXPERIMENTS"];
356             benchmarkController.showTestGraph(testName, axes, samples, samplingTimeOffset);
357         });
358             
359         button.textContent = Strings["TEXT_RESULTS"][1] + "...";
360     },
361
362     _showJSON: function(row, testName, testResults)
363     {
364         var data = testResults[Strings["JSON_SAMPLES"][0]];
365         if (!data) {
366             this._showEmptyCell(row, "");
367             return;
368         }
369
370         var td = DocumentExtension.createElement("td", {}, row);
371         var button = DocumentExtension.createElement("button", { class: "small-button" }, td);
372
373         button.addEventListener("click", function() {
374             benchmarkController.showTestJSON(testName, testResults);
375         });
376             
377         button.textContent = Strings["TEXT_RESULTS"][2] + "...";
378     },
379     
380     _isNoisyMeasurement: function(index, data, measurement, options)
381     {
382         const percentThreshold = 10;
383         const averageThreshold = 2;
384          
385         if (measurement == Strings["JSON_MEASUREMENTS"][3])
386             return data[Strings["JSON_MEASUREMENTS"][3]] >= percentThreshold;
387             
388         if (index == 1 && measurement == Strings["JSON_MEASUREMENTS"][0])
389             return Math.abs(data[Strings["JSON_MEASUREMENTS"][0]] - options["frame-rate"]) >= averageThreshold;
390
391         return false;
392     },
393
394     _isNoisyTest: function(testResults, options)
395     {
396         for (var index = 0; index < 2; ++index) {
397             var data = testResults[Strings["JSON_EXPERIMENTS"][index]];
398             for (var measurement in data) {
399                 if (this._isNoisyMeasurement(index, data, measurement, options))
400                     return true;
401             }
402         }
403         return false;
404     },
405
406     _showEmptyCells: function(row, headers)
407     {
408         for (var index = 0; index < headers.length; ++index) {
409             if (!headers[index].children.length)
410                 this._showEmptyCell(row, "suites-separator");
411             else
412                 this._showEmptyCells(row, headers[index].children);
413         }
414     },
415
416     _showEmptyRow: function()
417     {
418         var row = DocumentExtension.createElement("tr", {}, this.element);
419         this._showEmptyCells(row, this._headers);
420     },
421
422     _showTest: function(testName, testResults, options)
423     {
424         var row = DocumentExtension.createElement("tr", {}, this.element);
425         var className = this._isNoisyTest(testResults, options) ? "noisy-results" : "";
426         
427         for (var index = 0; index < this._headers.length; ++index) {
428
429             switch (index) {
430             case 0:
431                 this._showText(row, testName, className);
432                 break;
433
434             case 1:
435                 var data = testResults[Strings["JSON_SCORE"][0]];
436                 this._showFixedNumber(row, data, 2);
437                 break;
438
439             case 2:
440             case 3:
441                 var data = testResults[Strings["JSON_EXPERIMENTS"][index - 2]];
442                 for (var measurement in data)
443                     this._showFixedNumber(row, data[measurement], 2, this._isNoisyMeasurement(index - 2, data, measurement, options) ? className : "");
444                 break;
445                 
446             case 4:
447                 this._showGraph(row, testName, testResults);
448                 this._showJSON(row, testName, testResults);
449                 break;
450             }
451         }
452     },
453
454     _showSuite: function(suiteName, suiteResults, options)
455     {
456         for (var testName in suiteResults[Strings["JSON_RESULTS"][2]]) {
457             this._showTest(testName, suiteResults[Strings["JSON_RESULTS"][2]][testName], options);
458         }
459     },
460     
461     _showIteration : function(iterationResults, options)
462     {
463         for (var suiteName in iterationResults[Strings["JSON_RESULTS"][1]]) {
464             if (suiteName != Object.keys(iterationResults[Strings["JSON_RESULTS"][1]])[0])
465                 this._showEmptyRow();
466             this._showSuite(suiteName, iterationResults[Strings["JSON_RESULTS"][1]][suiteName], options);
467         }
468     },
469     
470     showRecord: function(testName, message, testResults, options)
471     {
472         this.clear();
473         this._showHeader(message);
474         this._showTest(testName, testResults, options);
475     },
476
477     showIterations: function(iterationsResults, options)
478     {
479         this.clear();
480         this._showHeader("");
481         
482         iterationsResults.forEach(function(iterationResults) {
483             this._showIteration(iterationResults, options);
484         }, this);
485     }
486 }
487