3ac2dccde87b47abb9ded319aa711656a0ba3ad6
[WebKit-https.git] / PerformanceTests / Animometer / resources / runner / animometer.js
1 ResultsDashboard = Utilities.createClass(
2     function(options, testData)
3     {
4         this._iterationsSamplers = [];
5         this._options = options;
6         this._results = null;
7         if (testData) {
8             this._iterationsSamplers = testData;
9             this._processData();
10         }
11     }, {
12
13     push: function(suitesSamplers)
14     {
15         this._iterationsSamplers.push(suitesSamplers);
16     },
17
18     _processData: function()
19     {
20         this._results = {};
21         this._results[Strings.json.results.iterations] = [];
22
23         var iterationsScores = [];
24         this._iterationsSamplers.forEach(function(iteration, index) {
25             var testsScores = [];
26
27             var result = {};
28             this._results[Strings.json.results.iterations][index] = result;
29
30             var suitesResult = {};
31             result[Strings.json.results.tests] = suitesResult;
32
33             for (var suiteName in iteration) {
34                 var suiteData = iteration[suiteName];
35
36                 var suiteResult = {};
37                 suitesResult[suiteName] = suiteResult;
38
39                 for (var testName in suiteData) {
40                     if (!suiteData[testName][Strings.json.result])
41                         this.calculateScore(suiteData[testName]);
42
43                     suiteResult[testName] = suiteData[testName][Strings.json.result];
44                     delete suiteData[testName][Strings.json.result];
45
46                     testsScores.push(suiteResult[testName][Strings.json.score]);
47                 }
48             }
49
50             result[Strings.json.score] = Statistics.geometricMean(testsScores);
51             iterationsScores.push(result[Strings.json.score]);
52         }, this);
53
54         this._results[Strings.json.score] = Statistics.sampleMean(iterationsScores.length, iterationsScores.reduce(function(a, b) { return a + b; }));
55     },
56
57     calculateScore: function(data)
58     {
59         var result = {};
60         data[Strings.json.result] = result;
61         var samples = data[Strings.json.samples];
62
63         function findRegression(series) {
64             var minIndex = Math.round(.025 * series.length);
65             var maxIndex = Math.round(.975 * (series.length - 1));
66             var minComplexity = series[minIndex].complexity;
67             var maxComplexity = series[maxIndex].complexity;
68             if (Math.abs(maxComplexity - minComplexity) < 20 && maxIndex - minIndex < 20) {
69                 minIndex = 0;
70                 maxIndex = series.length - 1;
71                 minComplexity = series[minIndex].complexity;
72                 maxComplexity = series[maxIndex].complexity;
73             }
74
75             return {
76                 minComplexity: minComplexity,
77                 maxComplexity: maxComplexity,
78                 samples: series.slice(minIndex, maxIndex + 1),
79                 regression: new Regression(
80                     series,
81                     function (datum, i) { return datum[i].complexity; },
82                     function (datum, i) { return datum[i].frameLength; },
83                     minIndex, maxIndex)
84             };
85         }
86
87         var complexitySamples;
88         [Strings.json.complexity, Strings.json.complexityAverage].forEach(function(seriesName) {
89             if (!(seriesName in samples))
90                 return;
91
92             var regression = {};
93             result[seriesName] = regression;
94             var regressionResult = findRegression(samples[seriesName]);
95             if (seriesName == Strings.json.complexity)
96                 complexitySamples = regressionResult.samples;
97             var calculation = regressionResult.regression;
98             regression[Strings.json.regressions.segment1] = [
99                 [regressionResult.minComplexity, calculation.s1 + calculation.t1 * regressionResult.minComplexity],
100                 [calculation.complexity, calculation.s1 + calculation.t1 * calculation.complexity]
101             ];
102             regression[Strings.json.regressions.segment2] = [
103                 [calculation.complexity, calculation.s2 + calculation.t2 * calculation.complexity],
104                 [regressionResult.maxComplexity, calculation.s2 + calculation.t2 * regressionResult.maxComplexity]
105             ];
106             regression[Strings.json.complexity] = calculation.complexity;
107             regression[Strings.json.measurements.stdev] = Math.sqrt(calculation.error / samples[seriesName].length);
108         });
109
110         if (this._options["adjustment"] == "ramp") {
111             var timeComplexity = new Experiment;
112             data[Strings.json.controller].forEach(function(regression) {
113                 timeComplexity.sample(regression[Strings.json.complexity]);
114             });
115
116             var experimentResult = {};
117             result[Strings.json.controller] = experimentResult;
118             experimentResult[Strings.json.score] = timeComplexity.mean();
119             experimentResult[Strings.json.measurements.average] = timeComplexity.mean();
120             experimentResult[Strings.json.measurements.stdev] = timeComplexity.standardDeviation();
121             experimentResult[Strings.json.measurements.percent] = timeComplexity.percentage();
122
123             result[Strings.json.complexity][Strings.json.bootstrap] = Regression.bootstrap(complexitySamples, 2500, function(resample) {
124                     resample.sort(function(a, b) {
125                         return a.complexity - b.complexity;
126                     });
127
128                     var regressionResult = findRegression(resample);
129                     return regressionResult.regression.complexity;
130                 }, .95);
131             result[Strings.json.score] = result[Strings.json.complexity][Strings.json.bootstrap].median;
132
133         } else {
134             var marks = data[Strings.json.marks];
135             var samplingStartIndex = 0, samplingEndIndex = -1;
136             if (Strings.json.samplingStartTimeOffset in marks)
137                 samplingStartIndex = marks[Strings.json.samplingStartTimeOffset].index;
138             if (Strings.json.samplingEndTimeOffset in marks)
139                 samplingEndIndex = marks[Strings.json.samplingEndTimeOffset].index;
140
141             var averageComplexity = new Experiment;
142             var averageFrameLength = new Experiment;
143             samples[Strings.json.controller].forEach(function (sample, i) {
144                 if (i >= samplingStartIndex && (samplingEndIndex == -1 || i < samplingEndIndex)) {
145                     averageComplexity.sample(sample.complexity);
146                     if (sample.smoothedFrameLength && sample.smoothedFrameLength != -1)
147                         averageFrameLength.sample(sample.smoothedFrameLength);
148                 }
149             });
150
151             var experimentResult = {};
152             result[Strings.json.controller] = experimentResult;
153             experimentResult[Strings.json.measurements.average] = averageComplexity.mean();
154             experimentResult[Strings.json.measurements.concern] = averageComplexity.concern(Experiment.defaults.CONCERN);
155             experimentResult[Strings.json.measurements.stdev] = averageComplexity.standardDeviation();
156             experimentResult[Strings.json.measurements.percent] = averageComplexity.percentage();
157
158             experimentResult = {};
159             result[Strings.json.frameLength] = experimentResult;
160             experimentResult[Strings.json.measurements.average] = 1000 / averageFrameLength.mean();
161             experimentResult[Strings.json.measurements.concern] = averageFrameLength.concern(Experiment.defaults.CONCERN);
162             experimentResult[Strings.json.measurements.stdev] = averageFrameLength.standardDeviation();
163             experimentResult[Strings.json.measurements.percent] = averageFrameLength.percentage();
164
165             result[Strings.json.score] = averageComplexity.score(Experiment.defaults.CONCERN);
166         }
167     },
168
169     get data()
170     {
171         return this._iterationsSamplers;
172     },
173
174     get results()
175     {
176         if (this._results)
177             return this._results[Strings.json.results.iterations];
178         this._processData();
179         return this._results[Strings.json.results.iterations];
180     },
181
182     get options()
183     {
184         return this._options;
185     },
186
187     get score()
188     {
189         if (this._results)
190             return this._results[Strings.json.score];
191         this._processData();
192         return this._results[Strings.json.score];
193     }
194 });
195
196 ResultsTable = Utilities.createClass(
197     function(element, headers)
198     {
199         this.element = element;
200         this._headers = headers;
201
202         this._flattenedHeaders = [];
203         this._headers.forEach(function(header) {
204             if (header.disabled)
205                 return;
206
207             if (header.children)
208                 this._flattenedHeaders = this._flattenedHeaders.concat(header.children);
209             else
210                 this._flattenedHeaders.push(header);
211         }, this);
212
213         this._flattenedHeaders = this._flattenedHeaders.filter(function (header) {
214             return !header.disabled;
215         });
216
217         this.clear();
218     }, {
219
220     clear: function()
221     {
222         this.element.innerHTML = "";
223     },
224
225     _addHeader: function()
226     {
227         var thead = Utilities.createElement("thead", {}, this.element);
228         var row = Utilities.createElement("tr", {}, thead);
229
230         this._headers.forEach(function (header) {
231             if (header.disabled)
232                 return;
233
234             var th = Utilities.createElement("th", {}, row);
235             if (header.title != Strings.text.graph)
236                 th.textContent = header.title;
237             if (header.children)
238                 th.colSpan = header.children.length;
239         });
240     },
241
242     _addEmptyRow: function()
243     {
244         var row = Utilities.createElement("tr", {}, this.element);
245         this._flattenedHeaders.forEach(function (header) {
246             return Utilities.createElement("td", { class: "suites-separator" }, row);
247         });
248     },
249
250     _addTest: function(testName, testResults, options)
251     {
252         var row = Utilities.createElement("tr", {}, this.element);
253
254         this._flattenedHeaders.forEach(function (header) {
255             var td = Utilities.createElement("td", {}, row);
256             if (header.title == Strings.text.testName) {
257                 td.textContent = testName;
258             } else if (header.text) {
259                 var data = testResults[header.text];
260                 if (typeof data == "number")
261                     data = data.toFixed(2);
262                 td.textContent = data;
263             }
264         }, this);
265     },
266
267     _addIteration: function(iterationResult, iterationData, options)
268     {
269         var testsResults = iterationResult[Strings.json.results.tests];
270         for (var suiteName in testsResults) {
271             this._addEmptyRow();
272             var suiteResult = testsResults[suiteName];
273             var suiteData = iterationData[suiteName];
274             for (var testName in suiteResult)
275                 this._addTest(testName, suiteResult[testName], options, suiteData[testName]);
276         }
277     },
278
279     showIterations: function(dashboard)
280     {
281         this.clear();
282         this._addHeader();
283
284         var iterationsResults = dashboard.results;
285         iterationsResults.forEach(function(iterationResult, index) {
286             this._addIteration(iterationResult, dashboard.data[index], dashboard.options);
287         }, this);
288     }
289 });
290
291 window.benchmarkRunnerClient = {
292     iterationCount: 1,
293     options: null,
294     results: null,
295
296     initialize: function(suites, options)
297     {
298         this.options = options;
299     },
300
301     willStartFirstIteration: function()
302     {
303         this.results = new ResultsDashboard(this.options);
304     },
305
306     didRunSuites: function(suitesSamplers)
307     {
308         this.results.push(suitesSamplers);
309     },
310
311     didRunTest: function(testData)
312     {
313         this.results.calculateScore(testData);
314     },
315
316     didFinishLastIteration: function()
317     {
318         benchmarkController.showResults();
319     }
320 };
321
322 window.sectionsManager =
323 {
324     showSection: function(sectionIdentifier, pushState)
325     {
326         var currentSectionElement = document.querySelector("section.selected");
327         console.assert(currentSectionElement);
328
329         var newSectionElement = document.getElementById(sectionIdentifier);
330         console.assert(newSectionElement);
331
332         currentSectionElement.classList.remove("selected");
333         newSectionElement.classList.add("selected");
334
335         if (pushState)
336             history.pushState({section: sectionIdentifier}, document.title);
337     },
338
339     setSectionScore: function(sectionIdentifier, score, mean)
340     {
341         document.querySelector("#" + sectionIdentifier + " .score").textContent = score;
342         if (mean)
343             document.querySelector("#" + sectionIdentifier + " .mean").innerHTML = mean;
344     },
345
346     populateTable: function(tableIdentifier, headers, dashboard)
347     {
348         var table = new ResultsTable(document.getElementById(tableIdentifier), headers);
349         table.showIterations(dashboard);
350     }
351 };
352
353 window.benchmarkController = {
354     _startBenchmark: function(suites, options, frameContainerID)
355     {
356         benchmarkRunnerClient.initialize(suites, options);
357         var frameContainer = document.getElementById(frameContainerID);
358         var runner = new BenchmarkRunner(suites, frameContainer, benchmarkRunnerClient);
359         runner.runMultipleIterations();
360
361         sectionsManager.showSection("test-container");
362     },
363
364     startBenchmark: function()
365     {
366         var options = {
367             "test-interval": 10,
368             "display": "minimal",
369             "adjustment": "adaptive",
370             "frame-rate": 50,
371             "kalman-process-error": 1,
372             "kalman-measurement-error": 4,
373             "time-measurement": "performance"
374         };
375         this._startBenchmark(Suites, options, "test-container");
376     },
377
378     showResults: function()
379     {
380         if (!this.addedKeyEvent) {
381             document.addEventListener("keypress", this.selectResults, false);
382             this.addedKeyEvent = true;
383         }
384
385         var dashboard = benchmarkRunnerClient.results;
386
387         sectionsManager.setSectionScore("results", dashboard.score.toFixed(2));
388         sectionsManager.populateTable("results-header", Headers.testName, dashboard);
389         sectionsManager.populateTable("results-score", Headers.score, dashboard);
390         sectionsManager.showSection("results", true);
391     },
392
393     selectResults: function(event)
394     {
395         if (event.charCode != 115) // 's'
396             return;
397
398         event.target.selectRange = ((event.target.selectRange || 0) + 1) % 3;
399
400         var selection = window.getSelection();
401         selection.removeAllRanges();
402         var range = document.createRange();
403         switch (event.target.selectRange) {
404             case 0: {
405                 range.setStart(document.querySelector("#results .score"), 0);
406                 range.setEndAfter(document.querySelector("#results-score > tr:last-of-type"), 0);
407                 break;
408             }
409             case 1: {
410                 range.selectNodeContents(document.querySelector("#results .score"));
411                 break;
412             }
413             case 2: {
414                 range.selectNode(document.getElementById("results-score"));
415                 break;
416             }
417         }
418         selection.addRange(range);
419     }
420 };