"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.summary = exports.onUpdate = exports.onModify = exports.make = exports.histogram = exports.gauge = exports.frequency = exports.counter = exports.MetricHookTypeId = void 0; var Arr = _interopRequireWildcard(require("../../Array.js")); var Duration = _interopRequireWildcard(require("../../Duration.js")); var _Function = require("../../Function.js"); var number = _interopRequireWildcard(require("../../Number.js")); var Option = _interopRequireWildcard(require("../../Option.js")); var _Pipeable = require("../../Pipeable.js"); var metricState = _interopRequireWildcard(require("./state.js")); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /** @internal */ const MetricHookSymbolKey = "effect/MetricHook"; /** @internal */ const MetricHookTypeId = exports.MetricHookTypeId = /*#__PURE__*/Symbol.for(MetricHookSymbolKey); const metricHookVariance = { /* c8 ignore next */ _In: _ => _, /* c8 ignore next */ _Out: _ => _ }; /** @internal */ const make = options => ({ [MetricHookTypeId]: metricHookVariance, pipe() { return (0, _Pipeable.pipeArguments)(this, arguments); }, ...options }); /** @internal */ exports.make = make; const onModify = exports.onModify = /*#__PURE__*/(0, _Function.dual)(2, (self, f) => ({ [MetricHookTypeId]: metricHookVariance, pipe() { return (0, _Pipeable.pipeArguments)(this, arguments); }, get: self.get, update: self.update, modify: input => { self.modify(input); return f(input); } })); /** @internal */ const onUpdate = exports.onUpdate = /*#__PURE__*/(0, _Function.dual)(2, (self, f) => ({ [MetricHookTypeId]: metricHookVariance, pipe() { return (0, _Pipeable.pipeArguments)(this, arguments); }, get: self.get, update: input => { self.update(input); return f(input); }, modify: self.modify })); const bigint0 = /*#__PURE__*/BigInt(0); /** @internal */ const counter = key => { let sum = key.keyType.bigint ? bigint0 : 0; const canUpdate = key.keyType.incremental ? key.keyType.bigint ? value => value >= bigint0 : value => value >= 0 : _value => true; const update = value => { if (canUpdate(value)) { sum = sum + value; } }; return make({ get: () => metricState.counter(sum), update, modify: update }); }; /** @internal */ exports.counter = counter; const frequency = key => { const values = new Map(); for (const word of key.keyType.preregisteredWords) { values.set(word, 0); } const update = word => { const slotCount = values.get(word) ?? 0; values.set(word, slotCount + 1); }; return make({ get: () => metricState.frequency(values), update, modify: update }); }; /** @internal */ exports.frequency = frequency; const gauge = (_key, startAt) => { let value = startAt; return make({ get: () => metricState.gauge(value), update: v => { value = v; }, modify: v => { value = value + v; } }); }; /** @internal */ exports.gauge = gauge; const histogram = key => { const bounds = key.keyType.boundaries.values; const size = bounds.length; const values = new Uint32Array(size + 1); const boundaries = new Float32Array(size); let count = 0; let sum = 0; let min = Number.MAX_VALUE; let max = Number.MIN_VALUE; (0, _Function.pipe)(bounds, Arr.sort(number.Order), Arr.map((n, i) => { boundaries[i] = n; })); // Insert the value into the right bucket with a binary search const update = value => { let from = 0; let to = size; while (from !== to) { const mid = Math.floor(from + (to - from) / 2); const boundary = boundaries[mid]; if (value <= boundary) { to = mid; } else { from = mid; } // The special case when to / from have a distance of one if (to === from + 1) { if (value <= boundaries[from]) { to = from; } else { from = to; } } } values[from] = values[from] + 1; count = count + 1; sum = sum + value; if (value < min) { min = value; } if (value > max) { max = value; } }; const getBuckets = () => { const builder = Arr.allocate(size); let cumulated = 0; for (let i = 0; i < size; i++) { const boundary = boundaries[i]; const value = values[i]; cumulated = cumulated + value; builder[i] = [boundary, cumulated]; } return builder; }; return make({ get: () => metricState.histogram({ buckets: getBuckets(), count, min, max, sum }), update, modify: update }); }; /** @internal */ exports.histogram = histogram; const summary = key => { const { error, maxAge, maxSize, quantiles } = key.keyType; const sortedQuantiles = (0, _Function.pipe)(quantiles, Arr.sort(number.Order)); const values = Arr.allocate(maxSize); let head = 0; let count = 0; let sum = 0; let min = 0; let max = 0; // Just before the snapshot we filter out all values older than maxAge const snapshot = now => { const builder = []; // If the buffer is not full yet it contains valid items at the 0..last // indices and null values at the rest of the positions. // // If the buffer is already full then all elements contains a valid // measurement with timestamp. // // At any given point in time we can enumerate all the non-null elements in // the buffer and filter them by timestamp to get a valid view of a time // window. // // The order does not matter because it gets sorted before passing to // `calculateQuantiles`. let i = 0; while (i !== maxSize - 1) { const item = values[i]; if (item != null) { const [t, v] = item; const age = Duration.millis(now - t); if (Duration.greaterThanOrEqualTo(age, Duration.zero) && Duration.lessThanOrEqualTo(age, maxAge)) { builder.push(v); } } i = i + 1; } return calculateQuantiles(error, sortedQuantiles, Arr.sort(builder, number.Order)); }; const observe = (value, timestamp) => { if (maxSize > 0) { head = head + 1; const target = head % maxSize; values[target] = [timestamp, value]; } min = count === 0 ? value : Math.min(min, value); max = count === 0 ? value : Math.max(max, value); count = count + 1; sum = sum + value; }; return make({ get: () => metricState.summary({ error, quantiles: snapshot(Date.now()), count, min, max, sum }), update: ([value, timestamp]) => observe(value, timestamp), modify: ([value, timestamp]) => observe(value, timestamp) }); }; /** @internal */ exports.summary = summary; const calculateQuantiles = (error, sortedQuantiles, sortedSamples) => { // The number of samples examined const sampleCount = sortedSamples.length; if (!Arr.isNonEmptyReadonlyArray(sortedQuantiles)) { return Arr.empty(); } const head = sortedQuantiles[0]; const tail = sortedQuantiles.slice(1); const resolvedHead = resolveQuantile(error, sampleCount, Option.none(), 0, head, sortedSamples); const resolved = Arr.of(resolvedHead); tail.forEach(quantile => { resolved.push(resolveQuantile(error, sampleCount, resolvedHead.value, resolvedHead.consumed, quantile, resolvedHead.rest)); }); return Arr.map(resolved, rq => [rq.quantile, rq.value]); }; /** @internal */ const resolveQuantile = (error, sampleCount, current, consumed, quantile, rest) => { let error_1 = error; let sampleCount_1 = sampleCount; let current_1 = current; let consumed_1 = consumed; let quantile_1 = quantile; let rest_1 = rest; let error_2 = error; let sampleCount_2 = sampleCount; let current_2 = current; let consumed_2 = consumed; let quantile_2 = quantile; let rest_2 = rest; // eslint-disable-next-line no-constant-condition while (1) { // If the remaining list of samples is empty, there is nothing more to resolve if (!Arr.isNonEmptyReadonlyArray(rest_1)) { return { quantile: quantile_1, value: Option.none(), consumed: consumed_1, rest: [] }; } // If the quantile is the 100% quantile, we can take the maximum of all the // remaining values as the result if (quantile_1 === 1) { return { quantile: quantile_1, value: Option.some(Arr.lastNonEmpty(rest_1)), consumed: consumed_1 + rest_1.length, rest: [] }; } // Split into two chunks - the first chunk contains all elements of the same // value as the chunk head const headValue = Arr.headNonEmpty(rest_1); // Get head value since rest_1 is non-empty const sameHead = Arr.span(rest_1, n => n === headValue); // How many elements do we want to accept for this quantile const desired = quantile_1 * sampleCount_1; // The error margin const allowedError = error_1 / 2 * desired; // Taking into account the elements consumed from the samples so far and the // number of same elements at the beginning of the chunk, calculate the number // of elements we would have if we selected the current head as result const candConsumed = consumed_1 + sameHead[0].length; const candError = Math.abs(candConsumed - desired); // If we haven't got enough elements yet, recurse if (candConsumed < desired - allowedError) { error_2 = error_1; sampleCount_2 = sampleCount_1; current_2 = Arr.head(rest_1); consumed_2 = candConsumed; quantile_2 = quantile_1; rest_2 = sameHead[1]; error_1 = error_2; sampleCount_1 = sampleCount_2; current_1 = current_2; consumed_1 = consumed_2; quantile_1 = quantile_2; rest_1 = rest_2; continue; } // If consuming this chunk leads to too many elements (rank is too high) if (candConsumed > desired + allowedError) { const valueToReturn = Option.isNone(current_1) ? Option.some(headValue) : current_1; return { quantile: quantile_1, value: valueToReturn, consumed: consumed_1, rest: rest_1 }; } // If we are in the target interval, select the current head and hand back the leftover after dropping all elements // from the sample chunk that are equal to the current head switch (current_1._tag) { case "None": { error_2 = error_1; sampleCount_2 = sampleCount_1; current_2 = Arr.head(rest_1); consumed_2 = candConsumed; quantile_2 = quantile_1; rest_2 = sameHead[1]; error_1 = error_2; sampleCount_1 = sampleCount_2; current_1 = current_2; consumed_1 = consumed_2; quantile_1 = quantile_2; rest_1 = rest_2; continue; } case "Some": { const prevError = Math.abs(desired - current_1.value); if (candError < prevError) { error_2 = error_1; sampleCount_2 = sampleCount_1; current_2 = Arr.head(rest_1); consumed_2 = candConsumed; quantile_2 = quantile_1; rest_2 = sameHead[1]; error_1 = error_2; sampleCount_1 = sampleCount_2; current_1 = current_2; consumed_1 = consumed_2; quantile_1 = quantile_2; rest_1 = rest_2; continue; } return { quantile: quantile_1, value: Option.some(current_1.value), consumed: consumed_1, rest: rest_1 }; } } } throw new Error("BUG: MetricHook.resolveQuantiles - please report an issue at https://github.com/Effect-TS/effect/issues"); }; //# sourceMappingURL=hook.js.map