@grest-ts/metrics 0.0.6 → 0.0.8
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 -21
- package/README.md +40 -40
- package/dist/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/src/GGMetric.ts +124 -124
- package/src/GGMetricKey.ts +38 -38
- package/src/GGMetrics.ts +34 -34
- package/src/GGMetricsDefineStorage.ts +8 -8
- package/src/GGMetricsLoader.ts +21 -21
- package/src/GGMetricsStore.ts +26 -26
- package/src/exporters/GGJsonMetricsExporter.ts +176 -176
- package/src/exporters/GGMetricsExporter.ts +88 -88
- package/src/exporters/GGNestedMetricsExporter.ts +335 -335
- package/src/index-browser.ts +16 -16
- package/src/index-node.ts +21 -21
- package/src/keys/GGCounterKey.ts +29 -29
- package/src/keys/GGGaugeKey.ts +37 -37
- package/src/keys/GGHistogramKey.ts +37 -37
- package/src/keys/GGLazyGaugeKey.ts +36 -36
- package/src/metric/GGCounter.ts +19 -19
- package/src/metric/GGGauge.ts +38 -38
- package/src/metric/GGHistogram.ts +68 -68
- package/src/metric/GGLazyGauge.ts +31 -31
|
@@ -1,176 +1,176 @@
|
|
|
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 {GGMetricsExporter, ExporterConfig} from "./GGMetricsExporter.js";
|
|
7
|
-
|
|
8
|
-
export type JsonMetricConverter = (metric: GGMetric<any>, exporter: GGJsonMetricsExporter) => JsonMetric;
|
|
9
|
-
|
|
10
|
-
export class GGJsonMetricsExporter extends GGMetricsExporter<JsonMetricsOutput> {
|
|
11
|
-
|
|
12
|
-
// Static map for extensibility - register converters for new metric types
|
|
13
|
-
private static converters = new Map<Function, JsonMetricConverter>();
|
|
14
|
-
|
|
15
|
-
static {
|
|
16
|
-
GGJsonMetricsExporter.registerConverter(GGCounter, convertCounter);
|
|
17
|
-
GGJsonMetricsExporter.registerConverter(GGGauge, convertGauge);
|
|
18
|
-
GGJsonMetricsExporter.registerConverter(GGLazyGauge, convertLazyGauge);
|
|
19
|
-
GGJsonMetricsExporter.registerConverter(GGHistogram, convertHistogram);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Register a converter for a custom metric type.
|
|
24
|
-
*/
|
|
25
|
-
static registerConverter(metricClass: Function, converter: JsonMetricConverter): void {
|
|
26
|
-
GGJsonMetricsExporter.converters.set(metricClass, converter);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
constructor(config: ExporterConfig = {}) {
|
|
30
|
-
super(config);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
getMetrics(): JsonMetricsOutput {
|
|
34
|
-
const output: JsonMetricsOutput = {
|
|
35
|
-
timestamp: Date.now(),
|
|
36
|
-
metrics: {}
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
for (const metric of this.getFilteredMetrics()) {
|
|
40
|
-
output.metrics[metric.name] = this.convertMetric(metric);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return output;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
private convertMetric(metric: GGMetric<any>): JsonMetric {
|
|
47
|
-
const converter = GGJsonMetricsExporter.converters.get(metric.constructor);
|
|
48
|
-
if (!converter) {
|
|
49
|
-
throw new Error(`No converter registered for metric type: ${metric.constructor.name}`);
|
|
50
|
-
}
|
|
51
|
-
return converter(metric, this);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Parse a label key string into a labels object.
|
|
56
|
-
* Exposed for use by converters.
|
|
57
|
-
*/
|
|
58
|
-
parseLabels(labelKey: string): Record<string, string> {
|
|
59
|
-
if (!labelKey) return {};
|
|
60
|
-
const labels: Record<string, string> = {};
|
|
61
|
-
const parts = labelKey.split(',');
|
|
62
|
-
for (const part of parts) {
|
|
63
|
-
const [key, val] = part.split('=');
|
|
64
|
-
if (key && val !== undefined) {
|
|
65
|
-
labels[key] = val;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return labels;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Built-in converters
|
|
73
|
-
|
|
74
|
-
function convertCounter(metric: GGCounter<any>, exporter: GGJsonMetricsExporter): JsonMetric {
|
|
75
|
-
const values: JsonMetricValue[] = [];
|
|
76
|
-
for (const [labelKey, value] of metric.getValues()) {
|
|
77
|
-
values.push({
|
|
78
|
-
labels: exporter.parseLabels(labelKey),
|
|
79
|
-
value
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
return {
|
|
83
|
-
name: metric.name,
|
|
84
|
-
type: 'counter',
|
|
85
|
-
help: metric.key.help,
|
|
86
|
-
values
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function convertGauge(metric: GGGauge<any>, exporter: GGJsonMetricsExporter): JsonMetric {
|
|
91
|
-
const values: JsonMetricValue[] = [];
|
|
92
|
-
for (const [labelKey, value] of metric.getValues()) {
|
|
93
|
-
values.push({
|
|
94
|
-
labels: exporter.parseLabels(labelKey),
|
|
95
|
-
value
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
return {
|
|
99
|
-
name: metric.name,
|
|
100
|
-
type: 'gauge',
|
|
101
|
-
help: metric.key.help,
|
|
102
|
-
values
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function convertLazyGauge(metric: GGLazyGauge, exporter: GGJsonMetricsExporter): JsonMetric {
|
|
107
|
-
// Lazy gauge computes value on read - no labels
|
|
108
|
-
return {
|
|
109
|
-
name: metric.name,
|
|
110
|
-
type: 'gauge',
|
|
111
|
-
help: metric.key.help,
|
|
112
|
-
values: [{
|
|
113
|
-
labels: {},
|
|
114
|
-
value: metric.getValue()
|
|
115
|
-
}]
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function convertHistogram(metric: GGHistogram<any>, exporter: GGJsonMetricsExporter): JsonMetric {
|
|
120
|
-
const values: JsonMetricValue[] = [];
|
|
121
|
-
const buckets = metric.getBuckets();
|
|
122
|
-
|
|
123
|
-
for (const [labelKey, data] of metric.getValues() as Map<string, HistogramData>) {
|
|
124
|
-
const bucketObj: Record<string, number> = {};
|
|
125
|
-
for (let i = 0; i < buckets.length; i++) {
|
|
126
|
-
bucketObj[String(buckets[i])] = data.values[i] ?? 0;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
values.push({
|
|
130
|
-
labels: exporter.parseLabels(labelKey),
|
|
131
|
-
value: {
|
|
132
|
-
count: data.count,
|
|
133
|
-
sum: data.sum,
|
|
134
|
-
avg: data.count > 0 ? data.sum / data.count : 0,
|
|
135
|
-
min: data.min === Infinity ? 0 : data.min,
|
|
136
|
-
max: data.max === -Infinity ? 0 : data.max,
|
|
137
|
-
buckets: bucketObj
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
name: metric.name,
|
|
144
|
-
type: 'histogram',
|
|
145
|
-
help: metric.key.help,
|
|
146
|
-
values
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Types
|
|
151
|
-
|
|
152
|
-
export interface JsonMetricValue {
|
|
153
|
-
labels: Record<string, string>;
|
|
154
|
-
value: number | JsonHistogramValue;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export interface JsonHistogramValue {
|
|
158
|
-
count: number;
|
|
159
|
-
sum: number;
|
|
160
|
-
avg: number;
|
|
161
|
-
min: number;
|
|
162
|
-
max: number;
|
|
163
|
-
buckets: Record<string, number>;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export interface JsonMetric {
|
|
167
|
-
name: string;
|
|
168
|
-
type: string;
|
|
169
|
-
help: string;
|
|
170
|
-
values: JsonMetricValue[];
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export interface JsonMetricsOutput {
|
|
174
|
-
timestamp: number;
|
|
175
|
-
metrics: Record<string, JsonMetric>;
|
|
176
|
-
}
|
|
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 {GGMetricsExporter, ExporterConfig} from "./GGMetricsExporter.js";
|
|
7
|
+
|
|
8
|
+
export type JsonMetricConverter = (metric: GGMetric<any>, exporter: GGJsonMetricsExporter) => JsonMetric;
|
|
9
|
+
|
|
10
|
+
export class GGJsonMetricsExporter extends GGMetricsExporter<JsonMetricsOutput> {
|
|
11
|
+
|
|
12
|
+
// Static map for extensibility - register converters for new metric types
|
|
13
|
+
private static converters = new Map<Function, JsonMetricConverter>();
|
|
14
|
+
|
|
15
|
+
static {
|
|
16
|
+
GGJsonMetricsExporter.registerConverter(GGCounter, convertCounter);
|
|
17
|
+
GGJsonMetricsExporter.registerConverter(GGGauge, convertGauge);
|
|
18
|
+
GGJsonMetricsExporter.registerConverter(GGLazyGauge, convertLazyGauge);
|
|
19
|
+
GGJsonMetricsExporter.registerConverter(GGHistogram, convertHistogram);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Register a converter for a custom metric type.
|
|
24
|
+
*/
|
|
25
|
+
static registerConverter(metricClass: Function, converter: JsonMetricConverter): void {
|
|
26
|
+
GGJsonMetricsExporter.converters.set(metricClass, converter);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
constructor(config: ExporterConfig = {}) {
|
|
30
|
+
super(config);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getMetrics(): JsonMetricsOutput {
|
|
34
|
+
const output: JsonMetricsOutput = {
|
|
35
|
+
timestamp: Date.now(),
|
|
36
|
+
metrics: {}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
for (const metric of this.getFilteredMetrics()) {
|
|
40
|
+
output.metrics[metric.name] = this.convertMetric(metric);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return output;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private convertMetric(metric: GGMetric<any>): JsonMetric {
|
|
47
|
+
const converter = GGJsonMetricsExporter.converters.get(metric.constructor);
|
|
48
|
+
if (!converter) {
|
|
49
|
+
throw new Error(`No converter registered for metric type: ${metric.constructor.name}`);
|
|
50
|
+
}
|
|
51
|
+
return converter(metric, this);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parse a label key string into a labels object.
|
|
56
|
+
* Exposed for use by converters.
|
|
57
|
+
*/
|
|
58
|
+
parseLabels(labelKey: string): Record<string, string> {
|
|
59
|
+
if (!labelKey) return {};
|
|
60
|
+
const labels: Record<string, string> = {};
|
|
61
|
+
const parts = labelKey.split(',');
|
|
62
|
+
for (const part of parts) {
|
|
63
|
+
const [key, val] = part.split('=');
|
|
64
|
+
if (key && val !== undefined) {
|
|
65
|
+
labels[key] = val;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return labels;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Built-in converters
|
|
73
|
+
|
|
74
|
+
function convertCounter(metric: GGCounter<any>, exporter: GGJsonMetricsExporter): JsonMetric {
|
|
75
|
+
const values: JsonMetricValue[] = [];
|
|
76
|
+
for (const [labelKey, value] of metric.getValues()) {
|
|
77
|
+
values.push({
|
|
78
|
+
labels: exporter.parseLabels(labelKey),
|
|
79
|
+
value
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
name: metric.name,
|
|
84
|
+
type: 'counter',
|
|
85
|
+
help: metric.key.help,
|
|
86
|
+
values
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function convertGauge(metric: GGGauge<any>, exporter: GGJsonMetricsExporter): JsonMetric {
|
|
91
|
+
const values: JsonMetricValue[] = [];
|
|
92
|
+
for (const [labelKey, value] of metric.getValues()) {
|
|
93
|
+
values.push({
|
|
94
|
+
labels: exporter.parseLabels(labelKey),
|
|
95
|
+
value
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
name: metric.name,
|
|
100
|
+
type: 'gauge',
|
|
101
|
+
help: metric.key.help,
|
|
102
|
+
values
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function convertLazyGauge(metric: GGLazyGauge, exporter: GGJsonMetricsExporter): JsonMetric {
|
|
107
|
+
// Lazy gauge computes value on read - no labels
|
|
108
|
+
return {
|
|
109
|
+
name: metric.name,
|
|
110
|
+
type: 'gauge',
|
|
111
|
+
help: metric.key.help,
|
|
112
|
+
values: [{
|
|
113
|
+
labels: {},
|
|
114
|
+
value: metric.getValue()
|
|
115
|
+
}]
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function convertHistogram(metric: GGHistogram<any>, exporter: GGJsonMetricsExporter): JsonMetric {
|
|
120
|
+
const values: JsonMetricValue[] = [];
|
|
121
|
+
const buckets = metric.getBuckets();
|
|
122
|
+
|
|
123
|
+
for (const [labelKey, data] of metric.getValues() as Map<string, HistogramData>) {
|
|
124
|
+
const bucketObj: Record<string, number> = {};
|
|
125
|
+
for (let i = 0; i < buckets.length; i++) {
|
|
126
|
+
bucketObj[String(buckets[i])] = data.values[i] ?? 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
values.push({
|
|
130
|
+
labels: exporter.parseLabels(labelKey),
|
|
131
|
+
value: {
|
|
132
|
+
count: data.count,
|
|
133
|
+
sum: data.sum,
|
|
134
|
+
avg: data.count > 0 ? data.sum / data.count : 0,
|
|
135
|
+
min: data.min === Infinity ? 0 : data.min,
|
|
136
|
+
max: data.max === -Infinity ? 0 : data.max,
|
|
137
|
+
buckets: bucketObj
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
name: metric.name,
|
|
144
|
+
type: 'histogram',
|
|
145
|
+
help: metric.key.help,
|
|
146
|
+
values
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Types
|
|
151
|
+
|
|
152
|
+
export interface JsonMetricValue {
|
|
153
|
+
labels: Record<string, string>;
|
|
154
|
+
value: number | JsonHistogramValue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface JsonHistogramValue {
|
|
158
|
+
count: number;
|
|
159
|
+
sum: number;
|
|
160
|
+
avg: number;
|
|
161
|
+
min: number;
|
|
162
|
+
max: number;
|
|
163
|
+
buckets: Record<string, number>;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface JsonMetric {
|
|
167
|
+
name: string;
|
|
168
|
+
type: string;
|
|
169
|
+
help: string;
|
|
170
|
+
values: JsonMetricValue[];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface JsonMetricsOutput {
|
|
174
|
+
timestamp: number;
|
|
175
|
+
metrics: Record<string, JsonMetric>;
|
|
176
|
+
}
|
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
import {GGMetricsStore} from "../GGMetricsStore.js";
|
|
2
|
-
import {GG_METRICS} from "../GGMetricsLoader.js";
|
|
3
|
-
import {GGMetric} from "../GGMetric.js";
|
|
4
|
-
import {GGMetricKey} from "../GGMetricKey.js";
|
|
5
|
-
|
|
6
|
-
export interface ExporterConfig {
|
|
7
|
-
store?: GGMetricsStore;
|
|
8
|
-
include?: unknown[];
|
|
9
|
-
exclude?: unknown[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Abstract base class for metrics exporters.
|
|
14
|
-
* Handles config parsing, metric filtering, and key discovery.
|
|
15
|
-
*/
|
|
16
|
-
export abstract class GGMetricsExporter<TOutput> {
|
|
17
|
-
protected readonly store: GGMetricsStore;
|
|
18
|
-
private readonly includeKeys?: Set<GGMetricKey<any>>;
|
|
19
|
-
private readonly excludeKeys?: Set<GGMetricKey<any>>;
|
|
20
|
-
|
|
21
|
-
constructor(config: ExporterConfig = {}) {
|
|
22
|
-
this.store = config.store ?? GG_METRICS.get();
|
|
23
|
-
this.includeKeys = config.include ? this.discoverKeys(config.include) : undefined;
|
|
24
|
-
this.excludeKeys = config.exclude ? this.discoverKeys(config.exclude) : undefined;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Discover all GGMetricKey instances from an array of objects.
|
|
29
|
-
* Objects can be individual keys, or nested structures containing keys.
|
|
30
|
-
*/
|
|
31
|
-
private discoverKeys(objects: unknown[]): Set<GGMetricKey<any>> {
|
|
32
|
-
const keys = new Set<GGMetricKey<any>>();
|
|
33
|
-
for (const obj of objects) {
|
|
34
|
-
this.discoverKeysRecursive(obj, keys);
|
|
35
|
-
}
|
|
36
|
-
return keys;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private discoverKeysRecursive(obj: unknown, keys: Set<GGMetricKey<any>>): void {
|
|
40
|
-
if (obj instanceof GGMetricKey) {
|
|
41
|
-
keys.add(obj);
|
|
42
|
-
} else if (obj && typeof obj === 'object') {
|
|
43
|
-
for (const value of Object.values(obj)) {
|
|
44
|
-
this.discoverKeysRecursive(value, keys);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Check if a metric should be included in the export.
|
|
51
|
-
* Exclude takes precedence over include.
|
|
52
|
-
*/
|
|
53
|
-
protected shouldIncludeMetric(metric: GGMetric<any>): boolean {
|
|
54
|
-
// Exclude takes precedence
|
|
55
|
-
if (this.excludeKeys?.has(metric.key)) {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
// If include is set, only include those
|
|
59
|
-
if (this.includeKeys) {
|
|
60
|
-
return this.includeKeys.has(metric.key);
|
|
61
|
-
}
|
|
62
|
-
// Default: include all
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Get all metrics that pass the include/exclude filters.
|
|
68
|
-
*/
|
|
69
|
-
protected* getFilteredMetrics(): Iterable<GGMetric<any>> {
|
|
70
|
-
for (const metric of this.store.getAllMetrics()) {
|
|
71
|
-
if (this.shouldIncludeMetric(metric)) {
|
|
72
|
-
yield metric;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Export metrics to the output format.
|
|
79
|
-
*/
|
|
80
|
-
abstract getMetrics(): TOutput;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Export metrics as a JSON string.
|
|
84
|
-
*/
|
|
85
|
-
getMetricsString(): string {
|
|
86
|
-
return JSON.stringify(this.getMetrics(), null, 2);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
1
|
+
import {GGMetricsStore} from "../GGMetricsStore.js";
|
|
2
|
+
import {GG_METRICS} from "../GGMetricsLoader.js";
|
|
3
|
+
import {GGMetric} from "../GGMetric.js";
|
|
4
|
+
import {GGMetricKey} from "../GGMetricKey.js";
|
|
5
|
+
|
|
6
|
+
export interface ExporterConfig {
|
|
7
|
+
store?: GGMetricsStore;
|
|
8
|
+
include?: unknown[];
|
|
9
|
+
exclude?: unknown[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Abstract base class for metrics exporters.
|
|
14
|
+
* Handles config parsing, metric filtering, and key discovery.
|
|
15
|
+
*/
|
|
16
|
+
export abstract class GGMetricsExporter<TOutput> {
|
|
17
|
+
protected readonly store: GGMetricsStore;
|
|
18
|
+
private readonly includeKeys?: Set<GGMetricKey<any>>;
|
|
19
|
+
private readonly excludeKeys?: Set<GGMetricKey<any>>;
|
|
20
|
+
|
|
21
|
+
constructor(config: ExporterConfig = {}) {
|
|
22
|
+
this.store = config.store ?? GG_METRICS.get();
|
|
23
|
+
this.includeKeys = config.include ? this.discoverKeys(config.include) : undefined;
|
|
24
|
+
this.excludeKeys = config.exclude ? this.discoverKeys(config.exclude) : undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Discover all GGMetricKey instances from an array of objects.
|
|
29
|
+
* Objects can be individual keys, or nested structures containing keys.
|
|
30
|
+
*/
|
|
31
|
+
private discoverKeys(objects: unknown[]): Set<GGMetricKey<any>> {
|
|
32
|
+
const keys = new Set<GGMetricKey<any>>();
|
|
33
|
+
for (const obj of objects) {
|
|
34
|
+
this.discoverKeysRecursive(obj, keys);
|
|
35
|
+
}
|
|
36
|
+
return keys;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private discoverKeysRecursive(obj: unknown, keys: Set<GGMetricKey<any>>): void {
|
|
40
|
+
if (obj instanceof GGMetricKey) {
|
|
41
|
+
keys.add(obj);
|
|
42
|
+
} else if (obj && typeof obj === 'object') {
|
|
43
|
+
for (const value of Object.values(obj)) {
|
|
44
|
+
this.discoverKeysRecursive(value, keys);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if a metric should be included in the export.
|
|
51
|
+
* Exclude takes precedence over include.
|
|
52
|
+
*/
|
|
53
|
+
protected shouldIncludeMetric(metric: GGMetric<any>): boolean {
|
|
54
|
+
// Exclude takes precedence
|
|
55
|
+
if (this.excludeKeys?.has(metric.key)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
// If include is set, only include those
|
|
59
|
+
if (this.includeKeys) {
|
|
60
|
+
return this.includeKeys.has(metric.key);
|
|
61
|
+
}
|
|
62
|
+
// Default: include all
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get all metrics that pass the include/exclude filters.
|
|
68
|
+
*/
|
|
69
|
+
protected* getFilteredMetrics(): Iterable<GGMetric<any>> {
|
|
70
|
+
for (const metric of this.store.getAllMetrics()) {
|
|
71
|
+
if (this.shouldIncludeMetric(metric)) {
|
|
72
|
+
yield metric;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Export metrics to the output format.
|
|
79
|
+
*/
|
|
80
|
+
abstract getMetrics(): TOutput;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Export metrics as a JSON string.
|
|
84
|
+
*/
|
|
85
|
+
getMetricsString(): string {
|
|
86
|
+
return JSON.stringify(this.getMetrics(), null, 2);
|
|
87
|
+
}
|
|
88
|
+
}
|