kruonis
Version:
A tool to perform benchmarks on TS
311 lines (296 loc) • 9.09 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var now = require('performance-now');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var now__default = /*#__PURE__*/_interopDefaultLegacy(now);
/**
* User changeable properties of a Benchmark
*/
class BenchmarkProperties {
/**
* BenchmarkProperties constructor
*
* @param properties the new properties
*/
constructor(properties) {
/**
* The minimum number of cycle runs
*/
this.minCycles = 10;
/**
* The maximum number of cycle runs.
* The algorithm always tries to run the maximum number of runs
*/
this.maxCycles = 100;
/**
* The maximum time a test can run, in seconds.
* Note that minCycles has priority over this setting.
*/
this.maxTime = 15;
/**
* The Benchmark name
*/
this.name = undefined;
//If properties is an object
if (properties === Object(properties))
for (let property_name of Object.keys(properties))
// Only update properties that exist
if (this.hasOwnProperty(property_name))
this[property_name] = properties[property_name];
}
}
/**
* The main class.
* A Benchmark consists of a group of tests that are run and compared regarding the time performances.
*/
class Benchmark {
/**
* Benchmark constructor
*
* @param properties This Benchmark properties used to create a properties object
*/
constructor(properties) {
/**
* This Benchmark properties
*/
this.properties = null;
/**
* The tests added by the user, to be run
*/
this.tests = [];
/**
* The results obtained in the previous run of this benchmark.
* Consists of a list of pairs test name - test stats
*/
this.results = [];
/**
* Function to run on the beginning of this benchmark
*/
this.onBegin = (benchmark) => { };
/**
* Function to run on the end of the benchmark (on the end of all tests)
*/
this.onTestBegin = (benchmark, test) => { };
/**
* Function to run on the end of the benchmark (on the end of all tests)
*/
this.onTestEnd = (benchmark, test) => { };
/**
* Function to run on the end of the benchmark (on the end of all tests)
*/
this.onEnd = (benchmark) => { };
this.properties = new BenchmarkProperties(properties);
}
/**
* Get the @BenchmarkProperties used in this Benchmark, as an object
*/
getProperties() {
return this.properties;
}
/**
* Get the tests that perform on this Benchmark
*/
getTests() {
return this.tests;
}
/**
* Get the results previously obtained on this Benchmark
* @return list of pairs test name - test stats
*/
getResults() {
return this.results;
}
/**
* Add a new test to this Benchmark
*
* @param testName the test name
* @param fn The function to run on this test
* @return the created @Test
*/
add(test) {
this.tests.push(test);
return this;
}
;
/**
* Add an event to this Benchmark. Possibilities:
* - 'onBegin'
* - 'onTestBegin'
* - 'onTestEnd'
* - 'onEnd'
*
* @param eventName The name of the event to be altered
* @param fn The function that will run when the event is called
*/
on(eventName, fn) {
if (eventName.substr(0, 2) == 'on' && this.hasOwnProperty(eventName))
this[eventName] = fn;
return this;
}
/**
* Run this Benchmark list of @Test
*
* @return results obtained as a list of pairs test name - test stats
*/
run() {
this.onBegin(this);
for (let test of this.tests) {
this.onTestBegin(this, test);
test.run(this.getProperties());
this.results.push([test.name, test.getStats()]);
this.onTestEnd(this, test);
}
this.onEnd(this);
return this.results;
}
;
}
class Mean {
calculate(values) {
return values.reduce((acc, val) => acc + val, 0) / values.length;
}
}
class StandardDeviation {
calculate(values) {
const N = values.length;
const mean = values.reduce((acc, val) => acc + val, 0) / N;
return Math.sqrt(values.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / N);
}
}
class Count {
calculate(values) {
return values.length;
}
}
class Maximum {
calculate(values) {
return Math.max(...values);
}
}
class Minimum {
calculate(values) {
return Math.min(...values);
}
}
/**
* The stats resultant of running a test
*/
class Stats {
/**
* Constructor
*
* @param cycleTimes list of obtained performance times of each cycle
*/
constructor(cycleTimes) {
this.mean = new Mean().calculate(cycleTimes);
this.std = new StandardDeviation().calculate(cycleTimes);
this.count = new Count().calculate(cycleTimes);
this.max = new Maximum().calculate(cycleTimes);
this.min = new Minimum().calculate(cycleTimes);
}
}
/**
* A test represents a code that shall be run and afterwards compared regarding the performance
*/
class Test {
/**
* @param name The test name
* @param fn The function to be run
*/
constructor(name, fn) {
this.name = name;
this.fn = fn;
/**
* The measured times for the ran cycles, in milliseconds
*/
this.cycleTimes = [];
/**
* The stats obtained by running this test
*/
this.stats = null;
/**
* Function to run on the begin of the test
*/
this.onBegin = (test) => { };
/**
* Function to run after before running each cycle
*/
this.onCycleBegin = (test) => { };
/**
* Function to run after each cycle completes
*/
this.onCycleEnd = (test) => { };
/**
* Function to run on the end of all test cycles
*/
this.onEnd = (test) => { };
}
/**
* Add an event to this Test. Possibilities:
* - 'onBegin'
* - 'onCycleBegin'
* - 'onCycleEnd'
* - 'onEnd'
*
* @param eventName The name of the event to be altered
* @param fn The function that will run when the event is called
*/
on(eventName, fn) {
if (eventName.substr(0, 2) == 'on' && this.hasOwnProperty(eventName))
this[eventName] = fn;
return this;
}
/**
* Gets the test baseline time (the time it takes to measure the times)
*/
getBaselineTime() {
const startTime = now__default['default']();
const endTime = now__default['default']();
return endTime - startTime;
}
/**
* Run this test according to the given testProperties
*
* @param testProperties the testProperties to use on this test. Similar to a @BenchmarkProperties object
*/
run(testProperties) {
// Times are measured in milliseconds
let totalTime = 0;
const { minCycles, maxCycles, maxTime } = testProperties;
const maxTimeMS = maxTime * Test.SECONDS_TO_MILLISECONDS;
const baselineTime = this.getBaselineTime();
this.onBegin(this);
while (this.cycleTimes.length < minCycles ||
(totalTime < maxTimeMS && this.cycleTimes.length < maxCycles)) {
this.onCycleBegin(this);
// Measure performance
const startTime = now__default['default']();
this.fn();
const endTime = now__default['default']();
// Remove baseline times from performance testing
let testTime = (endTime - startTime - baselineTime);
// Because of small deviations
testTime = testTime < 0 ? 0 : testTime;
// Save times
this.cycleTimes.push(testTime);
totalTime += testTime;
this.onCycleEnd(this);
}
this.stats = new Stats(this.cycleTimes);
this.onEnd(this);
}
;
/**
* Get the stats obtained by running this test
*
* @return @Stats instance
*/
getStats() {
return this.stats;
}
}
Test.SECONDS_TO_MILLISECONDS = 1000;
exports.Benchmark = Benchmark;
exports.BenchmarkProperties = BenchmarkProperties;
exports.Stats = Stats;
exports.Test = Test;