run-perf-tests should record indivisual value instead of statistics
[WebKit-https.git] / PerformanceTests / resources / runner.js
1 // There are tests for computeStatistics() located in LayoutTests/fast/harness/perftests
2
3 // We need access to console.memory for the memory measurements
4 if (window.internals)
5      internals.settings.setMemoryInfoEnabled(true);
6
7 var PerfTestRunner = {};
8
9 // To make the benchmark results predictable, we replace Math.random with a
10 // 100% deterministic alternative.
11 PerfTestRunner.randomSeed = PerfTestRunner.initialRandomSeed = 49734321;
12
13 PerfTestRunner.resetRandomSeed = function() {
14     PerfTestRunner.randomSeed = PerfTestRunner.initialRandomSeed
15 }
16
17 PerfTestRunner.random = Math.random = function() {
18     // Robert Jenkins' 32 bit integer hash function.
19     var randomSeed = PerfTestRunner.randomSeed;
20     randomSeed = ((randomSeed + 0x7ed55d16) + (randomSeed << 12))  & 0xffffffff;
21     randomSeed = ((randomSeed ^ 0xc761c23c) ^ (randomSeed >>> 19)) & 0xffffffff;
22     randomSeed = ((randomSeed + 0x165667b1) + (randomSeed << 5))   & 0xffffffff;
23     randomSeed = ((randomSeed + 0xd3a2646c) ^ (randomSeed << 9))   & 0xffffffff;
24     randomSeed = ((randomSeed + 0xfd7046c5) + (randomSeed << 3))   & 0xffffffff;
25     randomSeed = ((randomSeed ^ 0xb55a4f09) ^ (randomSeed >>> 16)) & 0xffffffff;
26     PerfTestRunner.randomSeed = randomSeed;
27     return (randomSeed & 0xfffffff) / 0x10000000;
28 };
29
30 PerfTestRunner.now = window.performance && window.performance.webkitNow ? function () { return window.performance.webkitNow(); } : Date.now;
31
32 PerfTestRunner.log = function (text) {
33     if (this._logLines) {
34         this._logLines.push(text);
35         return;
36     }
37     if (!document.getElementById("log")) {
38         var pre = document.createElement('pre');
39         pre.id = 'log';
40         document.body.appendChild(pre);
41     }
42     document.getElementById("log").innerHTML += text + "\n";
43     window.scrollTo(0, document.body.height);
44 }
45
46 PerfTestRunner.info = function (text) {
47     this.log("Info: " + text);
48 }
49
50 PerfTestRunner.logInfo = function (text) {
51     if (!window.testRunner)
52         this.log(text);
53 }
54
55 PerfTestRunner.loadFile = function (path) {
56     var xhr = new XMLHttpRequest();
57     xhr.open("GET", path, false);
58     xhr.send(null);
59     return xhr.responseText;
60 }
61
62 PerfTestRunner.computeStatistics = function (times, unit) {
63     var data = times.slice();
64
65     // Add values from the smallest to the largest to avoid the loss of significance
66     data.sort(function(a,b){return a-b;});
67
68     var middle = Math.floor(data.length / 2);
69     var result = {
70         min: data[0],
71         max: data[data.length - 1],
72         median: data.length % 2 ? data[middle] : (data[middle - 1] + data[middle]) / 2,
73     };
74
75     // Compute the mean and variance using a numerically stable algorithm.
76     var squareSum = 0;
77     result.values = times;
78     result.mean = data[0];
79     result.sum = data[0];
80     for (var i = 1; i < data.length; ++i) {
81         var x = data[i];
82         var delta = x - result.mean;
83         var sweep = i + 1.0;
84         result.mean += delta / sweep;
85         result.sum += x;
86         squareSum += delta * delta * (i / sweep);
87     }
88     result.variance = squareSum / data.length;
89     result.stdev = Math.sqrt(result.variance);
90     result.unit = unit || "ms";
91
92     return result;
93 }
94
95 PerfTestRunner.logStatistics = function (values, unit, title) {
96     var statistics = this.computeStatistics(values, unit);
97     this.printStatistics(statistics, title);
98 }
99
100 PerfTestRunner.printStatistics = function (statistics, title) {
101     this.log("");
102     this.log(title);
103     this.log("values " + statistics.values.join(', ') + " " + statistics.unit)
104     this.log("avg " + statistics.mean + " " + statistics.unit);
105     this.log("median " + statistics.median + " " + statistics.unit);
106     this.log("stdev " + statistics.stdev + " " + statistics.unit);
107     this.log("min " + statistics.min + " " + statistics.unit);
108     this.log("max " + statistics.max + " " + statistics.unit);
109 }
110
111 PerfTestRunner.gc = function () {
112     if (window.GCController)
113         window.GCController.collect();
114     else {
115         function gcRec(n) {
116             if (n < 1)
117                 return {};
118             var temp = {i: "ab" + i + (i / 100000)};
119             temp += "foo";
120             gcRec(n-1);
121         }
122         for (var i = 0; i < 1000; i++)
123             gcRec(10);
124     }
125 }
126
127 PerfTestRunner._runLoop = function () {
128     if (this._completedRuns < this._runCount) {
129         this.gc();
130         window.setTimeout(function () { PerfTestRunner._runner(); }, 0);
131     } else {
132         if (this._description)
133             this.log("Description: " + this._description);
134         this.logStatistics(this._results, this.unit, "Time:");
135         if (this._jsHeapResults.length) {
136             this.logStatistics(this._jsHeapResults, "bytes", "JS Heap:");
137             this.logStatistics(this._mallocHeapResults, "bytes", "Malloc:");
138         }
139         if (this._logLines) {
140             var logLines = this._logLines;
141             this._logLines = null;
142             var self = this;
143             logLines.forEach(function(text) { self.log(text); });
144         }
145         this._doneFunction();
146         if (window.testRunner)
147             testRunner.notifyDone();
148     }
149 }
150
151 PerfTestRunner._runner = function () {
152     var start = this.now();
153     var totalTime = 0;
154
155     for (var i = 0; i < this._loopsPerRun; ++i) {
156         var returnValue = this._runFunction.call(window);
157         if (returnValue - 0 === returnValue) {
158             if (returnValue <= 0)
159                 this.log("runFunction returned a non-positive value: " + returnValue);
160             totalTime += returnValue;
161         }
162     }
163
164     // Assume totalTime can never be zero when _runFunction returns a number.
165     var time = totalTime ? totalTime : this.now() - start;
166
167     this.ignoreWarmUpAndLog(time);
168     this._runLoop();
169 }
170
171 PerfTestRunner.storeHeapResults = function() {
172     if (!window.internals)
173         return;
174     this._jsHeapResults.push(this.getUsedJSHeap());
175     this._mallocHeapResults.push(this.getUsedMallocHeap());
176 }
177
178 PerfTestRunner.getUsedMallocHeap = function() {
179     var stats = window.internals.mallocStatistics();
180     return stats.committedVMBytes - stats.freeListBytes;
181 }
182
183 PerfTestRunner.getUsedJSHeap = function() {
184     return console.memory.usedJSHeapSize;
185 }
186
187 PerfTestRunner.getAndPrintMemoryStatistics = function() {
188     if (!window.internals)
189         return;
190     var jsMemoryStats = PerfTestRunner.computeStatistics([PerfTestRunner.getUsedJSHeap()], "bytes");
191     PerfTestRunner.printStatistics(jsMemoryStats, "JS Heap:");
192
193     var mallocMemoryStats = PerfTestRunner.computeStatistics([PerfTestRunner.getUsedMallocHeap()], "bytes");
194     PerfTestRunner.printStatistics(mallocMemoryStats, "Malloc:");
195 }
196
197 PerfTestRunner.ignoreWarmUpAndLog = function (result) {
198     this._completedRuns++;
199
200     var labeledResult = result + " " + this.unit;
201     if (this._completedRuns <= 0)
202         this.log("Ignoring warm-up run (" + labeledResult + ")");
203     else {
204         this._results.push(result);
205         this.storeHeapResults();
206         this.log(labeledResult);
207     }
208 }
209
210 PerfTestRunner.initAndStartLoop = function() {
211     this._completedRuns = -1;
212     this.customRunFunction = null;
213     this._results = [];
214     this._jsHeapResults = [];
215     this._mallocHeapResults = [];
216     this._logLines = window.testRunner ? [] : null;
217     this.log("Running " + this._runCount + " times");
218     this._runLoop();
219 }
220
221 PerfTestRunner.run = function (runFunction, loopsPerRun, runCount, doneFunction, description) {
222     this._runFunction = runFunction;
223     this._loopsPerRun = loopsPerRun || 10;
224     this._runCount = runCount || 20;
225     this._doneFunction = doneFunction || function () {};
226     this._description = description || "";
227     this.unit = 'ms';
228     this.initAndStartLoop();
229 }
230
231 PerfTestRunner.runPerSecond = function (test) {
232     this._doneFunction = function () { if (test.done) test.done(); };
233     this._description = test.description || "";
234     this._runCount = test.runCount || 20;
235     this._callsPerIteration = 1;
236     this.unit = 'runs/s';
237
238     this._test = test;
239     this._runner = this._perSecondRunner;
240     this.initAndStartLoop();
241 }
242
243 PerfTestRunner._perSecondRunner = function () {
244     var timeToRun = this._test.timeToRun || 750;
245     var totalTime = 0;
246     var i = 0;
247     var callsPerIteration = this._callsPerIteration;
248
249     if (this._test.setup)
250         this._test.setup();
251
252     while (totalTime < timeToRun) {
253         totalTime += this._perSecondRunnerIterator(callsPerIteration);
254         i += callsPerIteration;
255         if (this._completedRuns < 0 && totalTime < 100)
256             callsPerIteration = Math.max(10, 2 * callsPerIteration);
257     }
258     this._callsPerIteration = callsPerIteration;
259
260     this.ignoreWarmUpAndLog(i * 1000 / totalTime);
261     this._runLoop();
262 }
263
264 PerfTestRunner._perSecondRunnerIterator = function (callsPerIteration) {
265     var startTime = this.now();
266     for (var i = 0; i < callsPerIteration; i++)
267         this._test.run();
268     return this.now() - startTime;
269 }
270
271 if (window.testRunner) {
272     testRunner.waitUntilDone();
273     testRunner.dumpAsText();
274 }