• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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