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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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;
}
}
|