• 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 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