Home | History | Annotate | Download | only in resources
      1 // There are tests for computeStatistics() located in LayoutTests/fast/harness/perftests
      2 
      3 if (window.testRunner) {
      4     testRunner.waitUntilDone();
      5     testRunner.dumpAsText();
      6 }
      7 
      8 (function () {
      9     var logLines = null;
     10     var completedIterations = -1;
     11     var callsPerIteration = 1;
     12     var currentTest = null;
     13     var results = [];
     14     var jsHeapResults = [];
     15     var iterationCount = undefined;
     16 
     17     var PerfTestRunner = {};
     18 
     19     // To make the benchmark results predictable, we replace Math.random with a
     20     // 100% deterministic alternative.
     21     PerfTestRunner.randomSeed = PerfTestRunner.initialRandomSeed = 49734321;
     22 
     23     PerfTestRunner.resetRandomSeed = function() {
     24         PerfTestRunner.randomSeed = PerfTestRunner.initialRandomSeed
     25     }
     26 
     27     PerfTestRunner.random = Math.random = function() {
     28         // Robert Jenkins' 32 bit integer hash function.
     29         var randomSeed = PerfTestRunner.randomSeed;
     30         randomSeed = ((randomSeed + 0x7ed55d16) + (randomSeed << 12))  & 0xffffffff;
     31         randomSeed = ((randomSeed ^ 0xc761c23c) ^ (randomSeed >>> 19)) & 0xffffffff;
     32         randomSeed = ((randomSeed + 0x165667b1) + (randomSeed << 5))   & 0xffffffff;
     33         randomSeed = ((randomSeed + 0xd3a2646c) ^ (randomSeed << 9))   & 0xffffffff;
     34         randomSeed = ((randomSeed + 0xfd7046c5) + (randomSeed << 3))   & 0xffffffff;
     35         randomSeed = ((randomSeed ^ 0xb55a4f09) ^ (randomSeed >>> 16)) & 0xffffffff;
     36         PerfTestRunner.randomSeed = randomSeed;
     37         return (randomSeed & 0xfffffff) / 0x10000000;
     38     };
     39 
     40     PerfTestRunner.now = window.performance && window.performance.now ? function () { return window.performance.now(); } : Date.now;
     41 
     42     PerfTestRunner.logInfo = function (text) {
     43         if (!window.testRunner)
     44             this.log(text);
     45     }
     46 
     47     PerfTestRunner.loadFile = function (path) {
     48         var xhr = new XMLHttpRequest();
     49         xhr.open("GET", path, false);
     50         xhr.send(null);
     51         return xhr.responseText;
     52     }
     53 
     54     PerfTestRunner.computeStatistics = function (times, unit) {
     55         var data = times.slice();
     56 
     57         // Add values from the smallest to the largest to avoid the loss of significance
     58         data.sort(function(a,b){return a-b;});
     59 
     60         var middle = Math.floor(data.length / 2);
     61         var result = {
     62             min: data[0],
     63             max: data[data.length - 1],
     64             median: data.length % 2 ? data[middle] : (data[middle - 1] + data[middle]) / 2,
     65         };
     66 
     67         // Compute the mean and variance using Knuth's online algorithm (has good numerical stability).
     68         var squareSum = 0;
     69         result.values = times;
     70         result.mean = 0;
     71         for (var i = 0; i < data.length; ++i) {
     72             var x = data[i];
     73             var delta = x - result.mean;
     74             var sweep = i + 1.0;
     75             result.mean += delta / sweep;
     76             squareSum += delta * (x - result.mean);
     77         }
     78         result.variance = data.length <= 1 ? 0 : squareSum / (data.length - 1);
     79         result.stdev = Math.sqrt(result.variance);
     80         result.unit = unit || "ms";
     81 
     82         return result;
     83     }
     84 
     85     PerfTestRunner.logStatistics = function (values, unit, title) {
     86         var statistics = this.computeStatistics(values, unit);
     87         this.log("");
     88         this.log(title);
     89         if (statistics.values)
     90             this.log("values " + statistics.values.join(", ") + " " + statistics.unit);
     91         this.log("avg " + statistics.mean + " " + statistics.unit);
     92         this.log("median " + statistics.median + " " + statistics.unit);
     93         this.log("stdev " + statistics.stdev + " " + statistics.unit);
     94         this.log("min " + statistics.min + " " + statistics.unit);
     95         this.log("max " + statistics.max + " " + statistics.unit);
     96     }
     97 
     98     function getUsedJSHeap() {
     99         return console.memory.usedJSHeapSize;
    100     }
    101 
    102     PerfTestRunner.gc = function () {
    103         if (window.GCController)
    104             window.GCController.collect();
    105         else {
    106             function gcRec(n) {
    107                 if (n < 1)
    108                     return {};
    109                 var temp = {i: "ab" + i + (i / 100000)};
    110                 temp += "foo";
    111                 gcRec(n-1);
    112             }
    113             for (var i = 0; i < 1000; i++)
    114                 gcRec(10);
    115         }
    116     };
    117 
    118     function logInDocument(text) {
    119         if (!document.getElementById("log")) {
    120             var pre = document.createElement("pre");
    121             pre.id = "log";
    122             document.body.appendChild(pre);
    123         }
    124         document.getElementById("log").innerHTML += text + "\n";
    125         window.scrollTo(0, document.body.height);
    126     }
    127 
    128     PerfTestRunner.log = function (text) {
    129         if (logLines)
    130             logLines.push(text);
    131         else
    132             logInDocument(text);
    133     }
    134 
    135     PerfTestRunner.logFatalError = function (text) {
    136         PerfTestRunner.log(text);
    137         finish();
    138     }
    139 
    140     function start(test, runner) {
    141         if (!test) {
    142             PerfTestRunner.logFatalError("Got a bad test object.");
    143             return;
    144         }
    145         currentTest = test;
    146         // FIXME: We should be using multiple instances of test runner on Dromaeo as well but it's too slow now.
    147         // FIXME: Don't hard code the number of in-process iterations to use inside a test runner.
    148         iterationCount = test.dromaeoIterationCount || (window.testRunner ? 5 : 20);
    149         logLines = window.testRunner ? [] : null;
    150         PerfTestRunner.log("Running " + iterationCount + " times");
    151         if (test.doNotIgnoreInitialRun)
    152             completedIterations++;
    153         if (runner)
    154             scheduleNextRun(runner);
    155     }
    156 
    157     function scheduleNextRun(runner) {
    158         PerfTestRunner.gc();
    159         window.setTimeout(function () {
    160             try {
    161                 if (currentTest.setup)
    162                     currentTest.setup();
    163 
    164                 var measuredValue = runner();
    165             } catch (exception) {
    166                 PerfTestRunner.logFatalError("Got an exception while running test.run with name=" + exception.name + ", message=" + exception.message);
    167                 return;
    168             }
    169 
    170             completedIterations++;
    171 
    172             try {
    173                 ignoreWarmUpAndLog(measuredValue);
    174             } catch (exception) {
    175                 PerfTestRunner.logFatalError("Got an exception while logging the result with name=" + exception.name + ", message=" + exception.message);
    176                 return;
    177             }
    178 
    179             if (completedIterations < iterationCount)
    180                 scheduleNextRun(runner);
    181             else
    182                 finish();
    183         }, 0);
    184     }
    185 
    186     function ignoreWarmUpAndLog(measuredValue) {
    187         var labeledResult = measuredValue + " " + PerfTestRunner.unit;
    188         if (completedIterations <= 0)
    189             PerfTestRunner.log("Ignoring warm-up run (" + labeledResult + ")");
    190         else {
    191             results.push(measuredValue);
    192             if (window.internals && !currentTest.doNotMeasureMemoryUsage) {
    193                 jsHeapResults.push(getUsedJSHeap());
    194             }
    195             PerfTestRunner.log(labeledResult);
    196         }
    197     }
    198 
    199     function finish() {
    200         try {
    201             if (currentTest.description)
    202                 PerfTestRunner.log("Description: " + currentTest.description);
    203             PerfTestRunner.logStatistics(results, PerfTestRunner.unit, "Time:");
    204             if (jsHeapResults.length) {
    205                 PerfTestRunner.logStatistics(jsHeapResults, "bytes", "JS Heap:");
    206             }
    207             if (logLines)
    208                 logLines.forEach(logInDocument);
    209             if (currentTest.done)
    210                 currentTest.done();
    211         } catch (exception) {
    212             logInDocument("Got an exception while finalizing the test with name=" + exception.name + ", message=" + exception.message);
    213         }
    214 
    215         if (window.testRunner)
    216             testRunner.notifyDone();
    217     }
    218 
    219     PerfTestRunner.prepareToMeasureValuesAsync = function (test) {
    220         PerfTestRunner.unit = test.unit;
    221         start(test);
    222     }
    223 
    224     PerfTestRunner.measureValueAsync = function (measuredValue) {
    225         completedIterations++;
    226 
    227         try {
    228             ignoreWarmUpAndLog(measuredValue);
    229         } catch (exception) {
    230             PerfTestRunner.logFatalError("Got an exception while logging the result with name=" + exception.name + ", message=" + exception.message);
    231             return;
    232         }
    233 
    234         if (completedIterations >= iterationCount)
    235             finish();
    236     }
    237 
    238     PerfTestRunner.measureTime = function (test) {
    239         PerfTestRunner.unit = "ms";
    240         start(test, measureTimeOnce);
    241     }
    242 
    243     function measureTimeOnce() {
    244         var start = PerfTestRunner.now();
    245         var returnValue = currentTest.run();
    246         var end = PerfTestRunner.now();
    247 
    248         if (returnValue - 0 === returnValue) {
    249             if (returnValue < 0)
    250                 PerfTestRunner.log("runFunction returned a negative value: " + returnValue);
    251             return returnValue;
    252         }
    253 
    254         return end - start;
    255     }
    256 
    257     PerfTestRunner.measureRunsPerSecond = function (test) {
    258         PerfTestRunner.unit = "runs/s";
    259         start(test, measureRunsPerSecondOnce);
    260     }
    261 
    262     function measureRunsPerSecondOnce() {
    263         var timeToRun = 750;
    264         var totalTime = 0;
    265         var numberOfRuns = 0;
    266 
    267         while (totalTime < timeToRun) {
    268             totalTime += callRunAndMeasureTime(callsPerIteration);
    269             numberOfRuns += callsPerIteration;
    270             if (completedIterations < 0 && totalTime < 100)
    271                 callsPerIteration = Math.max(10, 2 * callsPerIteration);
    272         }
    273 
    274         return numberOfRuns * 1000 / totalTime;
    275     }
    276 
    277     function callRunAndMeasureTime(callsPerIteration) {
    278         var startTime = PerfTestRunner.now();
    279         for (var i = 0; i < callsPerIteration; i++)
    280             currentTest.run();
    281         return PerfTestRunner.now() - startTime;
    282     }
    283 
    284 
    285     PerfTestRunner.measurePageLoadTime = function(test) {
    286         test.run = function() {
    287             var file = PerfTestRunner.loadFile(test.path);
    288             if (!test.chunkSize)
    289                 this.chunkSize = 50000;
    290 
    291             var chunks = [];
    292             // The smaller the chunks the more style resolves we do.
    293             // Smaller chunk sizes will show more samples in style resolution.
    294             // Larger chunk sizes will show more samples in line layout.
    295             // Smaller chunk sizes run slower overall, as the per-chunk overhead is high.
    296             var chunkCount = Math.ceil(file.length / this.chunkSize);
    297             for (var chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++) {
    298                 var chunk = file.substr(chunkIndex * this.chunkSize, this.chunkSize);
    299                 chunks.push(chunk);
    300             }
    301 
    302             PerfTestRunner.logInfo("Testing " + file.length + " byte document in " + chunkCount + " " + this.chunkSize + " byte chunks.");
    303 
    304             var iframe = document.createElement("iframe");
    305             document.body.appendChild(iframe);
    306 
    307             iframe.sandbox = '';  // Prevent external loads which could cause write() to return before completing the parse.
    308             iframe.style.width = "600px"; // Have a reasonable size so we're not line-breaking on every character.
    309             iframe.style.height = "800px";
    310             iframe.contentDocument.open();
    311 
    312             for (var chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
    313                 iframe.contentDocument.write(chunks[chunkIndex]);
    314                 // Note that we won't cause a style resolve until we've encountered the <body> element.
    315                 // Thus the number of chunks counted above is not exactly equal to the number of style resolves.
    316                 if (iframe.contentDocument.body)
    317                     iframe.contentDocument.body.clientHeight; // Force a full layout/style-resolve.
    318                 else if (iframe.documentElement.localName == 'html')
    319                     iframe.contentDocument.documentElement.offsetWidth; // Force the painting.
    320             }
    321 
    322             iframe.contentDocument.close();
    323             document.body.removeChild(iframe);
    324         };
    325 
    326         PerfTestRunner.measureTime(test);
    327     }
    328 
    329     window.PerfTestRunner = PerfTestRunner;
    330 })();
    331