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