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