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