summaryrefslogtreecommitdiff
path: root/u/server/metrics.ts
blob: 05df967eaca56912ea7d7cac76307dc72b0e8354 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import {
  type BiMapper,
  Either,
  type IEither,
  type ITraceable,
  type Mapper,
  type Supplier,
} from "@emprespresso/pengueno";

export enum Unit {
  COUNT,
  MILLISECONDS,
}

export interface IMetric<MetricT extends string, TUnit extends Unit> {
  readonly metric: MetricT;
  readonly unit: TUnit;
  readonly value: number;
  readonly emissionTimestamp: Date;
}

export type BaseMetricT = string;
export interface CountMetric<MetricT extends BaseMetricT>
  extends IMetric<MetricT, Unit.COUNT> {
  readonly unit: Unit.COUNT;
}

export interface TimeMetric<MetricT extends BaseMetricT>
  extends IMetric<MetricT, Unit.MILLISECONDS> {
  readonly unit: Unit.MILLISECONDS;
}

export interface IMetricsData<
  MetricT extends BaseMetricT,
  Tracing,
  TraceW,
> {
  addCount: BiMapper<MetricT, number, CountMetric<MetricT>>;

  stopwatch: BiMapper<
    MetricT,
    ITraceable<Tracing, TraceW>,
    ITraceable<MetricT, TraceW>
  >;
  endStopwatch: Mapper<
    ITraceable<MetricT, TraceW>,
    IEither<Error, TimeMetric<MetricT>>
  >;

  flush: Supplier<Array<IMetric<MetricT, Unit>>>;
}

export class TraceableMetricsData<MetricT extends BaseMetricT, Tracing, Trace>
  implements IMetricsData<MetricT, Tracing, Trace> {
  private readonly timers: Map<ITraceable<MetricT, Trace>, Date> = new Map();
  private metricBuffer: Array<IMetric<MetricT, Unit>> = [];

  private constructor() {}

  private addMetric<TUnit extends Unit>(
    metric: MetricT,
    unit: TUnit,
    value: number,
  ): IMetric<MetricT, TUnit> {
    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<MetricT> {
    return this.addMetric(metric, Unit.COUNT, count);
  }

  public stopwatch(metric: MetricT, traceable: ITraceable<Tracing, Trace>) {
    const timer = traceable.move(metric);
    this.timers.set(timer, new Date());
    return timer;
  }

  public endStopwatch(
    stopwatch: ITraceable<MetricT, Trace>,
  ): IEither<Error, TimeMetric<MetricT>> {
    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<Error, TimeMetric<MetricT>>(
        this.addMetric(stopwatch.item, Unit.MILLISECONDS, diff) as TimeMetric<
          MetricT
        >,
      );
    }
    return Either.left<Error, TimeMetric<MetricT>>(
      new Error("cannot stop stopwatch before starting it"),
    );
  }
}