Address Said's comments on the benchmark, and do some clean up.
[WebKit-https.git] / PerformanceTests / Animometer / resources / debug-runner / animometer.js
1 ProgressBar = Utilities.createClass(
2     function(element, ranges)
3     {
4         this._element = element;
5         this._ranges = ranges;
6         this._currentRange = 0;
7         this._updateElement();
8     }, {
9
10     _updateElement: function()
11     {
12         this._element.style.width = (this._currentRange * (100 / this._ranges)) + "%";
13     },
14
15     incrementRange: function()
16     {
17         ++this._currentRange;
18         this._updateElement();
19     }
20 });
21
22 DeveloperResultsTable = Utilities.createSubclass(ResultsTable,
23     function(element, headers)
24     {
25         ResultsTable.call(this, element, headers);
26     }, {
27
28     _addGraphButton: function(td, testName, testResults)
29     {
30         var data = testResults[Strings.json.samples];
31         if (!data)
32             return;
33
34         var button = Utilities.createElement("button", { class: "small-button" }, td);
35
36         button.addEventListener("click", function() {
37             var graphData = {
38                 axes: [Strings.text.experiments.complexity, Strings.text.experiments.frameRate],
39                 samples: data,
40                 complexityAverageSamples: testResults[Strings.json.complexityAverageSamples],
41                 averages: {},
42                 marks: testResults[Strings.json.marks]
43             };
44             [Strings.json.experiments.complexity, Strings.json.experiments.frameRate].forEach(function(experiment) {
45                 if (experiment in testResults)
46                     graphData.averages[experiment] = testResults[experiment];
47             });
48
49             [
50                 Strings.json.score,
51                 Strings.json.targetFrameLength
52             ].forEach(function(key) {
53                 if (testResults[key])
54                     graphData[key] = testResults[key];
55             });
56
57             benchmarkController.showTestGraph(testName, graphData);
58         });
59
60         button.textContent = Strings.text.results.graph + "...";
61     },
62
63     _isNoisyMeasurement: function(jsonExperiment, data, measurement, options)
64     {
65         const percentThreshold = 10;
66         const averageThreshold = 2;
67
68         if (measurement == Strings.json.measurements.percent)
69             return data[Strings.json.measurements.percent] >= percentThreshold;
70
71         if (jsonExperiment == Strings.json.experiments.frameRate && measurement == Strings.json.measurements.average)
72             return Math.abs(data[Strings.json.measurements.average] - options["frame-rate"]) >= averageThreshold;
73
74         return false;
75     },
76
77     _addTest: function(testName, testResults, options)
78     {
79         var row = Utilities.createElement("tr", {}, this.element);
80
81         var isNoisy = false;
82         [Strings.json.experiments.complexity, Strings.json.experiments.frameRate].forEach(function (experiment) {
83             var data = testResults[experiment];
84             for (var measurement in data) {
85                 if (this._isNoisyMeasurement(experiment, data, measurement, options))
86                     isNoisy = true;
87             }
88         }, this);
89
90         this._flattenedHeaders.forEach(function (header) {
91             var className = "";
92             if (header.className) {
93                 if (typeof header.className == "function")
94                     className = header.className(testResults, options);
95                 else
96                     className = header.className;
97             }
98
99             if (header.title == Strings.text.testName) {
100                 if (isNoisy)
101                     className += " noisy-results";
102                 var td = Utilities.createElement("td", { class: className }, row);
103                 td.textContent = testName;
104                 return;
105             }
106
107             var td = Utilities.createElement("td", { class: className }, row);
108             if (header.title == Strings.text.results.graph) {
109                 this._addGraphButton(td, testName, testResults);
110             } else if (!("text" in header)) {
111                 td.textContent = testResults[header.title];
112             } else if (typeof header.text == "string") {
113                 var data = testResults[header.text];
114                 if (typeof data == "number")
115                     data = data.toFixed(2);
116                 td.textContent = data;
117             } else {
118                 td.textContent = header.text(testResults, testName);
119             }
120         }, this);
121     }
122 });
123
124 Utilities.extendObject(window.benchmarkRunnerClient, {
125     testsCount: null,
126     progressBar: null,
127
128     initialize: function(suites, options)
129     {
130         this.testsCount = this.iterationCount * suites.reduce(function (count, suite) { return count + suite.tests.length; }, 0);
131         this.options = options;
132     },
133
134     willStartFirstIteration: function ()
135     {
136         this.results = new ResultsDashboard();
137         this.progressBar = new ProgressBar(document.getElementById("progress-completed"), this.testsCount);
138     },
139
140     didRunTest: function()
141     {
142         this.progressBar.incrementRange();
143     }
144 });
145
146 Utilities.extendObject(window.sectionsManager, {
147     setSectionHeader: function(sectionIdentifier, title)
148     {
149         document.querySelector("#" + sectionIdentifier + " h1").textContent = title;
150     },
151
152     populateTable: function(tableIdentifier, headers, data)
153     {
154         var table = new DeveloperResultsTable(document.getElementById(tableIdentifier), headers);
155         table.showIterations(data, benchmarkRunnerClient.options);
156     }
157 });
158
159 window.optionsManager =
160 {
161     valueForOption: function(name)
162     {
163         var formElement = document.forms["benchmark-options"].elements[name];
164         if (formElement.type == "checkbox")
165             return formElement.checked;
166         return formElement.value;
167     },
168
169     updateUIFromLocalStorage: function()
170     {
171         var formElements = document.forms["benchmark-options"].elements;
172
173         for (var i = 0; i < formElements.length; ++i) {
174             var formElement = formElements[i];
175             var name = formElement.id || formElement.name;
176             var type = formElement.type;
177
178             var value = localStorage.getItem(name);
179             if (value === null)
180                 continue;
181
182             if (type == "number")
183                 formElements[name].value = +value;
184             else if (type == "checkbox")
185                 formElements[name].checked = value == "true";
186             else if (type == "radio")
187                 formElements[name].value = value;
188         }
189     },
190
191     updateLocalStorageFromUI: function()
192     {
193         var formElements = document.forms["benchmark-options"].elements;
194         var options = {};
195
196         for (var i = 0; i < formElements.length; ++i) {
197             var formElement = formElements[i];
198             var name = formElement.id || formElement.name;
199             var type = formElement.type;
200
201             if (type == "number")
202                 options[name] = +formElement.value;
203             else if (type == "checkbox")
204                 options[name] = formElement.checked;
205             else if (type == "radio")
206                 options[name] = formElements[name].value;
207
208             try {
209                 localStorage.setItem(name, options[name]);
210             } catch (e) {}
211         }
212
213         return options;
214     }
215 }
216
217 window.suitesManager =
218 {
219     _treeElement: function()
220     {
221         return document.querySelector("#suites > .tree");
222     },
223
224     _suitesElements: function()
225     {
226         return document.querySelectorAll("#suites > ul > li");
227     },
228
229     _checkboxElement: function(element)
230     {
231         return element.querySelector("input[type='checkbox']:not(.expand-button)");
232     },
233
234     _editElement: function(element)
235     {
236         return element.querySelector("input[type='number']");
237     },
238
239     _editsElements: function()
240     {
241         return document.querySelectorAll("#suites input[type='number']");
242     },
243
244     _localStorageNameForTest: function(suiteName, testName)
245     {
246         return suiteName + "/" + testName;
247     },
248
249     _updateSuiteCheckboxState: function(suiteCheckbox)
250     {
251         var numberEnabledTests = 0;
252         suiteCheckbox.testsElements.forEach(function(testElement) {
253             var testCheckbox = this._checkboxElement(testElement);
254             if (testCheckbox.checked)
255                 ++numberEnabledTests;
256         }, this);
257         suiteCheckbox.checked = numberEnabledTests > 0;
258         suiteCheckbox.indeterminate = numberEnabledTests > 0 && numberEnabledTests < suiteCheckbox.testsElements.length;
259     },
260
261     _updateStartButtonState: function()
262     {
263         var suitesElements = this._suitesElements();
264         var startButton = document.querySelector("#intro button");
265
266         for (var i = 0; i < suitesElements.length; ++i) {
267             var suiteElement = suitesElements[i];
268             var suiteCheckbox = this._checkboxElement(suiteElement);
269
270             if (suiteCheckbox.checked) {
271                 startButton.disabled = false;
272                 return;
273             }
274         }
275
276         startButton.disabled = true;
277     },
278
279     _onChangeSuiteCheckbox: function(event)
280     {
281         var selected = event.target.checked;
282         event.target.testsElements.forEach(function(testElement) {
283             var testCheckbox = this._checkboxElement(testElement);
284             testCheckbox.checked = selected;
285         }, this);
286         this._updateStartButtonState();
287     },
288
289     _onChangeTestCheckbox: function(suiteCheckbox)
290     {
291         this._updateSuiteCheckboxState(suiteCheckbox);
292         this._updateStartButtonState();
293     },
294
295     _createSuiteElement: function(treeElement, suite, id)
296     {
297         var suiteElement = Utilities.createElement("li", {}, treeElement);
298         var expand = Utilities.createElement("input", { type: "checkbox",  class: "expand-button", id: id }, suiteElement);
299         var label = Utilities.createElement("label", { class: "tree-label", for: id }, suiteElement);
300
301         var suiteCheckbox = Utilities.createElement("input", { type: "checkbox" }, label);
302         suiteCheckbox.suite = suite;
303         suiteCheckbox.onchange = this._onChangeSuiteCheckbox.bind(this);
304         suiteCheckbox.testsElements = [];
305
306         label.appendChild(document.createTextNode(" " + suite.name));
307         return suiteElement;
308     },
309
310     _createTestElement: function(listElement, test, suiteCheckbox)
311     {
312         var testElement = Utilities.createElement("li", {}, listElement);
313         var span = Utilities.createElement("label", { class: "tree-label" }, testElement);
314
315         var testCheckbox = Utilities.createElement("input", { type: "checkbox" }, span);
316         testCheckbox.test = test;
317         testCheckbox.onchange = function(event) {
318             this._onChangeTestCheckbox(event.target.suiteCheckbox);
319         }.bind(this);
320         testCheckbox.suiteCheckbox = suiteCheckbox;
321
322         suiteCheckbox.testsElements.push(testElement);
323         span.appendChild(document.createTextNode(" " + test.name));
324         var complexity = Utilities.createElement("input", { type: "number" }, testElement);
325         complexity.relatedCheckbox = testCheckbox;
326         complexity.oninput = function(event) {
327             var relatedCheckbox = event.target.relatedCheckbox;
328             relatedCheckbox.checked = true;
329             this._onChangeTestCheckbox(relatedCheckbox.suiteCheckbox);
330         }.bind(this);
331         return testElement;
332     },
333
334     createElements: function()
335     {
336         var treeElement = this._treeElement();
337
338         Suites.forEach(function(suite, index) {
339             var suiteElement = this._createSuiteElement(treeElement, suite, "suite-" + index);
340             var listElement = Utilities.createElement("ul", {}, suiteElement);
341             var suiteCheckbox = this._checkboxElement(suiteElement);
342
343             suite.tests.forEach(function(test) {
344                 var testElement = this._createTestElement(listElement, test, suiteCheckbox);
345             }, this);
346         }, this);
347     },
348
349     updateEditsElementsState: function()
350     {
351         var editsElements = this._editsElements();
352         var showComplexityInputs = optionsManager.valueForOption("adjustment") == "step";
353
354         for (var i = 0; i < editsElements.length; ++i) {
355             var editElement = editsElements[i];
356             if (showComplexityInputs)
357                 editElement.classList.add("selected");
358             else
359                 editElement.classList.remove("selected");
360         }
361     },
362
363     updateDisplay: function()
364     {
365         document.body.className = "display-" + optionsManager.valueForOption("display");
366     },
367
368     updateUIFromLocalStorage: function()
369     {
370         var suitesElements = this._suitesElements();
371
372         for (var i = 0; i < suitesElements.length; ++i) {
373             var suiteElement = suitesElements[i];
374             var suiteCheckbox = this._checkboxElement(suiteElement);
375             var suite = suiteCheckbox.suite;
376
377             suiteCheckbox.testsElements.forEach(function(testElement) {
378                 var testCheckbox = this._checkboxElement(testElement);
379                 var testEdit = this._editElement(testElement);
380                 var test = testCheckbox.test;
381
382                 var str = localStorage.getItem(this._localStorageNameForTest(suite.name, test.name));
383                 if (str === null)
384                     return;
385
386                 var value = JSON.parse(str);
387                 testCheckbox.checked = value.checked;
388                 testEdit.value = value.complexity;
389             }, this);
390
391             this._updateSuiteCheckboxState(suiteCheckbox);
392         }
393
394         this._updateStartButtonState();
395     },
396
397     updateLocalStorageFromUI: function()
398     {
399         var suitesElements = this._suitesElements();
400         var suites = [];
401
402         for (var i = 0; i < suitesElements.length; ++i) {
403             var suiteElement = suitesElements[i];
404             var suiteCheckbox = this._checkboxElement(suiteElement);
405             var suite = suiteCheckbox.suite;
406
407             var tests = [];
408             suiteCheckbox.testsElements.forEach(function(testElement) {
409                 var testCheckbox = this._checkboxElement(testElement);
410                 var testEdit = this._editElement(testElement);
411                 var test = testCheckbox.test;
412
413                 if (testCheckbox.checked) {
414                     test.complexity = testEdit.value;
415                     tests.push(test);
416                 }
417
418                 var value = { checked: testCheckbox.checked, complexity: testEdit.value };
419                 try {
420                     localStorage.setItem(this._localStorageNameForTest(suite.name, test.name), JSON.stringify(value));
421                 } catch (e) {}
422             }, this);
423
424             if (tests.length)
425                 suites.push(new Suite(suiteCheckbox.suite.name, tests));
426         }
427
428         return suites;
429     },
430
431     updateLocalStorageFromJSON: function(iterationResults)
432     {
433         for (var suiteName in iterationResults[Strings.json.results.suites]) {
434             var suiteResults = iterationResults[Strings.json.results.suites][suiteName];
435
436             for (var testName in suiteResults[Strings.json.results.tests]) {
437                 var testResults = suiteResults[Strings.json.results.tests][testName];
438                 var data = testResults[Strings.json.experiments.complexity];
439                 var complexity = Math.round(data[Strings.json.measurements.average]);
440
441                 var value = { checked: true, complexity: complexity };
442                 try {
443                     localStorage.setItem(this._localStorageNameForTest(suiteName, testName), JSON.stringify(value));
444                 } catch (e) {}
445             }
446         }
447     }
448 }
449
450 Utilities.extendObject(window.benchmarkController, {
451     initialize: function()
452     {
453         document.forms["benchmark-options"].addEventListener("change", benchmarkController.onBenchmarkOptionsChanged, true);
454         document.forms["time-graph-options"].addEventListener("change", benchmarkController.onTimeGraphOptionsChanged, true);
455         optionsManager.updateUIFromLocalStorage();
456         suitesManager.createElements();
457         suitesManager.updateUIFromLocalStorage();
458         suitesManager.updateDisplay();
459         suitesManager.updateEditsElementsState();
460     },
461
462     onBenchmarkOptionsChanged: function(event)
463     {
464         if (event.target.name == "adjustment") {
465             suitesManager.updateEditsElementsState();
466             return;
467         }
468         if (event.target.name == "display") {
469             suitesManager.updateDisplay();
470         }
471     },
472
473     startBenchmark: function()
474     {
475         var options = optionsManager.updateLocalStorageFromUI();
476         var suites = suitesManager.updateLocalStorageFromUI();
477         this._startBenchmark(suites, options, "running-test");
478     },
479
480     showResults: function()
481     {
482         if (!this.addedKeyEvent) {
483             document.addEventListener("keypress", this.selectResults, false);
484             this.addedKeyEvent = true;
485         }
486
487         sectionsManager.setSectionScore("results", benchmarkRunnerClient.results.score.toFixed(2));
488         var data = benchmarkRunnerClient.results.data[Strings.json.results.iterations];
489         sectionsManager.populateTable("results-header", Headers.testName, data);
490         sectionsManager.populateTable("results-score", Headers.score, data);
491         sectionsManager.populateTable("results-data", Headers.details, data);
492         sectionsManager.showSection("results", true);
493
494         suitesManager.updateLocalStorageFromJSON(data[0]);
495     },
496
497     showJSONResults: function()
498     {
499         document.querySelector("#results-json textarea").textContent = JSON.stringify(benchmarkRunnerClient.results.data, function(key, value) {
500             if (typeof value == "number")
501                 return value.toFixed(2);
502             return value;
503         });
504         document.querySelector("#results-json button").remove();
505         document.querySelector("#results-json div").classList.remove("hidden");
506     },
507
508     showTestGraph: function(testName, graphData)
509     {
510         sectionsManager.setSectionHeader("test-graph", testName);
511         sectionsManager.showSection("test-graph", true);
512         this.updateGraphData(graphData);
513     }
514 });
515
516 window.addEventListener("load", benchmarkController.initialize);