diff options
author | Elizabeth Alexander Hunt <me@liz.coffee> | 2025-05-18 12:24:09 -0700 |
---|---|---|
committer | Elizabeth Alexander Hunt <me@liz.coffee> | 2025-05-18 12:24:09 -0700 |
commit | 9cf3fc0259730b7dcf47b3ab4a04369e39fb4614 (patch) | |
tree | a96d39b4f28d38e327376cbef7ba60dbaa95e111 /u/trace/metrics.ts | |
parent | ef51b25e4388cbdf3a27e23d9f1fa381ae20a5ad (diff) | |
download | ci-9cf3fc0259730b7dcf47b3ab4a04369e39fb4614.tar.gz ci-9cf3fc0259730b7dcf47b3ab4a04369e39fb4614.zip |
finish up pengueno
Diffstat (limited to 'u/trace/metrics.ts')
-rw-r--r-- | u/trace/metrics.ts | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/u/trace/metrics.ts b/u/trace/metrics.ts new file mode 100644 index 0000000..a26ee5d --- /dev/null +++ b/u/trace/metrics.ts @@ -0,0 +1,133 @@ +import { + isObject, + type ITrace, + type ITraceWith, + type Mapper, + type SideEffect, +} from "@emprespresso/pengueno"; + +export enum Unit { + COUNT, + MILLISECONDS, +} + +export interface IMetric { + readonly count: IEmittableMetric; + readonly time: IEmittableMetric; + readonly failure: IMetric; + readonly success: IMetric; + readonly _isIMetric: true; +} +export const isIMetric = (t: unknown): t is IMetric => + isObject(t) && "_isIMetric" in t; + +export interface IEmittableMetric { + readonly name: string; + readonly unit: Unit; + withValue: Mapper<number, MetricValue>; +} + +export class EmittableMetric implements IEmittableMetric { + constructor(public readonly name: string, public readonly unit: Unit) { + } + + public withValue(value: number): MetricValue { + return { + name: this.name, + unit: this.unit, + _isMetricValue: true as true, + emissionTimestamp: Date.now(), + value, + }; + } +} + +export class Metric implements IMetric { + constructor( + public readonly count: IEmittableMetric, + public readonly time: IEmittableMetric, + public readonly failure: Metric, + public readonly success: Metric, + public readonly _isIMetric: true = true, + ) {} + + static fromName(name: string): Metric { + return new Metric( + new EmittableMetric(`${name}.count`, Unit.COUNT), + new EmittableMetric(`${name}.elapsed`, Unit.MILLISECONDS), + Metric.fromName(`${name}.failure`), + Metric.fromName(`${name}.success`), + ); + } +} + +export interface MetricValue { + readonly name: string; + readonly unit: Unit; + readonly value: number; + readonly emissionTimestamp: number; + readonly _isMetricValue: true; +} +export const isMetricValue = (t: unknown): t is MetricValue => + isObject(t) && "_isMetricValue" in t; + +export const isMetricsTraceSupplier = (t: unknown): t is MetricsTraceSupplier => + isMetricValue(t) || isIMetric(t); + +export type MetricsTraceSupplier = ITraceWith<IMetric | MetricValue>; +type MetricTracingTuple = [IMetric, Date]; +export class MetricsTrace implements ITrace<MetricsTraceSupplier> { + constructor( + private readonly metricConsumer: SideEffect<Array<MetricValue>>, + private readonly tracing: Array<MetricTracingTuple> = [], + private readonly flushed: Set<IMetric> = new Set(), + ) {} + + public addTrace(trace: MetricsTraceSupplier) { + if (isMetricValue(trace) || typeof trace === "string") return this; + return new MetricsTrace(this.metricConsumer)._nowTracing(trace); + } + + public trace(metric: MetricsTraceSupplier) { + if (typeof metric === "string") return this; + if (isMetricValue(metric)) { + this.metricConsumer([metric]); + return this; + } + + const foundMetricValues = this.tracing.flatMap(( + [tracing, startedTracing], + ) => + [tracing, tracing.success, tracing.failure] + .filter((_tracing) => metric === _tracing) + .flatMap((metric) => [ + this.addMetric(metric, startedTracing), + this.addMetric(tracing, startedTracing), + ]) + ).flatMap((values) => values); + + if (foundMetricValues.length === 0) { + return this._nowTracing(metric); + } + + this.metricConsumer(foundMetricValues); + return this; + } + + private addMetric(metric: IMetric, startedTracing: Date): Array<MetricValue> { + if (this.flushed.has(metric)) { + return []; + } + + this.flushed.add(metric); + return [ + metric.count.withValue(1.0), + metric.time.withValue(Date.now() - startedTracing.getTime()), + ]; + } + + private _nowTracing(metric: IMetric): MetricsTrace { + this.tracing.push([metric, new Date()]); + return this; + } +} |