UNPKG

@aimee-blue/ab-service-kit

Version:
245 lines (204 loc) 6.62 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createState = createState; exports.attach = attach; exports.registerStarts = registerStarts; exports.start = start; exports.registerStops = registerStops; exports.stop = stop; exports.logSummaries = logSummaries; exports.timesRegistered = timesRegistered; exports.summariesLogged = summariesLogged; var _rxjs = require("rxjs"); var _operators = require("rxjs/operators"); var _os = require("os"); var _notifications = require("../notifications"); var _logging = require("../logging"); function timestampToTime(timestamp, since) { if (since) { const time = process.hrtime(since); return time[0] * 1e3 + time[1] / 1e6; } else { return timestamp[0] * 1e3 + timestamp[1] / 1e6; } } const allHits = new _rxjs.Subject(); const allMemos = new _rxjs.Subject(); const allSummaries = new _rxjs.Subject(); const defaultDeps = { timestamp: () => process.hrtime(), timestampDiff: timestamp => process.hrtime(timestamp) }; function createInitialState(deps = defaultDeps) { const stateByTag = new Map(); const createStateForTag = () => { return {}; }; const self = Object.freeze({ hit: name => { const state = stateByTag.get(name) || createStateForTag(); state.hitTime = deps.timestamp(); allHits.next({ name, timestamp: state.hitTime }); stateByTag.set(name, state); }, memo: (name, details, determine = value => value) => { const state = stateByTag.get(name); if (!state || !state.hitTime) { return null; } const timeSinceHit = determine(timestampToTime(deps.timestampDiff(state.hitTime))); allMemos.next({ name, time: timeSinceHit, ...(details && { details }) }); stateByTag.set(name, state); return timeSinceHit; } }); return self; } function isMatch(name, to) { return typeof to === 'string' && to === name || typeof to === 'object' && to.test(name); } function createSummary(name) { const MAX_MEMOS = 100; return allMemos.asObservable().pipe( // (0, _operators.filter)(item => isMatch(item.name, name)), (0, _operators.scan)((acc, item) => { const memos = [...acc.memos, item.time].slice(-MAX_MEMOS); const sortedMemos = [...memos]; sortedMemos.sort(); return { ...acc, memos, sortedMemos, max: Math.max(acc.max, item.time), min: Math.min(acc.min, item.time), average: Number.isNaN(acc.average) ? item.time : (acc.average + item.time) / 2, mostOfTheTimesLessThan: quantile(sortedMemos, 90) }; }, { sortedMemos: [], memos: [], max: 0, min: Number.MAX_SAFE_INTEGER, average: NaN, mostOfTheTimesLessThan: NaN }), (0, _operators.map)(item => ({ numberOfSamples: item.memos.length, max: item.max, min: item.min, average: item.average, mostOfTheTimesLessThan: item.mostOfTheTimesLessThan }))); } let cachedState = null; function globalState() { if (cachedState) { return cachedState; } return cachedState = createInitialState(); } function createState(deps = defaultDeps) { return createInitialState(deps); } function attach(params) { return source => { const effectiveState = params.state || createInitialState(); const startOp = () => start({ name: params.name, on: params.from }, effectiveState); const stopOp = () => stop({ name: params.name, till: params.till, cb: params.cb, transformTookTime: params.transformTimeTook, details: params.details }, effectiveState); return source.pipe(stream => { if (params.till === 'unsubscribe' && params.from === 'complete') { return stream.pipe(startOp(), stopOp()); } else { return stream.pipe(stopOp(), startOp()); } }); }; } function registerStarts(params, state = globalState()) { const hit = () => { state.hit(params.name); }; return params.on.pipe((0, _operators.tap)(hit), (0, _operators.ignoreElements)()); } function start(paramsRaw, state = globalState()) { const params = { on: 'next', ...paramsRaw }; return stream => { var _params$logger; const hit = () => { state.hit(params.name); }; return stream.pipe((0, _notifications.executeOnNotifications)([params.on], hit, (_params$logger = params.logger) !== null && _params$logger !== void 0 ? _params$logger : (0, _logging.defaultBasicLogger)())); }; } function registerStops(params, state = globalState()) { const setMemo = () => { const timeTook = state.memo(params.name, params.details, params.transformTookTime); if (typeof timeTook === 'number' && params.cb) { params.cb(timeTook); } }; return params.on.pipe((0, _operators.tap)(setMemo), (0, _operators.ignoreElements)()); } function stop(paramsRaw, state = globalState()) { const params = paramsRaw; return stream => { var _params$logger2; const setMemo = () => { const timeTook = state.memo(params.name, params.details, params.transformTookTime); if (typeof timeTook === 'number' && params.cb) { params.cb(timeTook); } }; return stream.pipe((0, _notifications.executeOnNotifications)([params.till], setMemo, (_params$logger2 = params.logger) !== null && _params$logger2 !== void 0 ? _params$logger2 : (0, _logging.defaultBasicLogger)())); }; } function quantile(input, percentile, params) { const array = params && params.sorted ? input : input.slice().sort(); const index = percentile / 100 * (array.length - 1); const i = Math.floor(index); if (i === index) { return array[index]; } else { return array[i] + (array[i + 1] - array[i]) / 2; } } function logSummaries(params) { var _params$logger3; const summaries = createSummary(params.name); const logger = (_params$logger3 = params.logger) !== null && _params$logger3 !== void 0 ? _params$logger3 : _logging.defaultLogger; return summaries.pipe((0, _logging.logEvents)({ prefix: `${_os.EOL}🔃 Profiler results for [${params.name}]`, suffix: [_os.EOL], logger, ...(params.on && { on: params.on }) }), (0, _operators.ignoreElements)()); } function timesRegistered(name) { return allMemos.pipe(stream => name ? stream.pipe((0, _operators.filter)(item => isMatch(item.name, name))) : stream); } function summariesLogged(name) { return allSummaries.pipe(stream => name ? stream.pipe((0, _operators.filter)(item => isMatch(item.name, name))) : stream); } //# sourceMappingURL=streams.js.map