JetStream should have a more rational story for jitter-oriented latency tests
[WebKit-https.git] / PerformanceTests / JetStream / Octane2 / base.js
1 // Copyright 2013 the V8 project authors. All rights reserved.
2 // Copyright (C) 2015 Apple Inc. All rights reserved.
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are
5 // met:
6 //
7 //     * Redistributions of source code must retain the above copyright
8 //       notice, this list of conditions and the following disclaimer.
9 //     * Redistributions in binary form must reproduce the above
10 //       copyright notice, this list of conditions and the following
11 //       disclaimer in the documentation and/or other materials provided
12 //       with the distribution.
13 //     * Neither the name of Google Inc. nor the names of its
14 //       contributors may be used to endorse or promote products derived
15 //       from this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29
30 // Performance.now is used in latency benchmarks, the fallback is Date.now.
31 var performance = performance || {};
32 performance.now = (function() {
33   return performance.now       ||
34          performance.mozNow    ||
35          performance.msNow     ||
36          performance.oNow      ||
37          performance.webkitNow ||
38          Date.now;
39 })();
40
41 // Simple framework for running the benchmark suites and
42 // computing a score based on the timing measurements.
43
44
45 // A benchmark has a name (string) and a function that will be run to
46 // do the performance measurement. The optional setup and tearDown
47 // arguments are functions that will be invoked before and after
48 // running the benchmark, but the running time of these functions will
49 // not be accounted for in the benchmark score.
50 function Benchmark(name, doWarmup, doDeterministic, run, setup, tearDown, latencyResult, minIterations) {
51   this.name = name;
52   this.doWarmup = doWarmup;
53   this.doDeterministic = doDeterministic;
54   this.run = run;
55   this.Setup = setup ? setup : function() { };
56   this.TearDown = tearDown ? tearDown : function() { };
57   this.latencyResult = latencyResult ? latencyResult : null; 
58   this.minIterations = minIterations ? minIterations : 32;
59 }
60
61
62 // Benchmark results hold the benchmark and the measured time used to
63 // run the benchmark. The benchmark score is computed later once a
64 // full benchmark suite has run to completion. If latency is set to 0
65 // then there is no latency score for this benchmark.
66 function BenchmarkResult(benchmark, time, latency) {
67   this.benchmark = benchmark;
68   this.time = time;
69   this.latency = latency;
70 }
71
72
73 // Automatically convert results to numbers. Used by the geometric
74 // mean computation.
75 BenchmarkResult.prototype.valueOf = function() {
76   return this.time;
77 }
78
79
80 // Suites of benchmarks consist of a name and the set of benchmarks in
81 // addition to the reference timing that the final score will be based
82 // on. This way, all scores are relative to a reference run and higher
83 // scores implies better performance.
84 function BenchmarkSuite(name, reference, benchmarks) {
85   this.name = name;
86   this.reference = reference;
87   this.benchmarks = benchmarks;
88   BenchmarkSuite.suites.push(this);
89 }
90
91
92 // Keep track of all declared benchmark suites.
93 BenchmarkSuite.suites = [];
94
95 // Scores are not comparable across versions. Bump the version if
96 // you're making changes that will affect that scores, e.g. if you add
97 // a new benchmark or change an existing one.
98 BenchmarkSuite.version = '9';
99
100 // Override the alert function to throw an exception instead.
101 alert = function(s) {
102   throw "Alert called with argument: " + s;
103 };
104
105
106 // To make the benchmark results predictable, we replace Math.random
107 // with a 100% deterministic alternative.
108 BenchmarkSuite.ResetRNG = function() {
109   Math.random = (function() {
110     var seed = 49734321;
111     return function() {
112       // Robert Jenkins' 32 bit integer hash function.
113       seed = ((seed + 0x7ed55d16) + (seed << 12))  & 0xffffffff;
114       seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff;
115       seed = ((seed + 0x165667b1) + (seed << 5))   & 0xffffffff;
116       seed = ((seed + 0xd3a2646c) ^ (seed << 9))   & 0xffffffff;
117       seed = ((seed + 0xfd7046c5) + (seed << 3))   & 0xffffffff;
118       seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff;
119       return (seed & 0xfffffff) / 0x10000000;
120     };
121   })();
122 }
123
124
125 // Runs all registered benchmark suites and optionally yields between
126 // each individual benchmark to avoid running for too long in the
127 // context of browsers. Once done, the final score is reported to the
128 // runner.
129 BenchmarkSuite.RunSuites = function(runner) {
130   var continuation = null;
131   var suites = BenchmarkSuite.suites;
132   var length = suites.length;
133   BenchmarkSuite.scores = [];
134   var index = 0;
135   function RunStep() {
136     while (continuation || index < length) {
137       if (continuation) {
138         continuation = continuation();
139       } else {
140         var suite = suites[index++];
141         if (runner.NotifyStart) runner.NotifyStart(suite.name);
142         continuation = suite.RunStep(runner);
143       }
144       if (continuation && typeof window != 'undefined' && window.setTimeout) {
145         window.setTimeout(RunStep, 25);
146         return;
147       }
148     }
149
150     // show final result
151     if (runner.NotifyScore) {
152       var score = BenchmarkSuite.GeometricMean(BenchmarkSuite.scores);
153       var formatted = BenchmarkSuite.FormatScore(100 * score);
154       runner.NotifyScore(formatted);
155     }
156   }
157   RunStep();
158 }
159
160
161 // Counts the total number of registered benchmarks. Useful for
162 // showing progress as a percentage.
163 BenchmarkSuite.CountBenchmarks = function() {
164   var result = 0;
165   var suites = BenchmarkSuite.suites;
166   for (var i = 0; i < suites.length; i++) {
167     result += suites[i].benchmarks.length;
168   }
169   return result;
170 }
171
172
173 // Computes the geometric mean of a set of numbers.
174 BenchmarkSuite.GeometricMean = function(numbers) {
175   var log = 0;
176   for (var i = 0; i < numbers.length; i++) {
177     log += Math.log(numbers[i]);
178   }
179   return Math.pow(Math.E, log / numbers.length);
180 }
181
182
183 // Computes the geometric mean of a set of throughput time measurements.
184 BenchmarkSuite.GeometricMeanTime = function(measurements) {
185   var log = 0;
186   for (var i = 0; i < measurements.length; i++) {
187     log += Math.log(measurements[i].time);
188   }
189   return Math.pow(Math.E, log / measurements.length);
190 }
191
192
193 // Computes the average of the worst samples. For example, if percentile is 99, this will report the
194 // average of the worst 1% of the samples.
195 BenchmarkSuite.AverageAbovePercentile = function(numbers, percentile) {
196   // Don't change the original array.
197   numbers = numbers.slice();
198   
199   // Sort in ascending order.
200   numbers.sort(function(a, b) { return a - b; });
201   
202   // Now the elements we want are at the end. Keep removing them until the array size shrinks too much.
203   // Examples assuming percentile = 99:
204   //
205   // - numbers.length starts at 100: we will remove just the worst entry and then not remove anymore,
206   //   since then numbers.length / originalLength = 0.99.
207   //
208   // - numbers.length starts at 1000: we will remove the ten worst.
209   //
210   // - numbers.length starts at 10: we will remove just the worst.
211   var numbersWeWant = [];
212   var originalLength = numbers.length;
213   while (numbers.length / originalLength > percentile / 100)
214     numbersWeWant.push(numbers.pop());
215   
216   var sum = 0;
217   for (var i = 0; i < numbersWeWant.length; ++i)
218     sum += numbersWeWant[i];
219   
220   var result = sum / numbersWeWant.length;
221   
222   // Do a sanity check.
223   if (numbers.length && result < numbers[numbers.length - 1]) {
224     throw "Sanity check fail: the worst case result is " + result +
225       " but we didn't take into account " + numbers;
226   }
227   
228   return result;
229 }
230
231
232 // Computes the geometric mean of a set of latency measurements.
233 BenchmarkSuite.GeometricMeanLatency = function(measurements) {
234   var log = 0;
235   var hasLatencyResult = false;
236   for (var i = 0; i < measurements.length; i++) {
237     if (measurements[i].latency != 0) {
238       log += Math.log(measurements[i].latency);
239       hasLatencyResult = true;
240     }
241   }
242   if (hasLatencyResult) {
243     return Math.pow(Math.E, log / measurements.length);
244   } else {
245     return 0;
246   }
247 }
248
249
250 // Converts a score value to a string with at least three significant
251 // digits.
252 BenchmarkSuite.FormatScore = function(value) {
253   if (value > 100) {
254     return value.toFixed(0);
255   } else {
256     return value.toPrecision(3);
257   }
258 }
259
260 // Notifies the runner that we're done running a single benchmark in
261 // the benchmark suite. This can be useful to report progress.
262 BenchmarkSuite.prototype.NotifyStep = function(result) {
263   this.results.push(result);
264   if (this.runner.NotifyStep) this.runner.NotifyStep(result.benchmark.name);
265 }
266
267
268 // Notifies the runner that we're done with running a suite and that
269 // we have a result which can be reported to the user if needed.
270 BenchmarkSuite.prototype.NotifyResult = function() {
271   var mean = BenchmarkSuite.GeometricMeanTime(this.results);
272   var score = this.reference[0] / mean;
273   BenchmarkSuite.scores.push(score);
274   if (this.runner.NotifyResult) {
275     var formatted = BenchmarkSuite.FormatScore(100 * score);
276     this.runner.NotifyResult(this.name, formatted);
277   }
278   if (this.reference.length == 2) {
279     var meanLatency = BenchmarkSuite.GeometricMeanLatency(this.results);
280     if (meanLatency != 0) {
281       var scoreLatency = this.reference[1] / meanLatency;
282       BenchmarkSuite.scores.push(scoreLatency);
283       if (this.runner.NotifyResult) {
284         var formattedLatency = BenchmarkSuite.FormatScore(100 * scoreLatency)
285         this.runner.NotifyResult(this.name + "Latency", formattedLatency);
286       }
287     }
288   }
289 }
290
291
292 // Notifies the runner that running a benchmark resulted in an error.
293 BenchmarkSuite.prototype.NotifyError = function(error) {
294   if (this.runner.NotifyError) {
295     this.runner.NotifyError(this.name, error);
296   }
297   if (this.runner.NotifyStep) {
298     this.runner.NotifyStep(this.name);
299   }
300 }
301
302
303 // Runs a single benchmark for at least a second and computes the
304 // average time it takes to run a single iteration.
305 BenchmarkSuite.prototype.RunSingleBenchmark = function(benchmark, data) {
306   function Measure(data) {
307     var elapsed = 0;
308     var start = new Date();
309   
310   // Run either for 1 second or for the number of iterations specified
311   // by minIterations, depending on the config flag doDeterministic.
312     for (var i = 0; (benchmark.doDeterministic ? 
313       i<benchmark.minIterations : elapsed < 1000); i++) {
314       benchmark.run();
315       elapsed = new Date() - start;
316     }
317     if (data != null) {
318       data.runs += i;
319       data.elapsed += elapsed;
320     }
321   }
322
323   // Sets up data in order to skip or not the warmup phase.
324   if (!benchmark.doWarmup && data == null) {
325     data = { runs: 0, elapsed: 0 };
326   }
327
328   if (data == null) {
329     Measure(null);
330     return { runs: 0, elapsed: 0 };
331   } else {
332     Measure(data);
333     // If we've run too few iterations, we continue for another second.
334     if (data.runs < benchmark.minIterations) return data;
335     var usec = (data.elapsed * 1000) / data.runs;
336     var latencySamples = (benchmark.latencyResult != null) ? benchmark.latencyResult() : [0];
337     var percentile = 95;
338     var latency = BenchmarkSuite.AverageAbovePercentile(latencySamples, percentile) * 1000;
339     this.NotifyStep(new BenchmarkResult(benchmark, usec, latency));
340     return null;
341   }
342 }
343
344
345 // This function starts running a suite, but stops between each
346 // individual benchmark in the suite and returns a continuation
347 // function which can be invoked to run the next benchmark. Once the
348 // last benchmark has been executed, null is returned.
349 BenchmarkSuite.prototype.RunStep = function(runner) {
350   BenchmarkSuite.ResetRNG();
351   this.results = [];
352   this.runner = runner;
353   var length = this.benchmarks.length;
354   var index = 0;
355   var suite = this;
356   var data;
357
358   // Run the setup, the actual benchmark, and the tear down in three
359   // separate steps to allow the framework to yield between any of the
360   // steps.
361
362   function RunNextSetup() {
363     if (index < length) {
364       try {
365         suite.benchmarks[index].Setup();
366       } catch (e) {
367         suite.NotifyError(e);
368         return null;
369       }
370       return RunNextBenchmark;
371     }
372     suite.NotifyResult();
373     return null;
374   }
375
376   function RunNextBenchmark() {
377     try {
378       data = suite.RunSingleBenchmark(suite.benchmarks[index], data);
379     } catch (e) {
380       suite.NotifyError(e);
381       return null;
382     }
383     // If data is null, we're done with this benchmark.
384     return (data == null) ? RunNextTearDown : RunNextBenchmark();
385   }
386
387   function RunNextTearDown() {
388     try {
389       suite.benchmarks[index++].TearDown();
390     } catch (e) {
391       suite.NotifyError(e);
392       return null;
393     }
394     return RunNextSetup;
395   }
396
397   // Start out running the setup.
398   return RunNextSetup();
399 }