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