@grest-ts/metrics 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +40 -0
- package/dist/src/GGMetric.d.ts +60 -0
- package/dist/src/GGMetric.d.ts.map +1 -0
- package/dist/src/GGMetric.js +64 -0
- package/dist/src/GGMetric.js.map +1 -0
- package/dist/src/GGMetricKey.d.ts +13 -0
- package/dist/src/GGMetricKey.d.ts.map +1 -0
- package/dist/src/GGMetricKey.js +29 -0
- package/dist/src/GGMetricKey.js.map +1 -0
- package/dist/src/GGMetrics.d.ts +10 -0
- package/dist/src/GGMetrics.d.ts.map +1 -0
- package/dist/src/GGMetrics.js +29 -0
- package/dist/src/GGMetrics.js.map +1 -0
- package/dist/src/GGMetricsDefineStorage.d.ts +4 -0
- package/dist/src/GGMetricsDefineStorage.d.ts.map +1 -0
- package/dist/src/GGMetricsDefineStorage.js +7 -0
- package/dist/src/GGMetricsDefineStorage.js.map +1 -0
- package/dist/src/GGMetricsLoader.d.ts +8 -0
- package/dist/src/GGMetricsLoader.d.ts.map +1 -0
- package/dist/src/GGMetricsLoader.js +17 -0
- package/dist/src/GGMetricsLoader.js.map +1 -0
- package/dist/src/GGMetricsStore.d.ts +9 -0
- package/dist/src/GGMetricsStore.d.ts.map +1 -0
- package/dist/src/GGMetricsStore.js +20 -0
- package/dist/src/GGMetricsStore.js.map +1 -0
- package/dist/src/exporters/GGJsonMetricsExporter.d.ts +41 -0
- package/dist/src/exporters/GGJsonMetricsExporter.d.ts.map +1 -0
- package/dist/src/exporters/GGJsonMetricsExporter.js +129 -0
- package/dist/src/exporters/GGJsonMetricsExporter.js.map +1 -0
- package/dist/src/exporters/GGMetricsExporter.d.ts +41 -0
- package/dist/src/exporters/GGMetricsExporter.d.ts.map +1 -0
- package/dist/src/exporters/GGMetricsExporter.js +70 -0
- package/dist/src/exporters/GGMetricsExporter.js.map +1 -0
- package/dist/src/exporters/GGNestedMetricsExporter.d.ts +33 -0
- package/dist/src/exporters/GGNestedMetricsExporter.d.ts.map +1 -0
- package/dist/src/exporters/GGNestedMetricsExporter.js +275 -0
- package/dist/src/exporters/GGNestedMetricsExporter.js.map +1 -0
- package/dist/src/index-browser.d.ts +17 -0
- package/dist/src/index-browser.d.ts.map +1 -0
- package/dist/src/index-browser.js +17 -0
- package/dist/src/index-browser.js.map +1 -0
- package/dist/src/index-node.d.ts +17 -0
- package/dist/src/index-node.d.ts.map +1 -0
- package/dist/src/index-node.js +20 -0
- package/dist/src/index-node.js.map +1 -0
- package/dist/src/keys/GGCounterKey.d.ts +11 -0
- package/dist/src/keys/GGCounterKey.d.ts.map +1 -0
- package/dist/src/keys/GGCounterKey.js +21 -0
- package/dist/src/keys/GGCounterKey.js.map +1 -0
- package/dist/src/keys/GGGaugeKey.d.ts +13 -0
- package/dist/src/keys/GGGaugeKey.d.ts.map +1 -0
- package/dist/src/keys/GGGaugeKey.js +27 -0
- package/dist/src/keys/GGGaugeKey.js.map +1 -0
- package/dist/src/keys/GGHistogramKey.d.ts +13 -0
- package/dist/src/keys/GGHistogramKey.d.ts.map +1 -0
- package/dist/src/keys/GGHistogramKey.js +27 -0
- package/dist/src/keys/GGHistogramKey.js.map +1 -0
- package/dist/src/keys/GGLazyGaugeKey.d.ts +21 -0
- package/dist/src/keys/GGLazyGaugeKey.d.ts.map +1 -0
- package/dist/src/keys/GGLazyGaugeKey.js +25 -0
- package/dist/src/keys/GGLazyGaugeKey.js.map +1 -0
- package/dist/src/metric/GGCounter.d.ts +7 -0
- package/dist/src/metric/GGCounter.d.ts.map +1 -0
- package/dist/src/metric/GGCounter.js +14 -0
- package/dist/src/metric/GGCounter.js.map +1 -0
- package/dist/src/metric/GGGauge.d.ts +9 -0
- package/dist/src/metric/GGGauge.d.ts.map +1 -0
- package/dist/src/metric/GGGauge.js +31 -0
- package/dist/src/metric/GGGauge.js.map +1 -0
- package/dist/src/metric/GGHistogram.d.ts +27 -0
- package/dist/src/metric/GGHistogram.d.ts.map +1 -0
- package/dist/src/metric/GGHistogram.js +41 -0
- package/dist/src/metric/GGHistogram.js.map +1 -0
- package/dist/src/metric/GGLazyGauge.d.ts +20 -0
- package/dist/src/metric/GGLazyGauge.d.ts.map +1 -0
- package/dist/src/metric/GGLazyGauge.js +27 -0
- package/dist/src/metric/GGLazyGauge.js.map +1 -0
- package/dist/src/tsconfig.json +16 -0
- package/dist/testkit/GGMetricsCommands.d.ts +18 -0
- package/dist/testkit/GGMetricsCommands.d.ts.map +1 -0
- package/dist/testkit/GGMetricsCommands.js +77 -0
- package/dist/testkit/GGMetricsCommands.js.map +1 -0
- package/dist/testkit/GGMetricsInterceptor.d.ts +34 -0
- package/dist/testkit/GGMetricsInterceptor.d.ts.map +1 -0
- package/dist/testkit/GGMetricsInterceptor.js +202 -0
- package/dist/testkit/GGMetricsInterceptor.js.map +1 -0
- package/dist/testkit/GGMetricsWith.d.ts +22 -0
- package/dist/testkit/GGMetricsWith.d.ts.map +1 -0
- package/dist/testkit/GGMetricsWith.js +61 -0
- package/dist/testkit/GGMetricsWith.js.map +1 -0
- package/dist/testkit/GGTestMetricsExporter.d.ts +40 -0
- package/dist/testkit/GGTestMetricsExporter.d.ts.map +1 -0
- package/dist/testkit/GGTestMetricsExporter.js +119 -0
- package/dist/testkit/GGTestMetricsExporter.js.map +1 -0
- package/dist/testkit/GGTestSelectorMetrics.d.ts +15 -0
- package/dist/testkit/GGTestSelectorMetrics.d.ts.map +1 -0
- package/dist/testkit/GGTestSelectorMetrics.js +16 -0
- package/dist/testkit/GGTestSelectorMetrics.js.map +1 -0
- package/dist/testkit/index-testkit.d.ts +6 -0
- package/dist/testkit/index-testkit.d.ts.map +1 -0
- package/dist/testkit/index-testkit.js +6 -0
- package/dist/testkit/index-testkit.js.map +1 -0
- package/dist/tsconfig.publish.tsbuildinfo +1 -0
- package/package.json +58 -0
- package/src/GGMetric.ts +124 -0
- package/src/GGMetricKey.ts +38 -0
- package/src/GGMetrics.ts +34 -0
- package/src/GGMetricsDefineStorage.ts +8 -0
- package/src/GGMetricsLoader.ts +21 -0
- package/src/GGMetricsStore.ts +26 -0
- package/src/exporters/GGJsonMetricsExporter.ts +176 -0
- package/src/exporters/GGMetricsExporter.ts +88 -0
- package/src/exporters/GGNestedMetricsExporter.ts +335 -0
- package/src/index-browser.ts +16 -0
- package/src/index-node.ts +21 -0
- package/src/keys/GGCounterKey.ts +29 -0
- package/src/keys/GGGaugeKey.ts +37 -0
- package/src/keys/GGHistogramKey.ts +37 -0
- package/src/keys/GGLazyGaugeKey.ts +36 -0
- package/src/metric/GGCounter.ts +19 -0
- package/src/metric/GGGauge.ts +38 -0
- package/src/metric/GGHistogram.ts +68 -0
- package/src/metric/GGLazyGauge.ts +31 -0
- package/src/tsconfig.json +16 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import {GGMetric} from "../GGMetric.js";
|
|
2
|
+
import {GGCounter} from "../metric/GGCounter.js";
|
|
3
|
+
import {GGGauge} from "../metric/GGGauge.js";
|
|
4
|
+
import {GGLazyGauge} from "../metric/GGLazyGauge.js";
|
|
5
|
+
import {GGHistogram, HistogramData} from "../metric/GGHistogram.js";
|
|
6
|
+
import {GGMetricKey} from "../GGMetricKey.js";
|
|
7
|
+
import {GGMetricsExporter, ExporterConfig} from "./GGMetricsExporter.js";
|
|
8
|
+
|
|
9
|
+
export type NestedValueConverter = (metric: GGMetric<any>, value: any, exporter: GGNestedMetricsExporter) => any;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Exports metrics in a nested, human-readable format.
|
|
13
|
+
* Groups metrics by their groupBy configuration and nests remaining labels.
|
|
14
|
+
*/
|
|
15
|
+
export class GGNestedMetricsExporter extends GGMetricsExporter<NestedMetricsOutput> {
|
|
16
|
+
|
|
17
|
+
// Static map for extensibility - register value converters for new metric types
|
|
18
|
+
private static converters = new Map<Function, NestedValueConverter>();
|
|
19
|
+
|
|
20
|
+
static {
|
|
21
|
+
GGNestedMetricsExporter.registerConverter(GGCounter, convertCounterValue);
|
|
22
|
+
GGNestedMetricsExporter.registerConverter(GGGauge, convertGaugeValue);
|
|
23
|
+
GGNestedMetricsExporter.registerConverter(GGLazyGauge, convertLazyGaugeValue);
|
|
24
|
+
GGNestedMetricsExporter.registerConverter(GGHistogram, convertHistogramValue);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Register a value converter for a custom metric type.
|
|
29
|
+
*/
|
|
30
|
+
static registerConverter(metricClass: Function, converter: NestedValueConverter): void {
|
|
31
|
+
GGNestedMetricsExporter.converters.set(metricClass, converter);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
constructor(config: ExporterConfig = {}) {
|
|
35
|
+
super(config);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getMetrics(): NestedMetricsOutput {
|
|
39
|
+
const output: NestedMetricsOutput = {
|
|
40
|
+
timestamp: Date.now(),
|
|
41
|
+
groups: {}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Collect all metric values with their parsed data
|
|
45
|
+
const allValues: MetricValueEntry[] = [];
|
|
46
|
+
|
|
47
|
+
for (const metric of this.getFilteredMetrics()) {
|
|
48
|
+
const entries = this.collectMetricValues(metric);
|
|
49
|
+
allValues.push(...entries);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Group by the groupBy key
|
|
53
|
+
const groupedByKey = new Map<string, MetricValueEntry[]>();
|
|
54
|
+
for (const entry of allValues) {
|
|
55
|
+
const existing = groupedByKey.get(entry.groupKey);
|
|
56
|
+
if (existing) {
|
|
57
|
+
existing.push(entry);
|
|
58
|
+
} else {
|
|
59
|
+
groupedByKey.set(entry.groupKey, [entry]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Build the nested structure for each group
|
|
64
|
+
for (const [groupKey, entries] of groupedByKey) {
|
|
65
|
+
output.groups[groupKey] = this.buildGroupEntries(entries);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return output;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Parse a label key string into a labels object.
|
|
73
|
+
* Exposed for use by converters.
|
|
74
|
+
*/
|
|
75
|
+
parseLabels(labelKey: string): Record<string, string> {
|
|
76
|
+
if (!labelKey) return {};
|
|
77
|
+
const labels: Record<string, string> = {};
|
|
78
|
+
const parts = labelKey.split(',');
|
|
79
|
+
for (const part of parts) {
|
|
80
|
+
const [key, val] = part.split('=');
|
|
81
|
+
if (key && val !== undefined) {
|
|
82
|
+
labels[key] = val;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return labels;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private collectMetricValues(metric: GGMetric<any>): MetricValueEntry[] {
|
|
89
|
+
const entries: MetricValueEntry[] = [];
|
|
90
|
+
const key = metric.key as GGMetricKey<any>;
|
|
91
|
+
const groupByLabels = key.groupBy?.labels ?? [];
|
|
92
|
+
const metricName = this.getShortName(key);
|
|
93
|
+
|
|
94
|
+
for (const [labelKey, value] of metric.getValues()) {
|
|
95
|
+
const labels = this.parseLabels(labelKey);
|
|
96
|
+
|
|
97
|
+
// Compute groupBy key
|
|
98
|
+
const groupKey = this.computeGroupKey(key, labels);
|
|
99
|
+
|
|
100
|
+
// Compute remaining labels (not in groupBy)
|
|
101
|
+
const remainingLabels: Record<string, string> = {};
|
|
102
|
+
for (const [k, v] of Object.entries(labels)) {
|
|
103
|
+
if (!groupByLabels.includes(k)) {
|
|
104
|
+
remainingLabels[k] = v;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
entries.push({
|
|
109
|
+
groupKey,
|
|
110
|
+
metricName,
|
|
111
|
+
metricType: this.getMetricType(metric),
|
|
112
|
+
remainingLabels,
|
|
113
|
+
value: this.formatValue(metric, value)
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return entries;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private computeGroupKey(key: GGMetricKey<any>, labels: Record<string, string>): string {
|
|
121
|
+
const groupBy = key.groupBy;
|
|
122
|
+
if (!groupBy || groupBy.labels.length === 0) {
|
|
123
|
+
return key.root; // Use metric root as default group
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (groupBy.template) {
|
|
127
|
+
// Template string - replace {labelName} with values, missing values become empty string
|
|
128
|
+
return groupBy.template.replace(/\{(\w+)\}/g, (_, labelName) => {
|
|
129
|
+
return String(labels[labelName] ?? '');
|
|
130
|
+
});
|
|
131
|
+
} else {
|
|
132
|
+
// Default: join values with comma
|
|
133
|
+
return groupBy.labels.map(l => String(labels[l] ?? '')).join(',');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private getShortName(key: GGMetricKey<any>): string {
|
|
138
|
+
// Remove the root prefix to get just the metric name
|
|
139
|
+
return key.name.replace(key.root, '');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private getMetricType(metric: GGMetric<any>): string {
|
|
143
|
+
return metric.constructor.name;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private formatValue(metric: GGMetric<any>, value: any): any {
|
|
147
|
+
const converter = GGNestedMetricsExporter.converters.get(metric.constructor);
|
|
148
|
+
if (!converter) {
|
|
149
|
+
// Fallback: return raw value
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
152
|
+
return converter(metric, value, this);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private buildGroupEntries(entries: MetricValueEntry[]): any[] {
|
|
156
|
+
// Find all unique "shared" label combinations
|
|
157
|
+
// Shared labels are those that exist in multiple metrics (like 'path')
|
|
158
|
+
// Non-shared labels are metric-specific (like 'result' only on counter)
|
|
159
|
+
|
|
160
|
+
// First, find which labels are common across all metrics in this group
|
|
161
|
+
const labelSets = new Map<string, Set<string>>();
|
|
162
|
+
for (const entry of entries) {
|
|
163
|
+
const labelNames = Object.keys(entry.remainingLabels);
|
|
164
|
+
const existing = labelSets.get(entry.metricName);
|
|
165
|
+
if (!existing) {
|
|
166
|
+
labelSets.set(entry.metricName, new Set(labelNames));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Find common labels (present in all metrics)
|
|
171
|
+
const allMetrics = [...labelSets.keys()];
|
|
172
|
+
let commonLabels: Set<string> = new Set();
|
|
173
|
+
if (allMetrics.length > 0) {
|
|
174
|
+
commonLabels = new Set(labelSets.get(allMetrics[0])!);
|
|
175
|
+
for (let i = 1; i < allMetrics.length; i++) {
|
|
176
|
+
const metricLabels = labelSets.get(allMetrics[i])!;
|
|
177
|
+
for (const label of commonLabels) {
|
|
178
|
+
if (!metricLabels.has(label)) {
|
|
179
|
+
commonLabels.delete(label);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Group entries by their common label values
|
|
186
|
+
const byCommonLabels = new Map<string, MetricValueEntry[]>();
|
|
187
|
+
for (const entry of entries) {
|
|
188
|
+
const commonKey = [...commonLabels]
|
|
189
|
+
.sort()
|
|
190
|
+
.map(l => `${l}=${entry.remainingLabels[l] ?? ''}`)
|
|
191
|
+
.join(',');
|
|
192
|
+
|
|
193
|
+
const existing = byCommonLabels.get(commonKey);
|
|
194
|
+
if (existing) {
|
|
195
|
+
existing.push(entry);
|
|
196
|
+
} else {
|
|
197
|
+
byCommonLabels.set(commonKey, [entry]);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Build output entries
|
|
202
|
+
const result: any[] = [];
|
|
203
|
+
for (const [_, groupEntries] of byCommonLabels) {
|
|
204
|
+
const outputEntry: Record<string, any> = {};
|
|
205
|
+
|
|
206
|
+
// Add common labels as direct properties
|
|
207
|
+
if (groupEntries.length > 0) {
|
|
208
|
+
for (const label of commonLabels) {
|
|
209
|
+
outputEntry[label] = groupEntries[0].remainingLabels[label];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Group entries by metric name
|
|
214
|
+
const byMetric = new Map<string, MetricValueEntry[]>();
|
|
215
|
+
for (const entry of groupEntries) {
|
|
216
|
+
const existing = byMetric.get(entry.metricName);
|
|
217
|
+
if (existing) {
|
|
218
|
+
existing.push(entry);
|
|
219
|
+
} else {
|
|
220
|
+
byMetric.set(entry.metricName, [entry]);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Add each metric's values
|
|
225
|
+
for (const [metricName, metricEntries] of byMetric) {
|
|
226
|
+
// Get extra labels (not in commonLabels)
|
|
227
|
+
const extraLabels = Object.keys(metricEntries[0].remainingLabels)
|
|
228
|
+
.filter(l => !commonLabels.has(l));
|
|
229
|
+
|
|
230
|
+
if (extraLabels.length === 0) {
|
|
231
|
+
// No extra labels - value goes directly under metric name
|
|
232
|
+
outputEntry[metricName] = metricEntries[0].value;
|
|
233
|
+
} else {
|
|
234
|
+
// Has extra labels - nest by those label names
|
|
235
|
+
this.nestByLabels(outputEntry, metricName, metricEntries, extraLabels, 0);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
result.push(outputEntry);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private nestByLabels(
|
|
246
|
+
obj: Record<string, any>,
|
|
247
|
+
metricName: string,
|
|
248
|
+
entries: MetricValueEntry[],
|
|
249
|
+
extraLabels: string[],
|
|
250
|
+
labelIndex: number
|
|
251
|
+
): void {
|
|
252
|
+
const currentLabel = extraLabels[labelIndex];
|
|
253
|
+
const isLastLabel = labelIndex === extraLabels.length - 1;
|
|
254
|
+
|
|
255
|
+
// Group entries by current label value
|
|
256
|
+
const byLabelValue = new Map<string, MetricValueEntry[]>();
|
|
257
|
+
for (const entry of entries) {
|
|
258
|
+
const labelValue = entry.remainingLabels[currentLabel] ?? '';
|
|
259
|
+
const existing = byLabelValue.get(labelValue);
|
|
260
|
+
if (existing) {
|
|
261
|
+
existing.push(entry);
|
|
262
|
+
} else {
|
|
263
|
+
byLabelValue.set(labelValue, [entry]);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Create or get the label container
|
|
268
|
+
if (!obj[currentLabel]) {
|
|
269
|
+
obj[currentLabel] = {};
|
|
270
|
+
}
|
|
271
|
+
const labelContainer = obj[currentLabel];
|
|
272
|
+
|
|
273
|
+
// Add each label value
|
|
274
|
+
for (const [labelValue, valueEntries] of byLabelValue) {
|
|
275
|
+
if (isLastLabel) {
|
|
276
|
+
// Last label - add metric name and value
|
|
277
|
+
if (!labelContainer[labelValue]) {
|
|
278
|
+
labelContainer[labelValue] = {};
|
|
279
|
+
}
|
|
280
|
+
labelContainer[labelValue][metricName] = valueEntries[0].value;
|
|
281
|
+
} else {
|
|
282
|
+
// More labels to go - recurse
|
|
283
|
+
if (!labelContainer[labelValue]) {
|
|
284
|
+
labelContainer[labelValue] = {};
|
|
285
|
+
}
|
|
286
|
+
this.nestByLabels(labelContainer[labelValue], metricName, valueEntries, extraLabels, labelIndex + 1);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Built-in value converters
|
|
293
|
+
|
|
294
|
+
function convertCounterValue(_metric: GGCounter<any>, value: number, _exporter: GGNestedMetricsExporter): number {
|
|
295
|
+
return value;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function convertGaugeValue(_metric: GGGauge<any>, value: number, _exporter: GGNestedMetricsExporter): number {
|
|
299
|
+
return value;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function convertLazyGaugeValue(_metric: GGLazyGauge, value: number, _exporter: GGNestedMetricsExporter): number {
|
|
303
|
+
return value;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function convertHistogramValue(metric: GGHistogram<any>, value: HistogramData, _exporter: GGNestedMetricsExporter): any {
|
|
307
|
+
const buckets = metric.getBuckets();
|
|
308
|
+
const bucketObj: Record<string, number> = {};
|
|
309
|
+
for (let i = 0; i < buckets.length; i++) {
|
|
310
|
+
bucketObj[String(buckets[i])] = value.values[i] ?? 0;
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
count: value.count,
|
|
314
|
+
sum: value.sum,
|
|
315
|
+
avg: value.count > 0 ? value.sum / value.count : 0,
|
|
316
|
+
min: value.min === Infinity ? 0 : value.min,
|
|
317
|
+
max: value.max === -Infinity ? 0 : value.max,
|
|
318
|
+
buckets: bucketObj
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Types
|
|
323
|
+
|
|
324
|
+
interface MetricValueEntry {
|
|
325
|
+
groupKey: string;
|
|
326
|
+
metricName: string;
|
|
327
|
+
metricType: string;
|
|
328
|
+
remainingLabels: Record<string, string>;
|
|
329
|
+
value: any;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export interface NestedMetricsOutput {
|
|
333
|
+
timestamp: number;
|
|
334
|
+
groups: Record<string, any[]>;
|
|
335
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from './GGMetric';
|
|
2
|
+
export * from './metric/GGCounter';
|
|
3
|
+
export * from './metric/GGGauge';
|
|
4
|
+
export * from './metric/GGLazyGauge';
|
|
5
|
+
export * from './metric/GGHistogram';
|
|
6
|
+
export * from './GGMetrics';
|
|
7
|
+
export * from './GGMetricsLoader';
|
|
8
|
+
export * from "./GGMetricKey";
|
|
9
|
+
export * from "./keys/GGCounterKey";
|
|
10
|
+
export * from "./keys/GGGaugeKey";
|
|
11
|
+
export * from "./keys/GGLazyGaugeKey";
|
|
12
|
+
export * from "./keys/GGHistogramKey";
|
|
13
|
+
export * from './exporters/GGMetricsExporter';
|
|
14
|
+
export * from './exporters/GGJsonMetricsExporter';
|
|
15
|
+
export * from './exporters/GGNestedMetricsExporter';
|
|
16
|
+
export * from "./GGMetricsStore";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {AsyncLocalStorage} from "node:async_hooks";
|
|
2
|
+
import {_initMetricsStorage} from "./GGMetricsDefineStorage";
|
|
3
|
+
_initMetricsStorage(new AsyncLocalStorage());
|
|
4
|
+
|
|
5
|
+
export * from './GGMetric';
|
|
6
|
+
export * from './metric/GGCounter';
|
|
7
|
+
export * from './metric/GGGauge';
|
|
8
|
+
export * from './metric/GGLazyGauge';
|
|
9
|
+
export * from './metric/GGHistogram';
|
|
10
|
+
export * from './GGMetrics';
|
|
11
|
+
export * from './GGMetricsLoader';
|
|
12
|
+
export * from "./GGMetricKey";
|
|
13
|
+
export * from "./keys/GGCounterKey";
|
|
14
|
+
export * from "./keys/GGGaugeKey";
|
|
15
|
+
export * from "./keys/GGLazyGaugeKey";
|
|
16
|
+
export * from "./keys/GGHistogramKey";
|
|
17
|
+
export * from './exporters/GGMetricsExporter';
|
|
18
|
+
export * from './exporters/GGJsonMetricsExporter';
|
|
19
|
+
export * from './exporters/GGNestedMetricsExporter';
|
|
20
|
+
export * from "./GGMetricsStore";
|
|
21
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {GGMetricKey} from "../GGMetricKey.js";
|
|
2
|
+
import {GGCounter} from "../metric/GGCounter.js";
|
|
3
|
+
import {GGMetricLabels, GGMetricOptions, LabelsArgs} from "../GGMetric.js";
|
|
4
|
+
|
|
5
|
+
export class GGCounterKey<
|
|
6
|
+
TLabels extends GGMetricLabels = {}
|
|
7
|
+
> extends GGMetricKey<TLabels, GGCounter<TLabels>> {
|
|
8
|
+
|
|
9
|
+
constructor(name: string, options: GGMetricOptions<TLabels>) {
|
|
10
|
+
super(name, options);
|
|
11
|
+
Object.freeze(this);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public create(): GGCounter<TLabels> {
|
|
15
|
+
return new GGCounter<TLabels>(this);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public inc(value: number = 1, ...args: LabelsArgs<TLabels>): void {
|
|
19
|
+
this.getMetric().inc(value, ...args);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public getValue(...args: LabelsArgs<TLabels>): number {
|
|
23
|
+
return this.getMetric().getValue(...args);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public reset(): void {
|
|
27
|
+
this.getMetric().reset();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {GGMetricKey} from "../GGMetricKey.js";
|
|
2
|
+
import {GGGauge} from "../metric/GGGauge.js";
|
|
3
|
+
import {GGMetricLabels, GGMetricOptions, LabelsArgs} from "../GGMetric.js";
|
|
4
|
+
|
|
5
|
+
export class GGGaugeKey<
|
|
6
|
+
TLabels extends GGMetricLabels = {}
|
|
7
|
+
> extends GGMetricKey<TLabels, GGGauge<TLabels>> {
|
|
8
|
+
|
|
9
|
+
constructor(name: string, options: GGMetricOptions<TLabels>) {
|
|
10
|
+
super(name, options);
|
|
11
|
+
Object.freeze(this);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public create(): GGGauge<TLabels> {
|
|
15
|
+
return new GGGauge<TLabels>(this);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public set(value: number, ...args: LabelsArgs<TLabels>): void {
|
|
19
|
+
this.getMetric().set(value, ...args);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public inc(value: number = 1, ...args: LabelsArgs<TLabels>): void {
|
|
23
|
+
this.getMetric().inc(value, ...args);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public dec(value: number = 1, ...args: LabelsArgs<TLabels>): void {
|
|
27
|
+
this.getMetric().dec(value, ...args);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public getValue(...args: LabelsArgs<TLabels>): number {
|
|
31
|
+
return this.getMetric().getValue(...args);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public reset(): void {
|
|
35
|
+
this.getMetric().reset();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {GGMetricKey} from "../GGMetricKey.js";
|
|
2
|
+
import {GGHistogram, HistogramData, HistogramOptions} from "../metric/GGHistogram.js";
|
|
3
|
+
import {GGMetricLabels, LabelsArgs} from "../GGMetric.js";
|
|
4
|
+
|
|
5
|
+
export class GGHistogramKey<
|
|
6
|
+
TLabels extends GGMetricLabels = {}
|
|
7
|
+
> extends GGMetricKey<TLabels, GGHistogram<TLabels>> {
|
|
8
|
+
|
|
9
|
+
public readonly buckets: number[];
|
|
10
|
+
|
|
11
|
+
constructor(name: string, options: HistogramOptions<TLabels>) {
|
|
12
|
+
super(name, options);
|
|
13
|
+
this.buckets = options.buckets.sort((a, b) => a - b)
|
|
14
|
+
Object.freeze(this.buckets)
|
|
15
|
+
Object.freeze(this);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public create(): GGHistogram<TLabels> {
|
|
19
|
+
return new GGHistogram<TLabels>(this);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public observe(value: number, ...args: LabelsArgs<TLabels>): void {
|
|
23
|
+
this.getMetric().observe(value, ...args);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public startTimer(...args: LabelsArgs<TLabels>): () => void {
|
|
27
|
+
return this.getMetric().startTimer(...args);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public getValue(...args: LabelsArgs<TLabels>): HistogramData | undefined {
|
|
31
|
+
return this.getMetric().getValue(...args);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public reset(): void {
|
|
35
|
+
this.getMetric().reset();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {GGMetricKey} from "../GGMetricKey.js";
|
|
2
|
+
import {GGLazyGauge} from "../metric/GGLazyGauge.js";
|
|
3
|
+
import {GGMetricOptionsBase} from "../GGMetric.js";
|
|
4
|
+
|
|
5
|
+
export interface LazyGaugeOptions extends GGMetricOptionsBase {
|
|
6
|
+
getValue: () => number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Key for a lazy gauge metric.
|
|
11
|
+
* The getValue function is called whenever the metric value is read.
|
|
12
|
+
*/
|
|
13
|
+
export class GGLazyGaugeKey extends GGMetricKey<{}, GGLazyGauge> {
|
|
14
|
+
|
|
15
|
+
public declare readonly options: LazyGaugeOptions;
|
|
16
|
+
|
|
17
|
+
constructor(name: string, options: LazyGaugeOptions) {
|
|
18
|
+
super(name, options);
|
|
19
|
+
Object.freeze(this);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public create(): GGLazyGauge {
|
|
23
|
+
return new GGLazyGauge(this);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get the current value by calling the getValue function.
|
|
28
|
+
*/
|
|
29
|
+
public getValue(): number {
|
|
30
|
+
return this.getMetric().getValue();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public reset(): void {
|
|
34
|
+
// No-op for lazy gauge - nothing to reset
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {GGMetric, GGMetricLabels, LabelsArgs} from "../GGMetric.js";
|
|
2
|
+
import type {GGCounterKey} from "../keys/GGCounterKey";
|
|
3
|
+
|
|
4
|
+
export class GGCounter<
|
|
5
|
+
TLabels extends GGMetricLabels = {}
|
|
6
|
+
> extends GGMetric<TLabels, number, GGCounterKey<TLabels>> {
|
|
7
|
+
|
|
8
|
+
protected getDefaultValue(): number {
|
|
9
|
+
return 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public inc(value: number = 1, ...args: LabelsArgs<TLabels>): void {
|
|
13
|
+
const key = this.getKey(args[0] as TLabels);
|
|
14
|
+
const current = this.getByKey(key);
|
|
15
|
+
if (current !== undefined) {
|
|
16
|
+
this.setByKey(key, current + value);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {GGMetric, GGMetricLabels, LabelsArgs} from "../GGMetric.js";
|
|
2
|
+
import type {GGGaugeKey} from "../keys/GGGaugeKey";
|
|
3
|
+
|
|
4
|
+
export class GGGauge<
|
|
5
|
+
TLabels extends GGMetricLabels = {}
|
|
6
|
+
> extends GGMetric<TLabels, number, GGGaugeKey<TLabels>> {
|
|
7
|
+
|
|
8
|
+
protected getDefaultValue(): number {
|
|
9
|
+
return 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public set(value: number, ...args: LabelsArgs<TLabels>): void {
|
|
13
|
+
const key = this.getKey(args[0] as TLabels);
|
|
14
|
+
const current = this.getByKey(key);
|
|
15
|
+
if (current === undefined) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
this.setByKey(key, value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public inc(value: number = 1, ...args: LabelsArgs<TLabels>): void {
|
|
22
|
+
const key = this.getKey(args[0] as TLabels);
|
|
23
|
+
const current = this.getByKey(key);
|
|
24
|
+
if (current === undefined) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
this.setByKey(key, current + value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public dec(value: number = 1, ...args: LabelsArgs<TLabels>): void {
|
|
31
|
+
const key = this.getKey(args[0] as TLabels);
|
|
32
|
+
const current = this.getByKey(key);
|
|
33
|
+
if (current === undefined) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
this.setByKey(key, current - value);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {GGMetric, GGMetricLabels, GGMetricOptions, LabelsArgs} from "../GGMetric.js";
|
|
2
|
+
import type {GGHistogramKey} from "../keys/GGHistogramKey";
|
|
3
|
+
|
|
4
|
+
export type HistogramOptions<TLabels extends GGMetricLabels = {}> =
|
|
5
|
+
GGMetricOptions<TLabels> & { buckets: number[] };
|
|
6
|
+
|
|
7
|
+
export class GGHistogram<
|
|
8
|
+
TLabels extends GGMetricLabels = {}
|
|
9
|
+
> extends GGMetric<TLabels, HistogramData, GGHistogramKey<TLabels>> {
|
|
10
|
+
|
|
11
|
+
public getBuckets(): number[] {
|
|
12
|
+
return this.key.buckets;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
protected getDefaultValue(): HistogramData {
|
|
16
|
+
return {
|
|
17
|
+
count: 0,
|
|
18
|
+
sum: 0,
|
|
19
|
+
min: Infinity,
|
|
20
|
+
max: -Infinity,
|
|
21
|
+
values: this.key.buckets.map(() => 0)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public observe(value: number, ...args: LabelsArgs<TLabels>): void {
|
|
26
|
+
const key = this.getKey(args[0] as TLabels);
|
|
27
|
+
const data = this.getByKey(key);
|
|
28
|
+
if (data === undefined) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
data.count++;
|
|
32
|
+
data.sum += value;
|
|
33
|
+
if (value < data.min) {
|
|
34
|
+
data.min = value;
|
|
35
|
+
}
|
|
36
|
+
if (value > data.max) {
|
|
37
|
+
data.max = value;
|
|
38
|
+
}
|
|
39
|
+
for (let i = 0; i < this.key.buckets.length; i++) {
|
|
40
|
+
if (value <= this.key.buckets[i]) {
|
|
41
|
+
data.values[i]++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public startTimer(...args: LabelsArgs<TLabels>): () => void {
|
|
47
|
+
const start = Date.now();
|
|
48
|
+
const labels = args[0] as TLabels;
|
|
49
|
+
return () => this.observe(Date.now() - start, ...([labels] as LabelsArgs<TLabels>));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface HistogramData {
|
|
54
|
+
count: number;
|
|
55
|
+
sum: number;
|
|
56
|
+
min: number;
|
|
57
|
+
max: number;
|
|
58
|
+
values: number[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface SerializedHistogramData {
|
|
62
|
+
count: number;
|
|
63
|
+
sum: number;
|
|
64
|
+
min: number;
|
|
65
|
+
max: number;
|
|
66
|
+
buckets: number[];
|
|
67
|
+
values: number[];
|
|
68
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {GGMetric} from "../GGMetric.js";
|
|
2
|
+
import type {GGLazyGaugeKey} from "../keys/GGLazyGaugeKey";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A gauge that computes its value lazily by calling a function.
|
|
6
|
+
* Perfect for "current state" metrics like memory usage, active handles, etc.
|
|
7
|
+
* Does not support labels - use regular GGGauge if you need labels.
|
|
8
|
+
*/
|
|
9
|
+
export class GGLazyGauge extends GGMetric<{}, number, GGLazyGaugeKey> {
|
|
10
|
+
|
|
11
|
+
protected getDefaultValue(): number {
|
|
12
|
+
return 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get the current value by calling the getValue function.
|
|
17
|
+
*/
|
|
18
|
+
public getValue(): number {
|
|
19
|
+
return this.key.options.getValue();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Returns a map with single entry (empty key -> current value).
|
|
24
|
+
* Compatible with exporter iteration pattern.
|
|
25
|
+
*/
|
|
26
|
+
public getValues(): Map<string, number> {
|
|
27
|
+
const map = new Map<string, number>();
|
|
28
|
+
map.set('', this.getValue());
|
|
29
|
+
return map;
|
|
30
|
+
}
|
|
31
|
+
}
|