From 9970036d203ba2d0a46b35ba6fad21d49441cdd4 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sun, 27 Jul 2025 17:03:10 -0700 Subject: hai --- lib/trace/metric/trace.ts | 59 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 lib/trace/metric/trace.ts (limited to 'lib/trace/metric/trace.ts') diff --git a/lib/trace/metric/trace.ts b/lib/trace/metric/trace.ts new file mode 100644 index 0000000..0c5fe37 --- /dev/null +++ b/lib/trace/metric/trace.ts @@ -0,0 +1,59 @@ +import { IMetric, isIMetric, isMetricValue, ITrace, ITraceWith, MetricValue, SideEffect } from '@emprespresso/pengueno'; + +export type MetricsTraceSupplier = + | ITraceWith + | Array>; +export const isMetricsTraceSupplier = (t: unknown): t is MetricsTraceSupplier => + isMetricValue(t) || isIMetric(t) || (Array.isArray(t) && t.every((_m) => isMetricValue(_m) || isIMetric(_m))); + +export class MetricsTrace implements ITrace { + constructor( + private readonly metricConsumer: SideEffect>, + private readonly activeTraces: ReadonlyMap = new Map(), + private readonly completedTraces: ReadonlySet = new Set(), + ) {} + + public traceScope(trace: MetricsTraceSupplier): MetricsTrace { + const now = Date.now(); + const metricsToTrace = (Array.isArray(trace) ? trace : [trace]).filter(isIMetric); + + const initialTraces = new Map(metricsToTrace.map((metric) => [metric, now])); + + return new MetricsTrace(this.metricConsumer, initialTraces); + } + + public trace(metrics: MetricsTraceSupplier): MetricsTrace { + if (!metrics || typeof metrics === 'string') { + return this; + } + + const now = Date.now(); + const allMetrics = Array.isArray(metrics) ? metrics : [metrics]; + + // partition the incoming metrics + const valuesToEmit = allMetrics.filter(isMetricValue); + const traceableMetrics = allMetrics.filter(isIMetric); + + const metricsToStart = traceableMetrics.filter((m) => !this.activeTraces.has(m)); + const metricsToEnd = traceableMetrics.filter((m) => this.activeTraces.has(m) && !this.completedTraces.has(m)); + + // the new metrics to emit based on traces ending *now* + const endedMetricValues = metricsToEnd.flatMap((metric) => [ + metric.count.withValue(1.0), + metric.time.withValue(now - this.activeTraces.get(metric)!), + ]); + + const allMetricsToEmit = [...valuesToEmit, ...endedMetricValues]; + if (allMetricsToEmit.length > 0) { + this.metricConsumer(allMetricsToEmit); + } + + // the next immutable state + const nextActiveTraces = new Map([ + ...this.activeTraces, + ...metricsToStart.map((m): [IMetric, number] => [m, now]), + ]); + const nextCompletedTraces = new Set([...this.completedTraces, ...metricsToEnd]); + return new MetricsTrace(this.metricConsumer, nextActiveTraces, nextCompletedTraces); + } +} -- cgit v1.2.3-70-g09d2