trakr
Version:
Minimal utility for tracking performance
318 lines • 9.72 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Timer = exports.Tracker = exports.TRACER = exports.Tracer = exports.Stats = void 0;
const IDENTITY = (a) => a;
const NODE = typeof module !== 'undefined' && module.exports;
// @ts-ignore
const PERF = (NODE ? require('perf_hooks') : window).performance;
// T-Distribution two-tailed critical values for 95% confidence.
// http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm.
const TABLE = [
12.706, 4.303, 3.182, 2.776, 2.571, 2.447, 2.365, 2.306, 2.262, 2.228,
2.201, 2.179, 2.16, 2.145, 2.131, 2.12, 2.11, 2.101, 2.093, 2.086,
2.08, 2.074, 2.069, 2.064, 2.06, 2.056, 2.052, 2.048, 2.045, 2.042,
];
const TINF = 1.96;
class Stats {
constructor() { }
// T(N): N lg N + 4N
static compute(arr, pop) {
const sorted = arr.slice(); // N
sorted.sort((a, b) => a - b); // N lg N
const sum = Stats.sum(sorted); // N
const mean = Stats.mean(arr, sum);
const variance = Stats.variance(arr, pop, mean); // 2N
const stddev = Math.sqrt(variance);
const error = Stats.standardErrorOfMean(sorted, pop, stddev);
const margin = Stats.marginOfError(sorted, pop, error);
// clang-format off
return {
cnt: sorted.length,
sum,
avg: mean,
var: variance,
std: stddev,
sem: error,
moe: margin,
rme: Stats.relativeMarginOfError(sorted, pop, margin, mean),
min: sorted[0],
max: sorted[sorted.length - 1],
p50: Stats.ptile(sorted, 0.50),
p90: Stats.ptile(sorted, 0.90),
p95: Stats.ptile(sorted, 0.95),
p99: Stats.ptile(sorted, 0.99),
};
// clang-format on
}
// T(N): N
static max(arr) {
let m = -Infinity;
for (const a of arr) {
if (a > m)
m = a;
}
return m;
}
// T(N): N
static min(arr) {
let m = Infinity;
for (const a of arr) {
if (a < m)
m = a;
}
return m;
}
// T(N): N
static sum(arr) {
if (!arr.length)
return 0;
return arr.reduce((acc, v) => acc + v);
}
// T(N): N | 1
static mean(arr, sum) {
if (!arr.length)
return 0;
const s = typeof sum !== 'undefined' ? sum : Stats.sum(arr);
return s / arr.length;
}
// T(N): N lg N
static median(arr) {
return Stats.percentile(arr, 0.5);
}
// T(N): N lg N + N
static percentile(arr, p) {
const sorted = arr.slice();
sorted.sort((a, b) => a - b);
return Stats.ptile(sorted, p);
}
// PRE: arr = arr.sort((a, b) => a - b)
static ptile(arr, p) {
if (!arr.length)
return 0;
if (p <= 0)
return arr[0];
if (p >= 1)
return arr[arr.length - 1];
const index = (arr.length - 1) * p;
const lower = Math.floor(index);
const upper = lower + 1;
const weight = index % 1;
if (upper >= arr.length)
return arr[lower];
return arr[lower] * (1 - weight) + arr[upper] * weight;
}
// T(N): 3N | 2N
static variance(arr, pop, mean) {
if (!arr.length)
return 0;
const n = pop ? arr.length : arr.length - 1;
const m = typeof mean !== 'undefined' ? mean : Stats.mean(arr);
return Stats.sum(arr.map(num => Math.pow(num - m, 2))) / n;
}
// T(N): 3N | 2N
static standardDeviation(arr, pop, mean) {
return Math.sqrt(Stats.variance(arr, pop, mean));
}
// T(N): 3N | 1
static standardErrorOfMean(arr, pop, std) {
if (!arr.length)
return 0;
const dev = typeof std !== 'undefined' ? std : Stats.standardDeviation(arr, pop);
return dev / Math.sqrt(arr.length);
}
// T(N): 3N | 1
static marginOfError(arr, pop, sem) {
if (!arr.length)
return 0;
const err = typeof sem !== 'undefined' ? sem : Stats.standardErrorOfMean(arr, pop);
return err * (TABLE[(arr.length - 1) - 1] || TINF);
}
// T(N): 4N | 1
static relativeMarginOfError(arr, pop, moe, mean) {
if (!arr.length)
return 0;
const margin = typeof moe !== 'undefined' ? moe : Stats.marginOfError(arr, pop);
const avg = typeof mean !== 'undefined' ? mean : Stats.mean(arr);
return (margin / avg) * 100;
}
}
exports.Stats = Stats;
class Tracer {
constructor(traceEvents) {
this.traceEvents = traceEvents;
}
get enabled() {
var _a;
return !!((_a = this.tracing) === null || _a === void 0 ? void 0 : _a.enabled);
}
enable(categories) {
if (!this.traceEvents || this.enabled)
return;
categories = categories || ['node.perf'];
this.tracing = this.traceEvents.createTracing({ categories });
this.tracing.enable();
}
disable() {
if (!this.tracing || !this.enabled)
return;
this.tracing.disable();
this.tracing = undefined;
}
}
exports.Tracer = Tracer;
exports.TRACER = new Tracer((() => {
try {
return require('trace_events');
}
catch (_a) {
return undefined;
}
})());
class Tracker {
constructor() {
this.counters = new Map();
}
static create(options) {
const buf = options === null || options === void 0 ? void 0 : options.buf;
return buf ? new BoundedTracker(buf) : new UnboundedTracker();
}
count(name, val) {
val = val || 1;
const c = this.counters.get(name);
this.counters.set(name, typeof c !== 'undefined' ? (c + val) : val);
}
push(dists, name, val) {
const d = dists.get(name) || [];
if (!d.length)
dists.set(name, d);
d.push(val);
}
// T(M, N): M(N lg N + 3N)
compute(dists, pop) {
const stats = new Map();
for (const [name, vals] of dists.entries()) {
stats.set(name, Stats.compute(vals, pop));
}
return stats;
}
}
exports.Tracker = Tracker;
class UnboundedTracker extends Tracker {
constructor() {
super();
this.distributions = new Map();
}
add(name, val) {
return this.push(this.distributions, name, val);
}
stats(pop) {
return this.compute(this.distributions, pop);
}
}
class BoundedTracker extends Tracker {
constructor(buf) {
super();
this.buf = buf;
this.next = { tag: 0, loc: 0, dloc: 0 };
this.tags = new Map();
this.distributions = new Map();
}
add(name, val) {
let tag = this.tags.get(name);
if (typeof tag === 'undefined') {
this.tags.set(name, (tag = this.next.tag++));
}
this.buf.writeUInt8(tag, this.next.loc);
this.buf.writeDoubleBE(val, this.next.loc + 1);
this.next.loc += 9;
}
stats(pop) {
if (this.next.dloc !== this.next.loc) {
const names = [];
for (const [name, tag] of this.tags.entries()) {
names[tag] = name;
}
while (this.next.dloc < this.next.loc) {
const name = names[this.buf.readUInt8(this.next.dloc)];
const val = this.buf.readDoubleBE(this.next.dloc + 1);
this.push(this.distributions, name, val);
this.next.dloc += 9;
}
}
return this.compute(this.distributions, pop);
}
}
class Timer {
constructor(tracker, perf) {
this.tracker = tracker;
this.perf = perf;
}
static create(options) {
const tracker = Tracker.create(options);
const perf = (options === null || options === void 0 ? void 0 : options.perf) || PERF;
const trace = (options && typeof options.trace !== 'undefined')
? !!options.trace
: exports.TRACER.enabled;
return trace ? new TracingTimer(tracker, perf)
: new BasicTimer(tracker, perf);
}
count(name, val) {
this.tracker.count(name, val);
}
get counters() {
return this.tracker.counters;
}
get duration() {
if (typeof this.started === 'undefined' ||
typeof this.stopped === 'undefined') {
return undefined;
}
return this.stopped - this.started;
}
start() {
if (!this.started)
this.started = this.perf.now();
}
stop() {
if (!this.stopped)
this.stopped = this.perf.now();
}
stats(pop) {
return this.tracker.stats(pop);
}
}
exports.Timer = Timer;
class BasicTimer extends Timer {
constructor(tracker, perf) {
super(tracker, perf);
}
time(name) {
if (!this.started || this.stopped)
return IDENTITY;
const begin = this.perf.now();
return (a) => {
this.tracker.add(name, this.perf.now() - begin);
return a;
};
}
}
class TracingTimer extends Timer {
constructor(tracker, perf) {
super(tracker, perf);
}
time(name) {
if (!this.started || this.stopped)
return IDENTITY;
const b = `b|${name}`;
this.perf.mark(b);
const begin = this.perf.now();
return (a) => {
this.tracker.add(name, this.perf.now() - begin);
const e = `e|${name}`;
this.perf.mark(e);
this.perf.measure(name, b, e);
return a;
};
}
}
//# sourceMappingURL=index.js.map