Add support for statically linking to a specific test
[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     updateDisplay: function()
196     {
197         document.body.className = "display-" + optionsManager.valueForOption("display");
198     }
199 };
200
201 window.suitesManager =
202 {
203     _treeElement: function()
204     {
205         return document.querySelector("#suites > .tree");
206     },
207
208     _suitesElements: function()
209     {
210         return document.querySelectorAll("#suites > ul > li");
211     },
212
213     _checkboxElement: function(element)
214     {
215         return element.querySelector("input[type='checkbox']:not(.expand-button)");
216     },
217
218     _editElement: function(element)
219     {
220         return element.querySelector("input[type='number']");
221     },
222
223     _editsElements: function()
224     {
225         return document.querySelectorAll("#suites input[type='number']");
226     },
227
228     _localStorageNameForTest: function(suiteName, testName)
229     {
230         return suiteName + "/" + testName;
231     },
232
233     _updateSuiteCheckboxState: function(suiteCheckbox)
234     {
235         var numberEnabledTests = 0;
236         suiteCheckbox.testsElements.forEach(function(testElement) {
237             var testCheckbox = this._checkboxElement(testElement);
238             if (testCheckbox.checked)
239                 ++numberEnabledTests;
240         }, this);
241         suiteCheckbox.checked = numberEnabledTests > 0;
242         suiteCheckbox.indeterminate = numberEnabledTests > 0 && numberEnabledTests < suiteCheckbox.testsElements.length;
243     },
244
245     _updateStartButtonState: function()
246     {
247         var suitesElements = this._suitesElements();
248         var startButton = document.querySelector("#intro button");
249
250         for (var i = 0; i < suitesElements.length; ++i) {
251             var suiteElement = suitesElements[i];
252             var suiteCheckbox = this._checkboxElement(suiteElement);
253
254             if (suiteCheckbox.checked) {
255                 startButton.disabled = false;
256                 return;
257             }
258         }
259
260         startButton.disabled = true;
261     },
262
263     _onChangeSuiteCheckbox: function(event)
264     {
265         var selected = event.target.checked;
266         event.target.testsElements.forEach(function(testElement) {
267             var testCheckbox = this._checkboxElement(testElement);
268             testCheckbox.checked = selected;
269         }, this);
270         this._updateStartButtonState();
271     },
272
273     _onChangeTestCheckbox: function(suiteCheckbox)
274     {
275         this._updateSuiteCheckboxState(suiteCheckbox);
276         this._updateStartButtonState();
277     },
278
279     _createSuiteElement: function(treeElement, suite, id)
280     {
281         var suiteElement = Utilities.createElement("li", {}, treeElement);
282         var expand = Utilities.createElement("input", { type: "checkbox",  class: "expand-button", id: id }, suiteElement);
283         var label = Utilities.createElement("label", { class: "tree-label", for: id }, suiteElement);
284
285         var suiteCheckbox = Utilities.createElement("input", { type: "checkbox" }, label);
286         suiteCheckbox.suite = suite;
287         suiteCheckbox.onchange = this._onChangeSuiteCheckbox.bind(this);
288         suiteCheckbox.testsElements = [];
289
290         label.appendChild(document.createTextNode(" " + suite.name));
291         return suiteElement;
292     },
293
294     _createTestElement: function(listElement, test, suiteCheckbox)
295     {
296         var testElement = Utilities.createElement("li", {}, listElement);
297         var span = Utilities.createElement("label", { class: "tree-label" }, testElement);
298
299         var testCheckbox = Utilities.createElement("input", { type: "checkbox" }, span);
300         testCheckbox.test = test;
301         testCheckbox.onchange = function(event) {
302             this._onChangeTestCheckbox(event.target.suiteCheckbox);
303         }.bind(this);
304         testCheckbox.suiteCheckbox = suiteCheckbox;
305
306         suiteCheckbox.testsElements.push(testElement);
307         span.appendChild(document.createTextNode(" " + test.name + " "));
308
309         testElement.appendChild(document.createTextNode(" "));
310         var link = Utilities.createElement("span", {}, testElement);
311         link.classList.add("link");
312         link.textContent = "link";
313         link.suiteName = Utilities.stripNonASCIICharacters(suiteCheckbox.suite.name);
314         link.testName = test.name;
315         link.onclick = function(event) {
316             var element = event.target;
317             var title = "Link to run “" + element.testName + "” with current options:";
318             var url = location.href.split(/[?#]/)[0];
319             var options = optionsManager.updateLocalStorageFromUI();
320             Utilities.extendObject(options, {
321                 "suite-name": element.suiteName,
322                 "test-name": Utilities.stripNonASCIICharacters(element.testName)
323             });
324             var complexity = suitesManager._editElement(element.parentNode).value;
325             if (complexity)
326                 options.complexity = complexity;
327             prompt(title, url + Utilities.convertObjectToQueryString(options));
328         };
329
330         var complexity = Utilities.createElement("input", { type: "number" }, testElement);
331         complexity.relatedCheckbox = testCheckbox;
332         complexity.oninput = function(event) {
333             var relatedCheckbox = event.target.relatedCheckbox;
334             relatedCheckbox.checked = true;
335             this._onChangeTestCheckbox(relatedCheckbox.suiteCheckbox);
336         }.bind(this);
337         return testElement;
338     },
339
340     createElements: function()
341     {
342         var treeElement = this._treeElement();
343
344         Suites.forEach(function(suite, index) {
345             var suiteElement = this._createSuiteElement(treeElement, suite, "suite-" + index);
346             var listElement = Utilities.createElement("ul", {}, suiteElement);
347             var suiteCheckbox = this._checkboxElement(suiteElement);
348
349             suite.tests.forEach(function(test) {
350                 this._createTestElement(listElement, test, suiteCheckbox);
351             }, this);
352         }, this);
353     },
354
355     updateEditsElementsState: function()
356     {
357         var editsElements = this._editsElements();
358         var showComplexityInputs = ["fixed", "step"].indexOf(optionsManager.valueForOption("controller")) != -1;
359
360         for (var i = 0; i < editsElements.length; ++i) {
361             var editElement = editsElements[i];
362             if (showComplexityInputs)
363                 editElement.classList.add("selected");
364             else
365                 editElement.classList.remove("selected");
366         }
367     },
368
369     updateUIFromLocalStorage: function()
370     {
371         var suitesElements = this._suitesElements();
372
373         for (var i = 0; i < suitesElements.length; ++i) {
374             var suiteElement = suitesElements[i];
375             var suiteCheckbox = this._checkboxElement(suiteElement);
376             var suite = suiteCheckbox.suite;
377
378             suiteCheckbox.testsElements.forEach(function(testElement) {
379                 var testCheckbox = this._checkboxElement(testElement);
380                 var testEdit = this._editElement(testElement);
381                 var test = testCheckbox.test;
382
383                 var str = localStorage.getItem(this._localStorageNameForTest(suite.name, test.name));
384                 if (str === null)
385                     return;
386
387                 var value = JSON.parse(str);
388                 testCheckbox.checked = value.checked;
389                 testEdit.value = value.complexity;
390             }, this);
391
392             this._updateSuiteCheckboxState(suiteCheckbox);
393         }
394
395         this._updateStartButtonState();
396     },
397
398     updateLocalStorageFromUI: function()
399     {
400         var suitesElements = this._suitesElements();
401         var suites = [];
402
403         for (var i = 0; i < suitesElements.length; ++i) {
404             var suiteElement = suitesElements[i];
405             var suiteCheckbox = this._checkboxElement(suiteElement);
406             var suite = suiteCheckbox.suite;
407
408             var tests = [];
409             suiteCheckbox.testsElements.forEach(function(testElement) {
410                 var testCheckbox = this._checkboxElement(testElement);
411                 var testEdit = this._editElement(testElement);
412                 var test = testCheckbox.test;
413
414                 if (testCheckbox.checked) {
415                     test.complexity = testEdit.value;
416                     tests.push(test);
417                 }
418
419                 var value = { checked: testCheckbox.checked, complexity: testEdit.value };
420                 try {
421                     localStorage.setItem(this._localStorageNameForTest(suite.name, test.name), JSON.stringify(value));
422                 } catch (e) {}
423             }, this);
424
425             if (tests.length)
426                 suites.push(new Suite(suiteCheckbox.suite.name, tests));
427         }
428
429         return suites;
430     },
431
432     suitesFromQueryString: function(suiteName, testName)
433     {
434         var suites = [];
435         var suiteRegExp = new RegExp(suiteName, "i");
436         var testRegExp = new RegExp(testName, "i");
437
438         for (var i = 0; i < Suites.length; ++i) {
439             var suite = Suites[i];
440             if (!Utilities.stripNonASCIICharacters(suite.name).match(suiteRegExp))
441                 continue;
442
443             var test;
444             for (var j = 0; j < suite.tests.length; ++j) {
445                 suiteTest = suite.tests[j];
446                 if (Utilities.stripNonASCIICharacters(suiteTest.name).match(testRegExp)) {
447                     test = suiteTest;
448                     break;
449                 }
450             }
451
452             if (!test)
453                 continue;
454
455             suites.push(new Suite(suiteName, [test]));
456         };
457
458         return suites;
459     },
460
461     updateLocalStorageFromJSON: function(results)
462     {
463         for (var suiteName in results[Strings.json.results.tests]) {
464             var suiteResults = results[Strings.json.results.tests][suiteName];
465             for (var testName in suiteResults) {
466                 var testResults = suiteResults[testName];
467                 var data = testResults[Strings.json.controller];
468                 var complexity = Math.round(data[Strings.json.measurements.average]);
469
470                 var value = { checked: true, complexity: complexity };
471                 try {
472                     localStorage.setItem(this._localStorageNameForTest(suiteName, testName), JSON.stringify(value));
473                 } catch (e) {}
474             }
475         }
476     }
477 }
478
479 Utilities.extendObject(window.benchmarkController, {
480     initialize: function()
481     {
482         document.forms["benchmark-options"].addEventListener("change", benchmarkController.onBenchmarkOptionsChanged, true);
483         document.forms["graph-type"].addEventListener("change", benchmarkController.onGraphTypeChanged, true);
484         document.forms["time-graph-options"].addEventListener("change", benchmarkController.onTimeGraphOptionsChanged, true);
485         document.forms["complexity-graph-options"].addEventListener("change", benchmarkController.onComplexityGraphOptionsChanged, true);
486         optionsManager.updateUIFromLocalStorage();
487         optionsManager.updateDisplay();
488
489         if (benchmarkController.startBenchmarkImmediatelyIfEncoded())
490             return;
491
492         suitesManager.createElements();
493         suitesManager.updateUIFromLocalStorage();
494         suitesManager.updateEditsElementsState();
495
496         var dropTarget = document.getElementById("drop-target");
497         function stopEvent(e) {
498             e.stopPropagation();
499             e.preventDefault();
500         }
501         dropTarget.addEventListener("dragenter", stopEvent, false);
502         dropTarget.addEventListener("dragover", stopEvent, false);
503         dropTarget.addEventListener("dragleave", stopEvent, false);
504         dropTarget.addEventListener("drop", function (e) {
505             e.stopPropagation();
506             e.preventDefault();
507
508             if (!e.dataTransfer.files.length)
509                 return;
510
511             var file = e.dataTransfer.files[0];
512
513             var reader = new FileReader();
514             reader.filename = file.name;
515             reader.onload = function(e) {
516                 var run = JSON.parse(e.target.result);
517                 benchmarkRunnerClient.results = new ResultsDashboard(run.options, run.data);
518                 benchmarkController.showResults();
519             };
520
521             reader.readAsText(file);
522             document.title = "File: " + reader.filename;
523         }, false);
524     },
525
526     onBenchmarkOptionsChanged: function(event)
527     {
528         if (event.target.name == "controller") {
529             suitesManager.updateEditsElementsState();
530             return;
531         }
532         if (event.target.name == "display") {
533             optionsManager.updateDisplay();
534         }
535     },
536
537     startBenchmark: function()
538     {
539         benchmarkController.options = optionsManager.updateLocalStorageFromUI();
540         benchmarkController.suites = suitesManager.updateLocalStorageFromUI();
541         this._startBenchmark(benchmarkController.suites, benchmarkController.options, "running-test");
542     },
543
544     startBenchmarkImmediatelyIfEncoded: function()
545     {
546         benchmarkController.options = Utilities.convertQueryStringToObject(location.search);
547         if (!benchmarkController.options)
548             return false;
549
550         benchmarkController.suites = suitesManager.suitesFromQueryString(benchmarkController.options["suite-name"], benchmarkController.options["test-name"]);
551         if (!benchmarkController.suites.length)
552             return false;
553
554         setTimeout(function() {
555             this._startBenchmark(benchmarkController.suites, benchmarkController.options, "running-test");
556         }.bind(this), 0);
557         return true;
558     },
559
560     restartBenchmark: function()
561     {
562         this._startBenchmark(benchmarkController.suites, benchmarkController.options, "running-test");
563     },
564
565     showResults: function()
566     {
567         if (!this.addedKeyEvent) {
568             document.addEventListener("keypress", this.handleKeyPress, false);
569             this.addedKeyEvent = true;
570         }
571
572         var dashboard = benchmarkRunnerClient.results;
573         if (["ramp", "ramp30"].indexOf(dashboard.options["controller"]) != -1)
574             Headers.details[3].disabled = true;
575         else {
576             Headers.details[1].disabled = true;
577             Headers.details[4].disabled = true;
578         }
579
580         sectionsManager.setSectionScore("results", dashboard.score.toFixed(2));
581         sectionsManager.populateTable("results-header", Headers.testName, dashboard);
582         sectionsManager.populateTable("results-score", Headers.score, dashboard);
583         sectionsManager.populateTable("results-data", Headers.details, dashboard);
584         sectionsManager.showSection("results", true);
585
586         suitesManager.updateLocalStorageFromJSON(dashboard.results[0]);
587     },
588
589     showTestGraph: function(testName, testResult, testData)
590     {
591         sectionsManager.setSectionHeader("test-graph", testName);
592         sectionsManager.showSection("test-graph", true);
593         this.updateGraphData(testResult, testData, benchmarkRunnerClient.results.options);
594     }
595 });
596
597 window.addEventListener("load", benchmarkController.initialize);