1// There are tests for computeStatistics() located in LayoutTests/fast/harness/perftests 2 3// We need access to console.memory for the memory measurements 4if (window.internals) 5 internals.settings.setMemoryInfoEnabled(true); 6 7if (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