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