import * as Arr from "../Array.js" import * as Clock from "../Clock.js" import * as Duration from "../Duration.js" import type * as Effect from "../Effect.js" import type { LazyArg } from "../Function.js" import { constVoid, dual, identity, pipe } from "../Function.js" import { globalValue } from "../GlobalValue.js" import type * as Metric from "../Metric.js" import type * as MetricBoundaries from "../MetricBoundaries.js" import type * as MetricHook from "../MetricHook.js" import type * as MetricKey from "../MetricKey.js" import type * as MetricKeyType from "../MetricKeyType.js" import type * as MetricLabel from "../MetricLabel.js" import type * as MetricPair from "../MetricPair.js" import type * as MetricRegistry from "../MetricRegistry.js" import type * as MetricState from "../MetricState.js" import { pipeArguments } from "../Pipeable.js" import * as Cause from "./cause.js" import * as effect_ from "./core-effect.js" import * as core from "./core.js" import * as metricBoundaries from "./metric/boundaries.js" import * as metricKey from "./metric/key.js" import * as metricKeyType from "./metric/keyType.js" import * as metricLabel from "./metric/label.js" import * as metricRegistry from "./metric/registry.js" /** @internal */ const MetricSymbolKey = "effect/Metric" /** @internal */ export const MetricTypeId: Metric.MetricTypeId = Symbol.for( MetricSymbolKey ) as Metric.MetricTypeId const metricVariance = { /* c8 ignore next */ _Type: (_: any) => _, /* c8 ignore next */ _In: (_: unknown) => _, /* c8 ignore next */ _Out: (_: never) => _ } /** @internal */ export const globalMetricRegistry: MetricRegistry.MetricRegistry = globalValue( Symbol.for("effect/Metric/globalMetricRegistry"), () => metricRegistry.make() ) /** @internal */ export const make: Metric.MetricApply = function( keyType: Type, unsafeUpdate: (input: In, extraTags: ReadonlyArray) => void, unsafeValue: (extraTags: ReadonlyArray) => Out, unsafeModify: (input: In, extraTags: ReadonlyArray) => void ): Metric.Metric { const metric: Metric.Metric = Object.assign( (effect: Effect.Effect): Effect.Effect => core.tap(effect, (a) => update(metric, a)), { [MetricTypeId]: metricVariance, keyType, unsafeUpdate, unsafeValue, unsafeModify, register() { this.unsafeValue([]) return this as any }, pipe() { return pipeArguments(this, arguments) } } as const ) return metric } /** @internal */ export const mapInput = dual< (f: (input: In2) => In) => (self: Metric.Metric) => Metric.Metric, (self: Metric.Metric, f: (input: In2) => In) => Metric.Metric >(2, (self, f) => make( self.keyType, (input, extraTags) => self.unsafeUpdate(f(input), extraTags), self.unsafeValue, (input, extraTags) => self.unsafeModify(f(input), extraTags) )) /** @internal */ export const counter: { (name: string, options?: { readonly description?: string | undefined readonly bigint?: false | undefined readonly incremental?: boolean | undefined }): Metric.Metric.Counter (name: string, options: { readonly description?: string | undefined readonly bigint: true readonly incremental?: boolean | undefined }): Metric.Metric.Counter } = (name, options) => fromMetricKey(metricKey.counter(name, options as any)) as any /** @internal */ export const frequency = (name: string, options?: { readonly description?: string | undefined readonly preregisteredWords?: ReadonlyArray | undefined }): Metric.Metric.Frequency => fromMetricKey(metricKey.frequency(name, options)) /** @internal */ export const withConstantInput = dual< (input: In) => (self: Metric.Metric) => Metric.Metric, (self: Metric.Metric, input: In) => Metric.Metric >(2, (self, input) => mapInput(self, () => input)) /** @internal */ export const fromMetricKey = >( key: MetricKey.MetricKey ): Metric.Metric< Type, MetricKeyType.MetricKeyType.InType, MetricKeyType.MetricKeyType.OutType > => { let untaggedHook: | MetricHook.MetricHook< MetricKeyType.MetricKeyType.InType, MetricKeyType.MetricKeyType.OutType > | undefined const hookCache = new WeakMap, MetricHook.MetricHook>() const hook = (extraTags: ReadonlyArray): MetricHook.MetricHook< MetricKeyType.MetricKeyType.InType, MetricKeyType.MetricKeyType.OutType > => { if (extraTags.length === 0) { if (untaggedHook !== undefined) { return untaggedHook } untaggedHook = globalMetricRegistry.get(key) return untaggedHook } let hook = hookCache.get(extraTags) if (hook !== undefined) { return hook } hook = globalMetricRegistry.get(metricKey.taggedWithLabels(key, extraTags)) hookCache.set(extraTags, hook) return hook } return make( key.keyType, (input, extraTags) => hook(extraTags).update(input), (extraTags) => hook(extraTags).get(), (input, extraTags) => hook(extraTags).modify(input) ) } /** @internal */ export const gauge: { (name: string, options?: { readonly description?: string | undefined readonly bigint?: false | undefined }): Metric.Metric.Gauge (name: string, options: { readonly description?: string | undefined readonly bigint: true }): Metric.Metric.Gauge } = (name, options) => fromMetricKey(metricKey.gauge(name, options as any)) as any /** @internal */ export const histogram = (name: string, boundaries: MetricBoundaries.MetricBoundaries, description?: string) => fromMetricKey(metricKey.histogram(name, boundaries, description)) /* @internal */ export const increment = ( self: | Metric.Metric.Counter | Metric.Metric.Counter | Metric.Metric.Gauge | Metric.Metric.Gauge ): Effect.Effect => metricKeyType.isCounterKey(self.keyType) ? update(self as Metric.Metric.Counter, self.keyType.bigint ? BigInt(1) as any : 1) : modify(self as Metric.Metric.Gauge, self.keyType.bigint ? BigInt(1) as any : 1) /* @internal */ export const incrementBy = dual< { (amount: number): (self: Metric.Metric.Counter | Metric.Metric.Counter) => Effect.Effect (amount: bigint): (self: Metric.Metric.Counter | Metric.Metric.Gauge) => Effect.Effect }, { (self: Metric.Metric.Counter | Metric.Metric.Gauge, amount: number): Effect.Effect (self: Metric.Metric.Counter | Metric.Metric.Gauge, amount: bigint): Effect.Effect } >(2, (self, amount) => metricKeyType.isCounterKey(self.keyType) ? update(self as any, amount) : modify(self as any, amount)) /** @internal */ export const map = dual< (f: (out: Out) => Out2) => (self: Metric.Metric) => Metric.Metric, (self: Metric.Metric, f: (out: Out) => Out2) => Metric.Metric >(2, (self, f) => make( self.keyType, self.unsafeUpdate, (extraTags) => f(self.unsafeValue(extraTags)), self.unsafeModify )) /** @internal */ export const mapType = dual< ( f: (type: Type) => Type2 ) => ( self: Metric.Metric ) => Metric.Metric, ( self: Metric.Metric, f: (type: Type) => Type2 ) => Metric.Metric >(2, (self, f) => make( f(self.keyType), self.unsafeUpdate, self.unsafeValue, self.unsafeModify )) /** @internal */ export const modify = dual< (input: In) => (self: Metric.Metric) => Effect.Effect, (self: Metric.Metric, input: In) => Effect.Effect >(2, (self, input) => core.fiberRefGetWith( core.currentMetricLabels, (tags) => core.sync(() => self.unsafeModify(input, tags)) )) /* @internal */ export const set = dual< { (value: number): (self: Metric.Metric.Gauge) => Effect.Effect (value: bigint): (self: Metric.Metric.Gauge) => Effect.Effect }, { (self: Metric.Metric.Gauge, value: number): Effect.Effect (self: Metric.Metric.Gauge, value: bigint): Effect.Effect } >(2, (self, value) => update(self as any, value)) /** @internal */ export const succeed = (out: Out): Metric.Metric => make(void 0 as void, constVoid, () => out, constVoid) /** @internal */ export const sync = (evaluate: LazyArg): Metric.Metric => make(void 0 as void, constVoid, evaluate, constVoid) /** @internal */ export const summary = ( options: { readonly name: string readonly maxAge: Duration.DurationInput readonly maxSize: number readonly error: number readonly quantiles: ReadonlyArray readonly description?: string | undefined } ): Metric.Metric.Summary => withNow(summaryTimestamp(options)) /** @internal */ export const summaryTimestamp = ( options: { readonly name: string readonly maxAge: Duration.DurationInput readonly maxSize: number readonly error: number readonly quantiles: ReadonlyArray readonly description?: string | undefined } ): Metric.Metric.Summary => fromMetricKey(metricKey.summary(options)) /** @internal */ export const tagged = dual< (key: string, value: string) => (self: Metric.Metric) => Metric.Metric, (self: Metric.Metric, key: string, value: string) => Metric.Metric >(3, (self, key, value) => taggedWithLabels(self, [metricLabel.make(key, value)])) /** @internal */ export const taggedWithLabelsInput = dual< ( f: (input: In) => Iterable ) => (self: Metric.Metric) => Metric.Metric, ( self: Metric.Metric, f: (input: In) => Iterable ) => Metric.Metric >(2, (self, f) => map( make( self.keyType, (input, extraTags) => self.unsafeUpdate( input, Arr.union(f(input), extraTags) ), self.unsafeValue, (input, extraTags) => self.unsafeModify( input, Arr.union(f(input), extraTags) ) ), constVoid )) /** @internal */ export const taggedWithLabels = dual< ( extraTags: Iterable ) => (self: Metric.Metric) => Metric.Metric, ( self: Metric.Metric, extraTags: Iterable ) => Metric.Metric >(2, (self, extraTags) => { return make( self.keyType, (input, extraTags1) => self.unsafeUpdate(input, Arr.union(extraTags, extraTags1)), (extraTags1) => self.unsafeValue(Arr.union(extraTags, extraTags1)), (input, extraTags1) => self.unsafeModify(input, Arr.union(extraTags, extraTags1)) ) }) /** @internal */ export const timer = (name: string, description?: string): Metric.Metric< MetricKeyType.MetricKeyType.Histogram, Duration.Duration, MetricState.MetricState.Histogram > => { const boundaries = metricBoundaries.exponential({ start: 0.5, factor: 2, count: 35 }) const base = pipe(histogram(name, boundaries, description), tagged("time_unit", "milliseconds")) return mapInput(base, Duration.toMillis) } /** @internal */ export const timerWithBoundaries = ( name: string, boundaries: ReadonlyArray, description?: string ): Metric.Metric< MetricKeyType.MetricKeyType.Histogram, Duration.Duration, MetricState.MetricState.Histogram > => { const base = pipe( histogram(name, metricBoundaries.fromIterable(boundaries), description), tagged("time_unit", "milliseconds") ) return mapInput(base, Duration.toMillis) } /* @internal */ export const trackAll = dual< ( input: In ) => ( self: Metric.Metric ) => (effect: Effect.Effect) => Effect.Effect, ( self: Metric.Metric, input: In ) => (effect: Effect.Effect) => Effect.Effect >(2, (self, input) => (effect) => core.matchCauseEffect(effect, { onFailure: (cause) => core.zipRight(update(self, input), core.failCause(cause)), onSuccess: (value) => core.zipRight(update(self, input), core.succeed(value)) })) /* @internal */ export const trackDefect = dual< ( metric: Metric.Metric ) => (self: Effect.Effect) => Effect.Effect, ( self: Effect.Effect, metric: Metric.Metric ) => Effect.Effect >(2, (self, metric) => trackDefectWith(self, metric, identity)) /* @internal */ export const trackDefectWith = dual< ( metric: Metric.Metric, f: (defect: unknown) => In ) => (self: Effect.Effect) => Effect.Effect, ( self: Effect.Effect, metric: Metric.Metric, f: (defect: unknown) => In ) => Effect.Effect >(3, (self, metric, f) => { const updater = (defect: unknown) => update(metric, f(defect)) return effect_.tapDefect(self, (cause) => core.forEachSequentialDiscard(Cause.defects(cause), updater)) }) /* @internal */ export const trackDuration = dual< ( metric: Metric.Metric ) => (self: Effect.Effect) => Effect.Effect, ( self: Effect.Effect, metric: Metric.Metric ) => Effect.Effect >(2, (self, metric) => trackDurationWith(self, metric, identity)) /* @internal */ export const trackDurationWith = dual< ( metric: Metric.Metric, f: (duration: Duration.Duration) => In ) => (effect: Effect.Effect) => Effect.Effect, ( self: Effect.Effect, metric: Metric.Metric, f: (duration: Duration.Duration) => In ) => Effect.Effect >(3, (self, metric, f) => Clock.clockWith((clock) => { const startTime = clock.unsafeCurrentTimeNanos() return core.tap(self, (_) => { const endTime = clock.unsafeCurrentTimeNanos() const duration = Duration.nanos(endTime - startTime) return update(metric, f(duration)) }) })) /* @internal */ export const trackError = dual< ( metric: Metric.Metric ) => (self: Effect.Effect) => Effect.Effect, ( self: Effect.Effect, metric: Metric.Metric ) => Effect.Effect >(2, ( self: Effect.Effect, metric: Metric.Metric ) => trackErrorWith(self, metric, (a: In) => a)) /* @internal */ export const trackErrorWith = dual< ( metric: Metric.Metric, f: (error: In2) => In ) => (effect: Effect.Effect) => Effect.Effect, ( self: Effect.Effect, metric: Metric.Metric, f: (error: In2) => In ) => Effect.Effect >(3, ( self: Effect.Effect, metric: Metric.Metric, f: (error: In2) => In ) => { const updater = (error: E): Effect.Effect => update(metric, f(error)) return effect_.tapError(self, updater) }) /* @internal */ export const trackSuccess = dual< ( metric: Metric.Metric ) => (self: Effect.Effect) => Effect.Effect, ( self: Effect.Effect, metric: Metric.Metric ) => Effect.Effect >(2, ( self: Effect.Effect, metric: Metric.Metric ) => trackSuccessWith(self, metric, (a: In) => a)) /* @internal */ export const trackSuccessWith = dual< ( metric: Metric.Metric, f: (value: In2) => In ) => (self: Effect.Effect) => Effect.Effect, ( self: Effect.Effect, metric: Metric.Metric, f: (value: In2) => In ) => Effect.Effect >(3, ( self: Effect.Effect, metric: Metric.Metric, f: (value: In2) => In ) => { const updater = (value: A): Effect.Effect => update(metric, f(value)) return core.tap(self, updater) }) /* @internal */ export const update = dual< (input: In) => (self: Metric.Metric) => Effect.Effect, (self: Metric.Metric, input: In) => Effect.Effect >(2, (self, input) => core.fiberRefGetWith( core.currentMetricLabels, (tags) => core.sync(() => self.unsafeUpdate(input, tags)) )) /* @internal */ export const value = ( self: Metric.Metric ): Effect.Effect => core.fiberRefGetWith( core.currentMetricLabels, (tags) => core.sync(() => self.unsafeValue(tags)) ) /** @internal */ export const withNow = ( self: Metric.Metric ): Metric.Metric => mapInput(self, (input: In) => [input, Date.now()] as const) /** @internal */ export const zip = dual< ( that: Metric.Metric ) => ( self: Metric.Metric ) => Metric.Metric, ( self: Metric.Metric, that: Metric.Metric ) => Metric.Metric >( 2, (self: Metric.Metric, that: Metric.Metric) => make( [self.keyType, that.keyType] as const, (input: readonly [In, In2], extraTags) => { const [l, r] = input self.unsafeUpdate(l, extraTags) that.unsafeUpdate(r, extraTags) }, (extraTags) => [self.unsafeValue(extraTags), that.unsafeValue(extraTags)], (input: readonly [In, In2], extraTags) => { const [l, r] = input self.unsafeModify(l, extraTags) that.unsafeModify(r, extraTags) } ) ) /** @internal */ export const unsafeSnapshot = (): Array => globalMetricRegistry.snapshot() /** @internal */ export const snapshot: Effect.Effect> = core.sync( unsafeSnapshot )