@aimee-blue/ab-service-kit
Version:
Aimee Blue Service Template
245 lines (204 loc) • 6.62 kB
JavaScript
;
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