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
134
135
136
137
138
139
140
|
import {
isObject,
type ITrace,
type ITraceWith,
type Mapper,
type SideEffect,
type Supplier,
} from '@emprespresso/pengueno';
export enum Unit {
COUNT = 'COUNT',
MILLISECONDS = 'MILLISECONDS',
}
export interface IMetric {
readonly count: IEmittableMetric;
readonly time: IEmittableMetric;
readonly failure: undefined | IMetric;
readonly success: undefined | IMetric;
readonly warn: undefined | IMetric;
readonly children: Supplier<Array<IMetric>>;
readonly _tag: 'IMetric';
}
export const isIMetric = (t: unknown): t is IMetric => isObject(t) && '_tag' in t && t._tag === 'IMetric';
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,
emissionTimestamp: Date.now(),
value,
_tag: 'MetricValue',
};
}
}
export class Metric implements IMetric {
constructor(
public readonly count: IEmittableMetric,
public readonly time: IEmittableMetric,
public readonly failure: undefined | Metric = undefined,
public readonly success: undefined | Metric = undefined,
public readonly warn: undefined | Metric = undefined,
public readonly _tag: 'IMetric' = 'IMetric',
) {}
public children() {
return [this.failure, this.success, this.warn].filter((x) => x) as IMetric[];
}
static fromName(name: string, addChildren = true): Metric {
return new Metric(
new EmittableMetric(`${name}.count`, Unit.COUNT),
new EmittableMetric(`${name}.elapsed`, Unit.MILLISECONDS),
addChildren ? Metric.fromName(`${name}.failure`, false) : undefined,
addChildren ? Metric.fromName(`${name}.success`, false) : undefined,
addChildren ? Metric.fromName(`${name}.warn`, false) : undefined,
);
}
}
export interface MetricValue {
readonly name: string;
readonly unit: Unit;
readonly value: number;
readonly emissionTimestamp: number;
readonly _tag: 'MetricValue';
}
export const isMetricValue = (t: unknown): t is MetricValue => isObject(t) && '_tag' in t && t._tag === 'MetricValue';
export const isMetricsTraceSupplier = (t: unknown): t is MetricsTraceSupplier => isMetricValue(t) || isIMetric(t);
export type MetricsTraceSupplier = ITraceWith<IMetric | MetricValue | undefined>;
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 (!isIMetric(trace)) return this;
return new MetricsTrace(this.metricConsumer)._nowTracing(trace);
}
public trace(metric: MetricsTraceSupplier) {
if (typeof metric === 'undefined' || typeof metric === 'string') return this;
if (isMetricValue(metric)) {
this.metricConsumer([metric]);
return this;
}
const foundMetricValues = this.tracing
.flatMap(([tracing, startedTracing]) =>
[tracing, ...tracing.children()]
.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 {
if (!metric) return this;
this.tracing.push([metric, new Date()]);
return this;
}
}
|