Riven/riven/srcgen/node_modules/dot/benchmarks/jslitmus.js

629 lines
15 KiB
JavaScript
Raw Normal View History

// jslitmus.js
//
// Copyright (c) 2010, Robert Kieffer, http://broofa.com
// Available under MIT license (http://en.wikipedia.org/wiki/MIT_License)
(function() {
var root = this;
//
// Platform detect
//
var platform = (function() {
// Platform info object
var p = {
name: null,
version: null,
os: null,
description: 'unknown platform',
toString: function() {return this.description;}
};
if (root.navigator) {
var ua = navigator.userAgent;
// Detect OS
var oses = 'Windows|iPhone OS|(?:Intel |PPC )?Mac OS X|Linux';
p.os = new RegExp('((' + oses + ') +[^ \);]*)').test(ua) ? RegExp.$1.replace(/_/g, '.') : null;
// Detect expected names
p.name = /(Chrome|MSIE|Safari|Opera|Firefox|Minefield)/.test(ua) ? RegExp.$1 : null;
// Detect version
if (p.name == 'Opera') {
p.version = opera.name;
} else if (p.name) {
var vre = new RegExp('(Version|' + p.name + ')[ \/]([^ ;]*)');
p.version = vre.test(ua) ? RegExp.$2 : null;
}
} else if (root.process && process.platform) {
// Support node.js (see http://nodejs.org)
p.name = 'node';
p.version = process.version;
p.os = process.platform;
}
// Set the description
var d = [];
if (p.name) d.push(p.name);
if (p.version) d.push(' ' + p.version);
if (p.os) d.push(' on ' + p.os);
if (d.length) p.description = d.join('');
return p;
})();
//
// Context-specific initialization
//
var sys = null, querystring = null;
if (platform.name == 'node') {
util = require('util');
querystring = require('querystring');
}
//
// Misc convenience methods
//
function log(msg) {
if (typeof(console) != 'undefined') {
console.log(msg);
} else if (sys) {
util.log(msg);
}
}
// nil function
function nilf(x) {
return x;
}
// Copy properties
function extend(dst, src) {
for (var k in src) {
dst[k] = src[k];
}
return dst;
}
// Array: apply f to each item in a
function forEach(a, f) {
for (var i = 0, il = (a && a.length); i < il; i++) {
var o = a[i];
f(o, i);
}
}
// Array: return array of all results of f(item)
function map(a, f) {
var o, res = [];
for (var i = 0, il = (a && a.length); i < il; i++) {
var o = a[i];
res.push(f(o, i));
}
return res;
}
// Array: filter out items for which f(item) is falsy
function filter(a, f) {
var o, res = [];
for (var i = 0, il = (a && a.length); i < il; i++) {
var o = a[i];
if (f(o, i)) res.push(o);
}
return res;
}
// Array: IE doesn't have indexOf in some cases
function indexOf(a, o) {
if (a.indexOf) return a.indexOf(o);
for (var i = 0, l = a.length; i < l; i++) if (a[i] === o) return i;
return -1;
}
// Enhanced escape()
function escape2(s) {
s = s.replace(/,/g, '\\,');
s = querystring ? querystring.escape(s) : escape(s);
s = s.replace(/\+/g, '%2b');
s = s.replace(/ /g, '+');
return s;
}
// join(), for objects. Creates url query param-style strings by default
function join(o, delimit1, delimit2) {
var asQuery = !delimit1 && !delimit2;
if (asQuery) {
delimit1 = '&';
delimit2 = '=';
}
var pairs = [];
for (var key in o) {
var value = o[key];
if (asQuery) value = escape2(value);
pairs.push(key + delimit2 + o[key]);
}
return pairs.join(delimit1);
}
// split(), for object strings. Parses url query param strings by default
function split(s, delimit1, delimit2) {
var asQuery = !delimit1 && !delimit2;
if (asQuery) {
s = s.replace(/.*[?#]/, '');
delimit1 = '&';
delimit2 = '=';
}
if (match) {
var o = query.split(delimit1);
for (var i = 0; i < o.length; i++) {
var pair = o[i].split(new RegExp(delimit2 + '+'));
var key = pair.shift();
var value = (asQuery && pair.length > 1) ? pair.join(delimit2) : pair[0];
o[key] = value;
}
}
return o;
}
// Round x to d significant digits
function sig(x, d) {
var exp = Math.ceil(Math.log(Math.abs(x))/Math.log(10)),
f = Math.pow(10, exp-d);
return Math.round(x/f)*f;
}
// Convert x to a readable string version
function humanize(x, sd) {
var ax = Math.abs(x), res;
sd = sd | 4; // significant digits
if (ax == Infinity) {
res = ax > 0 ? 'Infinity' : '-Infinity';
} else if (ax > 1e9) {
res = sig(x/1e9, sd) + 'G';
} else if (ax > 1e6) {
res = sig(x/1e6, sd) + 'M';
} else if (ax > 1e3) {
res = sig(x/1e3, sd) + 'k';
} else if (ax > .01) {
res = sig(x, sd);
} else if (ax > 1e-3) {
res = sig(x/1e-3, sd) + 'm';
} else if (ax > 1e-6) {
res = sig(x/1e-6, sd) + '\u00b5'; // Greek mu
} else if (ax > 1e-9) {
res = sig(x/1e-9, sd) + 'n';
} else {
res = x ? sig(x, sd) : 0;
}
// Turn values like "1.1000000000005" -> "1.1"
res = (res + '').replace(/0{5,}\d*/, '');
return res;
}
// Node.js-inspired event emitter API, with some enhancements.
function EventEmitter() {
var ee = this;
var listeners = {};
extend(ee, {
on: function(e, f) {
if (!listeners[e]) listeners[e] = [];
listeners[e].push(f);
},
removeListener: function(e, f) {
listeners[e] = filter(listeners[e], function(l) {
return l != f;
});
},
removeAllListeners: function(e) {
listeners[e] = [];
},
emit: function(e) {
var args = Array.prototype.slice.call(arguments, 1);
forEach([].concat(listeners[e], listeners['*']), function(l) {
ee._emitting = e;
if (l) l.apply(ee, args);
});
delete ee._emitting;
}
});
}
//
// Test class
//
/**
* Test manages a single test (created with JSLitmus.test())
*/
function Test(name, f) {
var test = this;
// Test instances get EventEmitter API
EventEmitter.call(test);
if (!f) throw new Error('Undefined test function');
if (!/function[^\(]*\(([^,\)]*)/.test(f)) {
throw new Error('"' + name + '" test: Invalid test function');
}
// If the test function takes an argument, we assume it does the iteration
// for us
var isLoop = !!RegExp.$1;
/**
* Reset test state
*/
function reset() {
delete test.count;
delete test.time;
delete test.running;
test.emit('reset', test);
return test;
}
function clone() {
var test = extend(new Test(name, f), test);
return test.reset();
}
/**
* Run the test n times, and use the best results
*/
function bestOf(n) {
var best = null;
while (n--) {
var t = clone();
t.run(null, true);
if (!best || t.period < best.period) {
best = t;
}
}
extend(test, best);
}
/**
* Start running a test. Default is to run the test asynchronously (via
* setTimeout). Can be made synchronous by passing true for 2nd param
*/
function run(count, synchronous) {
count = count || test.INIT_COUNT;
test.running = true;
if (synchronous) {
_run(count, synchronous);
} else {
setTimeout(function() {
_run(count);
}, 1);
}
return test;
}
/**
* Run, for real
*/
function _run(count, noTimeout) {
try {
var start, f = test.f, now, i = count;
// Start the timer
start = new Date();
// Run the test code
test.count = count;
test.time = 0;
test.period = 0;
test.emit('start', test);
if (isLoop) {
// Test code does it's own iteration
f(count);
} else {
// Do the iteration ourselves
while (i--) f();
}
// Get time test took (in secs)
test.time = Math.max(1,new Date() - start)/1000;
// Store iteration count and per-operation time taken
test.count = count;
test.period = test.time/count;
// Do we need to keep running?
test.running = test.time < test.MIN_TIME;
// Publish results
test.emit('results', test);
// Set up for another run, if needed
if (test.running) {
// Use an iteration count that will (we hope) get us close to the
// MAX_COUNT time.
var x = test.MIN_TIME/test.time;
var pow = Math.pow(2, Math.max(1, Math.ceil(Math.log(x)/Math.log(2))));
count *= pow;
if (count > test.MAX_COUNT) {
throw new Error('Max count exceeded. If this test uses a looping function, make sure the iteration loop is working properly.');
}
if (noTimeout) {
_run(count, noTimeout);
} else {
run(count);
}
} else {
test.emit('complete', test);
}
} catch (err) {
log(err);
// Exceptions are caught and displayed in the test UI
test.emit('error', err);
}
return test;
}
/**
* Get the number of operations per second for this test.
*
* @param normalize if true, iteration loop overhead taken into account.
* Note that normalized tests may return Infinity if the
* test time is of the same order as the calibration time.
*/
function getHz(normalize) {
var p = test.period;
// Adjust period based on the calibration test time
if (normalize) {
var cal = test.isLoop ? Test.LOOP_CAL : Test.NOLOOP_CAL;
if (!cal.period) {
// Run calibration if needed
cal.MIN_TIME = .3;
cal.bestOf(3);
}
// Subtract off calibration time. In theory this should never be
// negative, but in practice the calibration times are affected by a
// variety of factors so just clip to zero and let users test for
// getHz() == Infinity
p = Math.max(0, p - cal.period);
}
return sig(1/p, 4);
}
// Set properties that are specific to this instance
extend(test, {
// Test name
name: name,
// Test function
f: f,
// True if the test function does it's own looping (i.e. takes an arg)
isLoop: isLoop,
clone: clone,
run: run,
bestOf: bestOf,
getHz: getHz,
reset: reset
});
// IE7 doesn't do 'toString' or 'toValue' in object enumerations, so set
// it explicitely here.
test.toString = function() {
if (this.time) {
return this.name + ', f = ' +
humanize(this.getHz()) + 'hz (' +
humanize(this.count) + '/' + humanize(this.time) + 'secs)';
} else {
return this.name + ', count = ' + humanize(this.count);
}
};
};
// Set static properties
extend(Test, {
LOOP_CAL: new Test('loop cal', function(count) {while (count--) {}}),
NOLOOP_CAL: new Test('noloop cal', nilf)
});
// Set default property values
extend(Test.prototype, {
// Initial number of iterations
INIT_COUNT: 10,
// Max iterations allowed (used to detect bad looping functions)
MAX_COUNT: 1e9,
// Minimum time test should take to get valid results (secs)
MIN_TIME: 1.0
});
//
// jslitmus
//
// Set up jslitmus context
var jslitmus;
if (platform.name == 'node') {
jslitmus = exports;
} else {
jslitmus = root.jslitmus = {};
}
var tests = [], // test store (all tests added w/ jslitmus.test())
queue = [], // test queue (to be run)
currentTest; // currently running test
// jslitmus gets EventEmitter API
EventEmitter.call(jslitmus);
/**
* Create a new test
*/
function test(name, f) {
// Create the Test object
var test = new Test(name, f);
tests.push(test);
// Run the next test if this one finished
test.on('*', function() {
// Forward test events to jslitmus listeners
var args = Array.prototype.slice.call(arguments);
args.unshift(test._emitting);
jslitmus.emit.apply(jslitmus, args);
// Auto-run the next test
if (test._emitting == 'complete') {
currentTest = null;
_nextTest();
}
});
jslitmus.emit('added', test);
return test;
}
/**
* Add all tests to the run queue
*/
function runAll(e) {
forEach(tests, _queueTest);
}
/**
* Remove all tests from the run queue. The current test has to finish on
* it's own though
*/
function stop() {
while (queue.length) {
var test = queue.shift();
}
}
/**
* Run the next test in the run queue
*/
function _nextTest() {
if (!currentTest) {
var test = queue.shift();
if (test) {
currentTest = test;
test.run();
} else {
jslitmus.emit('all_complete');
}
}
}
/**
* Add a test to the run queue
*/
function _queueTest(test) {
if (indexOf(queue, test) >= 0) return;
queue.push(test);
_nextTest();
}
function clearAll() {
tests = [];
}
/**
* Generate a Google Chart URL that shows the data for all tests
*/
function getGoogleChart(normalize) {
var chart_title = [
'Operations/second on ' + platform.name,
'(' + platform.version + ' / ' + platform.os + ')'
];
var n = tests.length, markers = [], data = [];
var d, min = 0, max = -1e10;
// Gather test data
var markers = map(tests, function(test, i) {
if (test.count) {
var hz = test.getHz();
var v = hz != Infinity ? hz : 0;
data.push(v);
var label = test.name + '(' + humanize(hz)+ ')';
var marker = 't' + escape2(label) + ',000000,0,' + i + ',10';
max = Math.max(v, max);
return marker;
}
});
if (markers.length <= 0) return null;
// Build labels
var labels = [humanize(min), humanize(max)];
var w = 250, bw = 15;
var bs = 5;
var h = markers.length*(bw + bs) + 30 + chart_title.length*20;
var params = {
chtt: escape(chart_title.join('|')),
chts: '000000,10',
cht: 'bhg', // chart type
chd: 't:' + data.join(','), // data set
chds: min + ',' + max, // max/min of data
chxt: 'x', // label axes
chxl: '0:|' + labels.join('|'), // labels
chsp: '0,1',
chm: markers.join('|'), // test names
chbh: [bw, 0, bs].join(','), // bar widths
// chf: 'bg,lg,0,eeeeee,0,eeeeee,.5,ffffff,1', // gradient
chs: w + 'x' + h
};
var url = 'http://chart.apis.google.com/chart?' + join(params);
return url;
}
// Public API
extend(jslitmus, {
Test: Test,
platform: platform,
test: test,
runAll: runAll,
getGoogleChart: getGoogleChart,
clearAll: clearAll
});
// Expose code goodness we've got here, since it's useful, but do so in a way
// that doesn't commit us to supporting it in future versions.
jslitmus.unsupported = {
nilf: nilf,
log: log,
extend: extend,
forEach: forEach,
filter: filter,
map: map,
indexOf: indexOf,
escape2: escape2,
join: join,
split: split,
sig: sig,
humanize: humanize
};
})();