UNPKG

@actyx/sdk

Version:
284 lines 8.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.runStats = exports.RunStats = exports.GaugeMap = exports.DurationMap = exports.CounterMap = void 0; /* * Actyx SDK: Functions for writing distributed apps * deployed on peer-to-peer networks, without any servers. * * Copyright (C) 2021 Actyx AG */ const Option_1 = require("fp-ts/lib/Option"); const t = require("io-ts"); const rxjs_1 = require("../../node_modules/rxjs"); // Copypasta to avoid weird circular dependency issues const lookup = (m, k) => m[k]; const Timestamp = { now: () => Date.now() * 1000, }; const CounterMapEntry = t.type({ count: t.number, last: t.number, }); const CounterMapInternal = t.record(t.string, CounterMapEntry); const CounterMapMut = t.record(t.string, t.tuple([t.number, t.number])); exports.CounterMap = t.readonly(CounterMapMut); const DurationStats = t.union([ t.readonly(t.type({ count: t.number, min: t.number, max: t.number, median: t.number, _90: t.number, _95: t.number, _99: t.number, pending: t.number, discarded: t.number, })), t.readonly(t.type({ pending: t.number, discarded: t.number, })), ]); const DurationMapMut = t.record(t.string, DurationStats); exports.DurationMap = t.readonly(DurationMapMut); const addDurationStats = (stats, duration) => { const { durations } = stats; // only sample the first 1000 in each time interval if (durations.length < 1000) { durations.push(duration); } }; const addPendingOperation = (stats, at) => { const count = stats.pending[at] || 0; stats.pending[at] = count + 1; }; const endPendingOperation = (stats, from, to) => { const count = stats.pending[from] || undefined; if (count === undefined || count <= 0) { stats.discarded += 1; return; } const newCount = count - 1; if (newCount === 0) { delete stats.pending[from]; } else { stats.pending[from] = newCount; } addDurationStats(stats, to - from); }; const createEntry = (duration) => ({ durations: [duration], pending: {}, discarded: 0, }); const createEntryAt = (at) => ({ durations: [], pending: { [at]: 1 }, discarded: 0, }); const createDiscardedEntry = () => ({ durations: [], pending: {}, discarded: 1, }); const getDurationStats = (stats) => { const count = stats.durations.length; const pending = Object.values(stats.pending).reduce((acc, v) => acc + v, 0); const discarded = stats.discarded; if (count === 0) { if (pending !== 0 || discarded !== 0) return (0, Option_1.some)({ pending, discarded }); else return Option_1.none; } const d = stats.durations.sort((a, b) => a - b); const min = d[0]; const median = d[Math.floor(count / 2)]; const _90 = d[Math.floor(count * 0.9)]; const _95 = d[Math.floor(count * 0.95)]; const _99 = d[Math.floor(count * 0.99)]; const max = d[count - 1]; // clear the stats, this is reported per interval stats.durations = []; stats.discarded = 0; return (0, Option_1.some)({ count, pending, discarded, min, median, _90, _95, _99, max, }); }; const mkCounters = () => { const counters = {}; const add = (name, count) => { const entry = lookup(counters, name); if (entry === undefined) { counters[name] = { count: count || 1, last: 0 }; } else { entry.count += count || 1; } }; const current = () => { const result = {}; Object.entries(counters).forEach(([name, entry]) => { result[name] = [entry.count, entry.count - entry.last]; entry.last = entry.count; }); return result; }; return { add, current, }; }; const createDurationStats = () => { const durations = {}; const add = (name, duration) => { const entry = lookup(durations, name); if (entry === undefined) { durations[name] = createEntry(duration); } else { addDurationStats(entry, duration); } }; const start = (name, at) => { const entry = lookup(durations, name); if (entry === undefined) { durations[name] = createEntryAt(at); } else { addPendingOperation(entry, at); } }; const end = (name, from, to) => { const entry = lookup(durations, name); if (entry === undefined) { durations[name] = createDiscardedEntry(); return; } else { endPendingOperation(entry, from, to); } }; const getAndClear = () => { const result = {}; Object.entries(durations).forEach(([key, value]) => { (0, Option_1.map)((stats) => { result[key] = stats; })(getDurationStats(value)); if (Object.keys(value.pending).length === 0) { // If nothing is pending then this object is now useless, remove it. // This metric may be repopulated in the next interval. delete durations[key]; } }); return result; }; return { add, getAndClear, start, end, }; }; const profileSync = (durations) => (name) => (block) => { const t0 = Timestamp.now(); let result; try { result = block(); durations.add(name, Timestamp.now() - t0); return result; } catch (e) { durations.add(name, Timestamp.now() - t0); throw e; } }; const profileObservable = (durations, counters) => (name, n) => (inner) => { const maxCount = n || 1; return new rxjs_1.Observable((subscriber) => { let t0 = Timestamp.now(); let count = 0; durations.start(name, t0); return inner.subscribe({ next: (value) => { count++; if (count <= maxCount) { const t1 = Timestamp.now(); durations.end(name, t0, t1); t0 = t1; } if (count < maxCount) { durations.start(name, t0); } subscriber.next(value); }, error: (reason) => { counters.add(`errprof-${name}`); count++; if (count <= maxCount) { durations.end(name, t0, Timestamp.now()); } subscriber.error(reason); }, complete: () => { count++; if (count <= maxCount) { durations.end(name, t0, Timestamp.now()); } subscriber.complete(); }, }); }); }; const GaugeMapMut = t.record(t.string, t.type({ last: t.number, max: t.number, })); exports.GaugeMap = t.readonly(GaugeMapMut); const mkGauges = () => { const gauges = {}; const set = (name, value) => { const old = gauges[name] || { last: value, max: value }; const next = { last: value, max: Math.max(old.max, value) }; gauges[name] = next; }; const current = () => ({ ...gauges }); return { set, current, }; }; const createRunStats = () => { const durations = createDurationStats(); const counters = mkCounters(); const profile = { profileSync: profileSync(durations), profileObservable: profileObservable(durations, counters), }; const gauges = mkGauges(); return { counters, durations, profile, gauges, }; }; exports.RunStats = { create: createRunStats, }; /** * Global statistics singleton. * * In the same file to avoid circular dependency issues. */ exports.runStats = exports.RunStats.create(); //# sourceMappingURL=runStats.js.map