import { type BiMapper, Either, type IEither, type ITraceable, type Mapper, type Supplier, } from "@emprespresso/pengueno"; export enum Unit { COUNT, MILLISECONDS, } export interface IMetric { readonly metric: MetricT; readonly unit: TUnit; readonly value: number; readonly emissionTimestamp: Date; } export type BaseMetricT = string; export interface CountMetric extends IMetric { readonly unit: Unit.COUNT; } export interface TimeMetric extends IMetric { readonly unit: Unit.MILLISECONDS; } export interface IMetricsData< MetricT extends BaseMetricT, Tracing, TraceW, > { addCount: BiMapper>; stopwatch: BiMapper< MetricT, ITraceable, ITraceable >; endStopwatch: Mapper< ITraceable, IEither> >; flush: Supplier>>; } export class TraceableMetricsData implements IMetricsData { private readonly timers: Map, Date> = new Map(); private metricBuffer: Array> = []; private constructor() {} private addMetric( metric: MetricT, unit: TUnit, value: number, ): IMetric { const _metric = { metric, unit, value, emissionTimestamp: new Date(), }; this.metricBuffer.push(_metric); return _metric; } public flush() { const metrics = [...this.metricBuffer]; this.metricBuffer = []; return metrics; } public addCount( metric: MetricT, count: number, ): CountMetric { return this.addMetric(metric, Unit.COUNT, count); } public stopwatch(metric: MetricT, traceable: ITraceable) { const timer = traceable.move(metric); this.timers.set(timer, new Date()); return timer; } public endStopwatch( stopwatch: ITraceable, ): IEither> { const now = new Date(); if (this.timers.has(stopwatch)) { const timer = this.timers.get(stopwatch)!; const diff = now.getTime() - timer.getTime(); this.timers.delete(stopwatch); return Either.right>( this.addMetric(stopwatch.item, Unit.MILLISECONDS, diff) as TimeMetric< MetricT >, ); } return Either.left>( new Error("cannot stop stopwatch before starting it"), ); } }