Add JetStream to PerformanceTests
[WebKit-https.git] / PerformanceTests / JetStream / JetStreamDriver.js
1 // Copyright (C) 2014 Apple Inc. All rights reserved.
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions
5 // are met:
6 // 1. Redistributions of source code must retain the above copyright
7 //    notice, this list of conditions and the following disclaimer.
8 // 2. Redistributions in binary form must reproduce the above copyright
9 //    notice, this list of conditions and the following disclaimer in the
10 //    documentation and/or other materials provided with the distribution.
11 //
12 // THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
13 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
14 // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
15 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
16 // BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
17 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
18 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
19 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
20 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
21 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
22 // THE POSSIBILITY OF SUCH DAMAGE.
23
24 var JetStream = (function() {
25     var isRunning;
26     var hasAlreadyRun;
27     var currentPlan;
28     var currentIteration;
29     var numberOfIterations;
30     var accumulator;
31     var selectBenchmark;
32
33     var givenPlans = [];
34     var givenReferences = {};
35     var categoryNames;
36     var plans;
37     var benchmarks;
38
39     // Import Octane benchmarks.
40
41     var tDistribution = [NaN, NaN, 12.71, 4.30, 3.18, 2.78, 2.57, 2.45, 2.36, 2.31, 2.26, 2.23, 2.20, 2.18, 2.16, 2.14, 2.13, 2.12, 2.11, 2.10, 2.09, 2.09, 2.08, 2.07, 2.07, 2.06, 2.06, 2.06, 2.05, 2.05, 2.05, 2.04, 2.04, 2.04, 2.03, 2.03, 2.03, 2.03, 2.03, 2.02, 2.02, 2.02, 2.02, 2.02, 2.02, 2.02, 2.01, 2.01, 2.01, 2.01, 2.01, 2.01, 2.01, 2.01, 2.01, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.96];
42     var tMax = tDistribution.length;
43     var tLimit = 1.96;
44
45     function tDist(n)
46     {
47         if (n > tMax)
48             return tLimit;
49         return tDistribution[n];
50     }
51
52     function displayResultMessage(name, message, style)
53     {
54         var element = document.getElementById("results-cell-" + name);
55         element.innerHTML = message || "—";
56         if (element.classList) {
57             element.classList.remove("result");
58             element.classList.remove("highlighted-result");
59             element.classList.add(style);
60         } else
61             element.className = style;
62     }
63
64     function displayResultMessageForPlan(plan, message, style)
65     {
66         for (var i = plan.benchmarks.length; i--;)
67             displayResultMessage(plan.benchmarks[i].name, message, style);
68     }
69
70     function computeStatistics(values)
71     {
72         if (!values.length)
73             return {n: 0};
74
75         var sum = 0;
76         var n = 0;
77         for (var i = 0; i < values.length; ++i) {
78             if (!(i in values))
79                 continue;
80             sum += values[i];
81             n++;
82         }
83
84         if (n != values.length)
85             return {n: values.length, failed: values.length - n};
86
87         var mean = sum / n;
88
89         if (n <= 2)
90             return {n: n, mean: mean};
91
92         var sumForStdDev = 0;
93         for (var i = 0; i < values.length; ++i) {
94             if (!(i in values))
95                 continue;
96             sumForStdDev += Math.pow(values[i] - mean, 2);
97         }
98         var standardDeviation = Math.sqrt(sumForStdDev / (n - 1));
99         var standardError = standardDeviation / Math.sqrt(n);
100         var interval = tDist(n) * standardError;
101         return {n: n, mean: mean, interval: interval};
102     }
103
104     function formatResult(values, options)
105     {
106         options = options || {};
107         var extraPrecision = options.extraPrecision || 0;
108
109         function prepare(value)
110         {
111             var precision = 4 + extraPrecision;
112             var digitsAfter;
113             if (value) {
114                 var log = Math.log(value) / Math.log(10);
115                 if (log >= precision)
116                     digitsAfter = 0;
117                 else if (log < 0)
118                     digitsAfter = precision;
119                 else
120                     digitsAfter = precision - 1 - (log | 0);
121             } else
122                 digitsAfter = precision - 1;
123
124             return value.toFixed(digitsAfter);
125         }
126
127         var statistics = computeStatistics(values);
128
129         if (!statistics.n)
130             return "";
131
132         if ("failed" in statistics) {
133             if (statistics.n == 1)
134                 return "ERROR";
135             return "ERROR <i>(failed " + statistics.failed + "/" + statistics.n + ")</i>";
136         }
137
138         if ("interval" in statistics)
139             return prepare(statistics.mean) + "<span class=\"interval\"> &plusmn; " + prepare(statistics.interval) + "</span>";
140
141         return prepare(statistics.mean);
142     }
143
144     function runCode(string)
145     {
146         var magic = document.getElementById("magic");
147         magic.contentDocument.body.textContent = "";
148         magic.contentDocument.body.innerHTML = "<iframe id=\"magicframe\" frameborder=\"0\">";
149
150         var magicFrame = magic.contentDocument.getElementById("magicframe");
151         magicFrame.contentDocument.open();
152         magicFrame.contentDocument.write(
153             "<!DOCTYPE html><head><title>benchmark payload</title></head><body><script>\n" +
154             "window.onerror = top.JetStream.reportError;</script>\n" +
155             string + "</body></html>");
156         magicFrame.contentDocument.close();
157     }
158
159     function addPlan(plan)
160     {
161         givenPlans.push(plan);
162     }
163
164     function addReferences(references)
165     {
166         for (var s in references)
167             givenReferences[s] = references[s];
168     }
169
170     function reset()
171     {
172         var categoryMap = {};
173         benchmarks = [];
174         for (var i = 0; i < givenPlans.length; ++i) {
175             var plan = givenPlans[i];
176             if (selectBenchmark && plan.name != selectBenchmark)
177                 continue;
178             for (var j = 0; j < plan.benchmarks.length; ++j) {
179                 var benchmark = plan.benchmarks[j];
180                 benchmarks.push(benchmark);
181                 var benchmarksForCategory = categoryMap[benchmark.category];
182                 if (!benchmarksForCategory)
183                     benchmarksForCategory = categoryMap[benchmark.category] = [];
184                 benchmarksForCategory.push(benchmark);
185                 benchmark.plan = plan;
186                 benchmark.reference = givenReferences[benchmark.name] || 1;
187             }
188         }
189         categoryNames = [];
190         for (var category in categoryMap)
191             categoryNames.push(category);
192         categoryNames.sort();
193
194         var cells = [];
195         for (var i = 0; i < categoryNames.length; ++i) {
196             var categoryName = categoryNames[i];
197             cells.push({kind: "category", name: categoryName});
198             var benchmarksForCategory = categoryMap[categoryName];
199             benchmarksForCategory.sort(function(a, b) {
200                 return a.name.localeCompare(b.name);
201             });
202             for (var j = 0; j < benchmarksForCategory.length; ++j)
203                 cells.push({kind: "benchmark", benchmark: benchmarksForCategory[j]});
204         }
205
206         plans = [];
207         var remainingPlans = [].concat(givenPlans);
208         for (var i = 0; i < cells.length; ++i) {
209             var cell = cells[i];
210             switch (cell.kind) {
211             case "category":
212                 break;
213
214             case "benchmark":
215                 var plan = cell.benchmark.plan;
216                 var index = remainingPlans.indexOf(plan);
217                 if (index < 0)
218                     break;
219                 plans.push(plan);
220                 remainingPlans.splice(index, 1);
221                 break;
222
223             default:
224                 throw "Bad cell kind: " + cell.king;
225             }
226         }
227
228         var numColumns = 3;
229         var columnHeight = Math.ceil((cells.length + 1) / numColumns);
230
231         var resultsTable = document.getElementById("results");
232
233         var text = "";
234
235         text += "<tr>";
236         for (var i = 0; i < numColumns; ++i)
237             text += "<th>Benchmark</th><th>Average Score</th>";
238         text += "</tr>";
239
240         for (var i = 0; i < columnHeight; ++i) {
241             function benchmarkLine(index)
242             {
243                 if (index > cells.length)
244                     return "";
245
246                 var result = "";
247
248                 if (index == cells.length) {
249                     result += "<td class=\"benchmark-name geometric-mean\">Geometric Mean</td>";
250                     result += "<td class=\"result geometric-mean\" id=\"results-cell-geomean\">&mdash;</td>";
251                 } else {
252                     var cell = cells[index];
253                     switch (cell.kind) {
254                     case "category":
255                         result += "<td class=\"benchmark-name category\">" + cell.name + "</td>";
256                         result += "<td class=\"result category\" id=\"results-cell-geomean-" + cell.name + "\">&mdash;</td>";
257                         break;
258
259                     case "benchmark":
260                         var benchmark = cell.benchmark;
261                         result += "<td class=\"benchmark-name\">";
262                         result += "<a href=\"in-depth.html#" + benchmark.name + "\" target=\"_blank\">" + benchmark.name + "</a></td>";
263                         result += "<td class=\"result\" id=\"results-cell-" + benchmark.name + "\">&mdash;</td>";
264                         break;
265
266                     default:
267                         throw "Bad cell kind: " + cell.kind;
268                     }
269                 }
270
271                 return result;
272             }
273
274             text += "<tr>";
275             for (var j = 0; j < numColumns; ++j)
276                 text += benchmarkLine(j * columnHeight + i);
277             text += "</tr>";
278         }
279
280         resultsTable.innerHTML = text;
281
282         document.getElementById("magic").textContent = "";
283
284         for (var i = benchmarks.length; i--;) {
285             benchmarks[i].results = [];
286             benchmarks[i].times = [];
287         }
288
289         currentIteration = 0;
290         currentPlan = -1;
291         isRunning = false;
292         hasAlreadyRun = false;
293     }
294
295     function prepareToStart()
296     {
297         var startButton = document.getElementById("status");
298         startButton.innerHTML =
299             "<a href=\"javascript:void(JetStream.start())\">" +
300             (hasAlreadyRun ? "Test Again" : "Start Test") + "</a>";
301     }
302
303     function initializeWithMode(modeLine)
304     {
305         reset();
306         prepareToStart();
307
308         var experimentalMethod = document.getElementById("mode-description");
309         selectBenchmark = null;
310         var modes = modeLine.split(",");
311         for (var i = 0; i < modes.length; ++i) {
312             var mode = modes[i];
313
314             if (/benchmark=/.test(mode)) {
315                 selectBenchmark = RegExp.rightContext;
316                 continue;
317             }
318
319             var confidenceIntervals = "<a href=\"http://en.wikipedia.org/wiki/Confidence_interval\">confidence intervals</a>";
320
321             switch (mode) {
322             case "quick":
323                 numberOfIterations = 1;
324                 experimentalMethod.innerHTML =
325                     "<strong>Note:</strong> Only one iteration will run per benchmark and no statistics can be calulated.<br><em>This mode is " +
326                     "not statistically valid &mdash; please don't report these results.</em> <a " +
327                     "href=\"javascript:JetStream.switchToNormal()\">Switch to three iterations.</a>";
328                 break;
329
330             case "long":
331                 numberOfIterations = 7;
332                 experimentalMethod.innerHTML =
333                     "<strong>Note:</strong> Seven iterations will run per benchmark and report scores with 95% " +
334                     confidenceIntervals + ".";
335                 break;
336
337             default:
338                 numberOfIterations = 3;
339                 experimentalMethod.textContent = "";
340                 break;
341             }
342         }
343     }
344
345     function initialize()
346     {
347         function initializeWithModeBasedOnHash()
348         {
349             initializeWithMode(window.location.hash.substr(1));
350         }
351
352         initializeWithModeBasedOnHash();
353         window.onpopstate = initializeWithModeBasedOnHash;
354     }
355
356     function switchMode(mode)
357     {
358         window.location.hash = "#" + mode;
359         initializeWithMode(mode);
360     }
361
362     function start()
363     {
364         document.getElementById("status").textContent = "";
365         document.getElementById("result-summary").textContent = "";
366         reset();
367         isRunning = true;
368         iterate();
369     }
370
371     function allSelector(benchmark) { return true; }
372     function createCategorySelector(category) {
373         return function(benchmark) {
374             return benchmark.category == category;
375         };
376     }
377
378     function computeGeomeans(selector)
379     {
380         var geomeans = [];
381
382         for (var iterationIndex = 0; ; ++iterationIndex) {
383             var sum = 0;
384             var numDone = 0;
385             var numSelected = 0;
386             var allFinished = true;
387             for (var i = 0; i < benchmarks.length; ++i) {
388                 if (!selector(benchmarks[i]))
389                     continue;
390                 numSelected++;
391                 if (iterationIndex >= benchmarks[i].results.length) {
392                     allFinished = false;
393                     break;
394                 }
395                 if (!(iterationIndex in benchmarks[i].results))
396                     continue;
397                 sum += Math.log(benchmarks[i].results[iterationIndex]);
398                 numDone++;
399             }
400             if (!allFinished)
401                 break;
402             if (numDone != numSelected)
403                 geomeans.length++;
404             else
405                 geomeans.push(Math.exp(sum * (1 / numDone)));
406         }
407
408         return geomeans;
409     }
410
411     function formatGeomean(selector)
412     {
413         return formatResult(computeGeomeans(selector), {extraPrecision: 1});
414     }
415
416     function updateGeomeans()
417     {
418         for (var i = 0; i < categoryNames.length; ++i) {
419             var categoryName = categoryNames[i];
420             displayResultMessage(
421                 "geomean-" + categoryName,
422                 formatGeomean(createCategorySelector(categoryName)),
423                 "result");
424         }
425         displayResultMessage(
426             "geomean", formatGeomean(allSelector),
427             computeGeomeans(allSelector).length == numberOfIterations ? "highlighted-result" : "result");
428     }
429
430     function computeRawResults()
431     {
432         function rawResultsLine(values) {
433             return {
434                 result: values,
435                 statistics: computeStatistics(values)
436             };
437         }
438
439         var rawResults = {};
440         for (var i = 0; i < benchmarks.length; ++i) {
441             var line = rawResultsLine(benchmarks[i].results);
442             line.times = benchmarks[i].times;
443             line.category = benchmarks[i].category;
444             line.reference = benchmarks[i].reference;
445             rawResults[benchmarks[i].name] = line;
446         }
447         rawResults.geomean = rawResultsLine(computeGeomeans(allSelector));
448
449         return rawResults;
450     }
451
452     function end()
453     {
454         console.log("Raw results:", JSON.stringify(computeRawResults()));
455
456         document.getElementById("result-summary").innerHTML = "<label>Score</label><br><span class=\"score\">" + formatGeomean(allSelector) + "</span>";
457
458         isRunning = false;
459         hasAlreadyRun = true;
460         prepareToStart();
461     }
462
463     function iterate()
464     {
465         ++currentPlan;
466
467         updateGeomeans();
468
469         if (currentPlan >= plans.length) {
470             if (++currentIteration >= numberOfIterations) {
471                 end();
472                 return;
473             } else
474                 currentPlan = 0;
475         }
476
477         document.getElementById("status").innerHTML = "<em>Running iteration " + (currentIteration + 1) + " of " + numberOfIterations + "\u2026</em>";
478         displayResultMessageForPlan(
479             plans[currentPlan], "<em>Running\u2026</em>", "highlighted-result");
480
481         accumulator = void 0;
482
483         window.setTimeout(function() {
484             if (!isRunning)
485                 return;
486             runCode(plans[currentPlan].code);
487         }, 100);
488     }
489
490     function reportError(message, url, lineNumber)
491     {
492         var plan = plans[currentPlan];
493         if (!plan)
494             return;
495         console.error(plan.name + ": ERROR: " + url + ":" + lineNumber + ": " + message);
496
497         for (var i = plan.benchmarks.length; i--;) {
498             plan.benchmarks[i].times.length++;
499             plan.benchmarks[i].results.length++;
500             displayResultMessage(
501                 plan.benchmarks[i].name,
502                 formatResult(plan.benchmarks[i].results, plan.benchmarks[i]),
503                 "result");
504         }
505         iterate();
506     }
507
508     function reportResult()
509     {
510         var plan = plans[currentPlan];
511         for (var i = plan.benchmarks.length; i--;) {
512             var benchmark = plan.benchmarks[i];
513             benchmark.times.push(arguments[i]);
514             benchmark.results.push(100 * benchmark.reference / arguments[i]);
515             displayResultMessage(
516                 benchmark.name,
517                 formatResult(benchmark.results, plan.benchmarks[i]),
518                 "result");
519         }
520         iterate();
521     }
522
523     function accumulate(data)
524     {
525         accumulator = data;
526         window.setTimeout(function() {
527             if (!isRunning)
528                 return;
529             runCode(plans[currentPlan].code);
530         }, 0);
531     }
532
533     function getAccumulator()
534     {
535         return accumulator;
536     }
537
538     return {
539         addPlan: addPlan,
540         addReferences: addReferences,
541         initialize: initialize,
542         switchToQuick: function() { switchMode("quick") },
543         switchToNormal: function() { switchMode("normal") },
544         switchToLong: function() { switchMode("long") },
545         start: start,
546         reportResult: reportResult,
547         reportError: reportError,
548         accumulate: accumulate,
549         getAccumulator: getAccumulator,
550         goodTime: Date.now
551     };
552 })();