performance tests should be able to measure runs/sec rather than time
[WebKit-https.git] / PerformanceTests / resources / runner.js
1 // There are tests for computeStatistics() located in LayoutTests/fast/harness/perftests
2
3 var PerfTestRunner = {};
4
5 // To make the benchmark results predictable, we replace Math.random with a
6 // 100% deterministic alternative.
7 PerfTestRunner.randomSeed = PerfTestRunner.initialRandomSeed = 49734321;
8
9 PerfTestRunner.resetRandomSeed = function() {
10     PerfTestRunner.randomSeed = PerfTestRunner.initialRandomSeed
11 }
12
13 PerfTestRunner.random = Math.random = function() {
14     // Robert Jenkins' 32 bit integer hash function.
15     var randomSeed = PerfTestRunner.randomSeed;
16     randomSeed = ((randomSeed + 0x7ed55d16) + (randomSeed << 12))  & 0xffffffff;
17     randomSeed = ((randomSeed ^ 0xc761c23c) ^ (randomSeed >>> 19)) & 0xffffffff;
18     randomSeed = ((randomSeed + 0x165667b1) + (randomSeed << 5))   & 0xffffffff;
19     randomSeed = ((randomSeed + 0xd3a2646c) ^ (randomSeed << 9))   & 0xffffffff;
20     randomSeed = ((randomSeed + 0xfd7046c5) + (randomSeed << 3))   & 0xffffffff;
21     randomSeed = ((randomSeed ^ 0xb55a4f09) ^ (randomSeed >>> 16)) & 0xffffffff;
22     PerfTestRunner.randomSeed = randomSeed;
23     return (randomSeed & 0xfffffff) / 0x10000000;
24 };
25
26 PerfTestRunner.log = function (text) {
27     if (!document.getElementById("log")) {
28         var pre = document.createElement('pre');
29         pre.id = 'log';
30         document.body.appendChild(pre);
31     }
32     document.getElementById("log").innerHTML += text + "\n";
33     window.scrollTo(0, document.body.height);
34 }
35
36 PerfTestRunner.info = function (text) {
37     this.log("Info: " + text);
38 }
39
40 PerfTestRunner.logInfo = function (text) {
41     if (!window.layoutTestController)
42         this.log(text);
43 }
44
45 PerfTestRunner.loadFile = function (path) {
46     var xhr = new XMLHttpRequest();
47     xhr.open("GET", path, false);
48     xhr.send(null);
49     return xhr.responseText;
50 }
51
52 PerfTestRunner.computeStatistics = function (times, unit) {
53     var data = times.slice();
54
55     // Add values from the smallest to the largest to avoid the loss of significance
56     data.sort(function(a,b){return a-b;});
57
58     var middle = Math.floor(data.length / 2);
59     var result = {
60         min: data[0],
61         max: data[data.length - 1],
62         median: data.length % 2 ? data[middle] : (data[middle - 1] + data[middle]) / 2,
63     };
64
65     // Compute the mean and variance using a numerically stable algorithm.
66     var squareSum = 0;
67     result.mean = data[0];
68     result.sum = data[0];
69     for (var i = 1; i < data.length; ++i) {
70         var x = data[i];
71         var delta = x - result.mean;
72         var sweep = i + 1.0;
73         result.mean += delta / sweep;
74         result.sum += x;
75         squareSum += delta * delta * (i / sweep);
76     }
77     result.variance = squareSum / data.length;
78     result.stdev = Math.sqrt(result.variance);
79     result.unit = unit || "ms";
80
81     return result;
82 }
83
84 PerfTestRunner.logStatistics = function (times) {
85     this.log("");
86     var statistics = this.computeStatistics(times, this.unit);
87     this.printStatistics(statistics);
88 }
89
90 PerfTestRunner.printStatistics = function (statistics) {
91     this.log("");
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 PerfTestRunner.gc = function () {
100     if (window.GCController)
101         window.GCController.collect();
102     else {
103         function gcRec(n) {
104             if (n < 1)
105                 return {};
106             var temp = {i: "ab" + i + (i / 100000)};
107             temp += "foo";
108             gcRec(n-1);
109         }
110         for (var i = 0; i < 1000; i++)
111             gcRec(10);
112     }
113 }
114
115 PerfTestRunner._runLoop = function () {
116     if (this._completedRuns < this._runCount) {
117         this.gc();
118         window.setTimeout(function () { PerfTestRunner._runner(); }, 0);
119     } else {
120         this.logStatistics(this._results);
121         this._doneFunction();
122         if (window.layoutTestController)
123             layoutTestController.notifyDone();
124     }
125 }
126
127 PerfTestRunner._runner = function () {
128     var start = Date.now();
129     var totalTime = 0;
130
131     for (var i = 0; i < this._loopsPerRun; ++i) {
132         var returnValue = this._runFunction.call(window);
133         if (returnValue - 0 === returnValue) {
134             if (returnValue <= 0)
135                 this.log("runFunction returned a non-positive value: " + returnValue);
136             totalTime += returnValue;
137         }
138     }
139
140     // Assume totalTime can never be zero when _runFunction returns a number.
141     var time = totalTime ? totalTime : Date.now() - start;
142
143     this.ignoreWarmUpAndLog(time);
144     this._runLoop();
145 }
146
147 PerfTestRunner.ignoreWarmUpAndLog = function (result) {
148     this._completedRuns++;
149
150     var labeledResult = result + " " + this.unit;
151     if (this._completedRuns <= 0)
152         this.log("Ignoring warm-up run (" + labeledResult + ")");
153     else {
154         this._results.push(result);
155         this.log(labeledResult);
156     }
157 }
158
159 PerfTestRunner.initAndStartLoop = function() {
160     this._completedRuns = -1;
161     this.customRunFunction = null;
162     this._results = [];
163     this.log("Running " + this._runCount + " times");
164     this._runLoop();
165 }
166
167 PerfTestRunner.run = function (runFunction, loopsPerRun, runCount, doneFunction) {
168     this._runFunction = runFunction;
169     this._loopsPerRun = loopsPerRun || 10;
170     this._runCount = runCount || 20;
171     this._doneFunction = doneFunction || function () {};
172     this.unit = 'ms';
173     this.initAndStartLoop();
174 }
175
176 PerfTestRunner.runPerSecond = function (test) {
177     this._doneFunction = function () { if (test.done) test.done(); };
178     this._runCount = test.runCount || 20;
179     this._callsPerIteration = 1;
180     this.unit = 'runs/s';
181
182     this._test = test;
183     this._runner = this._perSecondRunner;
184     this.initAndStartLoop();
185 }
186
187 PerfTestRunner._perSecondRunner = function () {
188     var timeToRun = this._test.timeToRun || 750;
189     var totalTime = 0;
190     var i = 0;
191     var callsPerIteration = this._callsPerIteration;
192
193     if (this._test.setup)
194         this._test.setup();
195
196     while (totalTime < timeToRun) {
197         totalTime += this._perSecondRunnerIterator(callsPerIteration);
198         i += callsPerIteration;
199         if (this._completedRuns <= 0 && totalTime < 100)
200             callsPerIteration = Math.max(10, 2 * callsPerIteration);
201     }
202     this._callsPerIteration = callsPerIteration;
203
204     this.ignoreWarmUpAndLog(i * 1000 / totalTime);
205     this._runLoop();
206 }
207
208 PerfTestRunner._perSecondRunnerIterator = function (callsPerIteration) {
209     var startTime = Date.now();
210     for (var i = 0; i < callsPerIteration; i++)
211         this._test.run();
212     return Date.now() - startTime;
213 }
214
215 if (window.layoutTestController) {
216     layoutTestController.waitUntilDone();
217     layoutTestController.dumpAsText();
218 }