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