@aztec-labs/telemetry-client 6.0.0-nightly.20260829
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/dest/attributes.d.ts +143 -0
- package/dest/attributes.d.ts.map +1 -0
- package/dest/attributes.js +91 -0
- package/dest/bench.d.ts +34 -0
- package/dest/bench.d.ts.map +1 -0
- package/dest/bench.js +115 -0
- package/dest/config.d.ts +21 -0
- package/dest/config.d.ts.map +1 -0
- package/dest/config.js +85 -0
- package/dest/index.d.ts +12 -0
- package/dest/index.d.ts.map +1 -0
- package/dest/index.js +11 -0
- package/dest/json_rpc_server_metrics.d.ts +27 -0
- package/dest/json_rpc_server_metrics.d.ts.map +1 -0
- package/dest/json_rpc_server_metrics.js +154 -0
- package/dest/l1_metrics.d.ts +17 -0
- package/dest/l1_metrics.d.ts.map +1 -0
- package/dest/l1_metrics.js +54 -0
- package/dest/lmdb_metrics.d.ts +18 -0
- package/dest/lmdb_metrics.d.ts.map +1 -0
- package/dest/lmdb_metrics.js +34 -0
- package/dest/metric-utils.d.ts +24 -0
- package/dest/metric-utils.d.ts.map +1 -0
- package/dest/metric-utils.js +59 -0
- package/dest/metrics.d.ts +338 -0
- package/dest/metrics.d.ts.map +1 -0
- package/dest/metrics.js +1702 -0
- package/dest/monitored_batch_span_processor.d.ts +33 -0
- package/dest/monitored_batch_span_processor.d.ts.map +1 -0
- package/dest/monitored_batch_span_processor.js +111 -0
- package/dest/nodejs_metrics_monitor.d.ts +19 -0
- package/dest/nodejs_metrics_monitor.d.ts.map +1 -0
- package/dest/nodejs_metrics_monitor.js +115 -0
- package/dest/noop.d.ts +19 -0
- package/dest/noop.d.ts.map +1 -0
- package/dest/noop.js +104 -0
- package/dest/otel.d.ts +43 -0
- package/dest/otel.d.ts.map +1 -0
- package/dest/otel.js +537 -0
- package/dest/otel_filter_metric_exporter.d.ts +20 -0
- package/dest/otel_filter_metric_exporter.d.ts.map +1 -0
- package/dest/otel_filter_metric_exporter.js +67 -0
- package/dest/otel_logger_provider.d.ts +4 -0
- package/dest/otel_logger_provider.d.ts.map +1 -0
- package/dest/otel_logger_provider.js +25 -0
- package/dest/otel_propagation.d.ts +6 -0
- package/dest/otel_propagation.d.ts.map +1 -0
- package/dest/otel_propagation.js +102 -0
- package/dest/otel_resource.d.ts +3 -0
- package/dest/otel_resource.d.ts.map +1 -0
- package/dest/otel_resource.js +53 -0
- package/dest/prom_otel_adapter.d.ts +176 -0
- package/dest/prom_otel_adapter.d.ts.map +1 -0
- package/dest/prom_otel_adapter.js +292 -0
- package/dest/start.d.ts +7 -0
- package/dest/start.d.ts.map +1 -0
- package/dest/start.js +25 -0
- package/dest/telemetry.d.ts +149 -0
- package/dest/telemetry.d.ts.map +1 -0
- package/dest/telemetry.js +139 -0
- package/dest/vendor/attributes.d.ts +6 -0
- package/dest/vendor/attributes.d.ts.map +1 -0
- package/dest/vendor/attributes.js +6 -0
- package/dest/vendor/otel-pino-stream.d.ts +40 -0
- package/dest/vendor/otel-pino-stream.d.ts.map +1 -0
- package/dest/vendor/otel-pino-stream.js +229 -0
- package/dest/with_tracer.d.ts +33 -0
- package/dest/with_tracer.d.ts.map +1 -0
- package/dest/with_tracer.js +32 -0
- package/dest/wrappers/fetch.d.ts +16 -0
- package/dest/wrappers/fetch.d.ts.map +1 -0
- package/dest/wrappers/fetch.js +42 -0
- package/dest/wrappers/index.d.ts +3 -0
- package/dest/wrappers/index.d.ts.map +1 -0
- package/dest/wrappers/index.js +2 -0
- package/dest/wrappers/json_rpc_server.d.ts +4 -0
- package/dest/wrappers/json_rpc_server.d.ts.map +1 -0
- package/dest/wrappers/json_rpc_server.js +16 -0
- package/package.json +95 -0
- package/src/attributes.ts +174 -0
- package/src/bench.ts +168 -0
- package/src/config.ts +135 -0
- package/src/index.ts +11 -0
- package/src/json_rpc_server_metrics.ts +150 -0
- package/src/l1_metrics.ts +64 -0
- package/src/lmdb_metrics.ts +45 -0
- package/src/metric-utils.ts +75 -0
- package/src/metrics.ts +1802 -0
- package/src/monitored_batch_span_processor.ts +147 -0
- package/src/nodejs_metrics_monitor.ts +135 -0
- package/src/noop.ts +152 -0
- package/src/otel.ts +482 -0
- package/src/otel_filter_metric_exporter.ts +80 -0
- package/src/otel_logger_provider.ts +31 -0
- package/src/otel_propagation.ts +107 -0
- package/src/otel_resource.ts +71 -0
- package/src/prom_otel_adapter.ts +450 -0
- package/src/start.ts +42 -0
- package/src/telemetry.ts +348 -0
- package/src/vendor/attributes.ts +6 -0
- package/src/vendor/otel-pino-stream.ts +279 -0
- package/src/with_tracer.ts +35 -0
- package/src/wrappers/fetch.ts +50 -0
- package/src/wrappers/index.ts +2 -0
- package/src/wrappers/json_rpc_server.ts +26 -0
package/src/config.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ConfigMappingsType,
|
|
3
|
+
booleanConfigHelper,
|
|
4
|
+
getConfigFromMappings,
|
|
5
|
+
numberConfigHelper,
|
|
6
|
+
} from '@aztec-labs/foundation/config';
|
|
7
|
+
|
|
8
|
+
export interface TelemetryClientConfig {
|
|
9
|
+
metricsCollectorUrl?: URL;
|
|
10
|
+
publicMetricsCollectorUrl?: URL;
|
|
11
|
+
publicIncludeMetrics: string[];
|
|
12
|
+
publicMetricsOptOut: boolean;
|
|
13
|
+
publicMetricsCollectFrom: string[];
|
|
14
|
+
tracesCollectorUrl?: URL;
|
|
15
|
+
logsCollectorUrl?: URL;
|
|
16
|
+
otelCollectIntervalMs: number;
|
|
17
|
+
otelExportTimeoutMs: number;
|
|
18
|
+
otelExcludeMetrics: string[];
|
|
19
|
+
otelIncludeMetrics: string[];
|
|
20
|
+
otelMinTraceDurationMs: number;
|
|
21
|
+
otelBspMaxQueueSize: number;
|
|
22
|
+
otelBspMaxExportBatchSize: number;
|
|
23
|
+
otelBspScheduleDelayMs: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const telemetryClientConfigMappings: ConfigMappingsType<TelemetryClientConfig> = {
|
|
27
|
+
metricsCollectorUrl: {
|
|
28
|
+
env: 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT',
|
|
29
|
+
description: 'The URL of the telemetry collector for metrics',
|
|
30
|
+
parseEnv: (val: string) => new URL(val),
|
|
31
|
+
},
|
|
32
|
+
tracesCollectorUrl: {
|
|
33
|
+
env: 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT',
|
|
34
|
+
description: 'The URL of the telemetry collector for traces',
|
|
35
|
+
parseEnv: (val: string) => new URL(val),
|
|
36
|
+
},
|
|
37
|
+
logsCollectorUrl: {
|
|
38
|
+
env: 'OTEL_EXPORTER_OTLP_LOGS_ENDPOINT',
|
|
39
|
+
description: 'The URL of the telemetry collector for logs',
|
|
40
|
+
parseEnv: (val: string) => new URL(val),
|
|
41
|
+
},
|
|
42
|
+
otelCollectIntervalMs: {
|
|
43
|
+
env: 'OTEL_COLLECT_INTERVAL_MS',
|
|
44
|
+
description: 'The interval at which to collect metrics',
|
|
45
|
+
...numberConfigHelper(60000),
|
|
46
|
+
},
|
|
47
|
+
otelExportTimeoutMs: {
|
|
48
|
+
env: 'OTEL_EXPORT_TIMEOUT_MS',
|
|
49
|
+
description: 'The timeout for exporting metrics',
|
|
50
|
+
...numberConfigHelper(30000),
|
|
51
|
+
},
|
|
52
|
+
otelExcludeMetrics: {
|
|
53
|
+
env: 'OTEL_EXCLUDE_METRICS',
|
|
54
|
+
description: 'A list of metric prefixes to exclude from export',
|
|
55
|
+
parseEnv: (val: string) =>
|
|
56
|
+
val
|
|
57
|
+
? val
|
|
58
|
+
.split(',')
|
|
59
|
+
.map(s => s.trim())
|
|
60
|
+
.filter(s => s.length > 0)
|
|
61
|
+
: [],
|
|
62
|
+
defaultValue: [],
|
|
63
|
+
},
|
|
64
|
+
otelMinTraceDurationMs: {
|
|
65
|
+
env: 'OTEL_MIN_TRACE_DURATION_MS',
|
|
66
|
+
description: 'The minimum successful trace duration to export in milliseconds. Set to 0 to export all traces.',
|
|
67
|
+
...numberConfigHelper(10),
|
|
68
|
+
},
|
|
69
|
+
otelBspMaxQueueSize: {
|
|
70
|
+
env: 'OTEL_BSP_MAX_QUEUE_SIZE',
|
|
71
|
+
description: 'The maximum number of completed spans to queue before export.',
|
|
72
|
+
...numberConfigHelper(65536),
|
|
73
|
+
},
|
|
74
|
+
otelBspMaxExportBatchSize: {
|
|
75
|
+
env: 'OTEL_BSP_MAX_EXPORT_BATCH_SIZE',
|
|
76
|
+
description: 'The maximum number of spans to send to the collector in a single export.',
|
|
77
|
+
...numberConfigHelper(4096),
|
|
78
|
+
},
|
|
79
|
+
otelBspScheduleDelayMs: {
|
|
80
|
+
env: 'OTEL_BSP_SCHEDULE_DELAY',
|
|
81
|
+
description: 'How long to wait before exporting a partially filled batch of spans, in milliseconds.',
|
|
82
|
+
...numberConfigHelper(5000),
|
|
83
|
+
},
|
|
84
|
+
otelIncludeMetrics: {
|
|
85
|
+
env: 'OTEL_INCLUDE_METRICS',
|
|
86
|
+
description: 'A list of metric prefixes to include in export (ignored if OTEL_EXCLUDE_METRICS is set)',
|
|
87
|
+
parseEnv: (val: string) =>
|
|
88
|
+
val
|
|
89
|
+
? val
|
|
90
|
+
.split(',')
|
|
91
|
+
.map(s => s.trim())
|
|
92
|
+
.filter(s => s.length > 0)
|
|
93
|
+
: [],
|
|
94
|
+
defaultValue: [],
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
publicMetricsCollectorUrl: {
|
|
98
|
+
env: 'PUBLIC_OTEL_EXPORTER_OTLP_METRICS_ENDPOINT',
|
|
99
|
+
description: 'A URL to publish a subset of metrics for public consumption',
|
|
100
|
+
parseEnv: (val: string) => new URL(val),
|
|
101
|
+
},
|
|
102
|
+
publicMetricsCollectFrom: {
|
|
103
|
+
env: 'PUBLIC_OTEL_COLLECT_FROM',
|
|
104
|
+
description: 'The role types to collect metrics from',
|
|
105
|
+
parseEnv: (val: string) =>
|
|
106
|
+
val
|
|
107
|
+
? val
|
|
108
|
+
.split(',')
|
|
109
|
+
.map(s => s.trim())
|
|
110
|
+
.filter(s => s.length > 0)
|
|
111
|
+
: [],
|
|
112
|
+
defaultValue: [],
|
|
113
|
+
},
|
|
114
|
+
publicIncludeMetrics: {
|
|
115
|
+
env: 'PUBLIC_OTEL_INCLUDE_METRICS',
|
|
116
|
+
description: 'A list of metric prefixes to publicly export',
|
|
117
|
+
parseEnv: (val: string) =>
|
|
118
|
+
val
|
|
119
|
+
? val
|
|
120
|
+
.split(',')
|
|
121
|
+
.map(s => s.trim())
|
|
122
|
+
.filter(s => s.length > 0)
|
|
123
|
+
: [],
|
|
124
|
+
defaultValue: [],
|
|
125
|
+
},
|
|
126
|
+
publicMetricsOptOut: {
|
|
127
|
+
env: 'PUBLIC_OTEL_OPT_OUT',
|
|
128
|
+
description: 'Whether to opt out of sharing optional telemetry',
|
|
129
|
+
...booleanConfigHelper(true),
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export function getConfigEnvVars(): TelemetryClientConfig {
|
|
134
|
+
return getConfigFromMappings<TelemetryClientConfig>(telemetryClientConfigMappings);
|
|
135
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './telemetry.js';
|
|
2
|
+
export * as Metrics from './metrics.js';
|
|
3
|
+
export * as Attributes from './attributes.js';
|
|
4
|
+
export * from './with_tracer.js';
|
|
5
|
+
export * from './prom_otel_adapter.js';
|
|
6
|
+
export * from './lmdb_metrics.js';
|
|
7
|
+
export * from './l1_metrics.js';
|
|
8
|
+
export * from './wrappers/index.js';
|
|
9
|
+
export * from './start.js';
|
|
10
|
+
export * from './otel_propagation.js';
|
|
11
|
+
export * from './json_rpc_server_metrics.js';
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Timer } from '@aztec-labs/foundation/timer';
|
|
2
|
+
import type Koa from 'koa';
|
|
3
|
+
|
|
4
|
+
import * as Attributes from './attributes.js';
|
|
5
|
+
import * as Metrics from './metrics.js';
|
|
6
|
+
import { getTelemetryClient } from './start.js';
|
|
7
|
+
import type { Histogram, MetricAttributesType, TelemetryClient, UpDownCounter } from './telemetry.js';
|
|
8
|
+
import { ATTR_JSONRPC_METHOD, ATTR_JSONRPC_SERVICE } from './vendor/attributes.js';
|
|
9
|
+
|
|
10
|
+
const BATCH_SIZE_BUCKETS = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000];
|
|
11
|
+
|
|
12
|
+
/** Fixed reasons for rejecting an RPC request before dispatching it to a registered handler. */
|
|
13
|
+
export type JsonRpcRejectionReason =
|
|
14
|
+
| 'unauthorized'
|
|
15
|
+
| 'parse_error'
|
|
16
|
+
| 'invalid_request'
|
|
17
|
+
| 'method_not_found'
|
|
18
|
+
| 'bad_request'
|
|
19
|
+
| 'internal_error';
|
|
20
|
+
|
|
21
|
+
/** Records bounded-cardinality metrics for registered JSON-RPC calls, rejected requests, and batches. */
|
|
22
|
+
export class JsonRpcServerMetrics {
|
|
23
|
+
private readonly requestCount: UpDownCounter;
|
|
24
|
+
private readonly requestDuration: Histogram;
|
|
25
|
+
private readonly requestValidationDuration: Histogram;
|
|
26
|
+
private readonly rejectedRequestCount: UpDownCounter;
|
|
27
|
+
private readonly batchCount: UpDownCounter;
|
|
28
|
+
private readonly batchDuration: Histogram;
|
|
29
|
+
private readonly batchSize: Histogram;
|
|
30
|
+
|
|
31
|
+
constructor(telemetry: TelemetryClient) {
|
|
32
|
+
const meter = telemetry.getMeter('JsonRpcServer');
|
|
33
|
+
this.requestCount = meter.createUpDownCounter(Metrics.JSON_RPC_SERVER_REQUEST_COUNT);
|
|
34
|
+
this.requestDuration = meter.createHistogram(Metrics.JSON_RPC_SERVER_REQUEST_DURATION);
|
|
35
|
+
this.requestValidationDuration = meter.createHistogram(Metrics.JSON_RPC_SERVER_REQUEST_VALIDATION_DURATION);
|
|
36
|
+
this.rejectedRequestCount = meter.createUpDownCounter(Metrics.JSON_RPC_SERVER_REJECTED_REQUEST_COUNT);
|
|
37
|
+
this.batchCount = meter.createUpDownCounter(Metrics.JSON_RPC_SERVER_BATCH_COUNT);
|
|
38
|
+
this.batchDuration = meter.createHistogram(Metrics.JSON_RPC_SERVER_BATCH_DURATION);
|
|
39
|
+
this.batchSize = meter.createHistogram(Metrics.JSON_RPC_SERVER_BATCH_SIZE, {
|
|
40
|
+
advice: { explicitBucketBoundaries: BATCH_SIZE_BUCKETS },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Records the outcome and handler duration of a registered RPC method. */
|
|
45
|
+
public recordRequest(fullMethod: string, durationMs: number, ok: boolean): void {
|
|
46
|
+
const [service, method] = splitJsonRpcMethod(fullMethod);
|
|
47
|
+
const attributes: MetricAttributesType = {
|
|
48
|
+
...(service === undefined ? {} : { [ATTR_JSONRPC_SERVICE]: service }),
|
|
49
|
+
[ATTR_JSONRPC_METHOD]: method,
|
|
50
|
+
[Attributes.OK]: ok,
|
|
51
|
+
};
|
|
52
|
+
this.requestCount.add(1, attributes);
|
|
53
|
+
this.requestDuration.record(durationMs, attributes);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Records a pre-dispatch rejection using a fixed reason. */
|
|
57
|
+
public recordRejectedRequest(reason: JsonRpcRejectionReason): void {
|
|
58
|
+
this.rejectedRequestCount.add(1, { [Attributes.JSON_RPC_REJECTION_REASON]: reason });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Records the duration and outcome of validating a registered RPC method's parameters. */
|
|
62
|
+
public recordRequestValidation(fullMethod: string, durationMs: number, ok: boolean): void {
|
|
63
|
+
const [service, method] = splitJsonRpcMethod(fullMethod);
|
|
64
|
+
this.requestValidationDuration.record(durationMs, {
|
|
65
|
+
...(service === undefined ? {} : { [ATTR_JSONRPC_SERVICE]: service }),
|
|
66
|
+
[ATTR_JSONRPC_METHOD]: method,
|
|
67
|
+
[Attributes.OK]: ok,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Records the outcome, processing duration, and number of calls in a batch envelope. */
|
|
72
|
+
public recordBatch(size: number, durationMs: number, ok: boolean): void {
|
|
73
|
+
const attributes = { [Attributes.OK]: ok };
|
|
74
|
+
this.batchCount.add(1, attributes);
|
|
75
|
+
this.batchDuration.record(durationMs, attributes);
|
|
76
|
+
this.batchSize.record(size, attributes);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let metricsOwner: TelemetryClient | undefined;
|
|
81
|
+
let metrics: JsonRpcServerMetrics | undefined;
|
|
82
|
+
|
|
83
|
+
export function getJsonRpcServerMetrics(): JsonRpcServerMetrics {
|
|
84
|
+
const telemetry = getTelemetryClient();
|
|
85
|
+
if (metricsOwner !== telemetry) {
|
|
86
|
+
metricsOwner = telemetry;
|
|
87
|
+
metrics = new JsonRpcServerMetrics(telemetry);
|
|
88
|
+
}
|
|
89
|
+
return metrics!;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function getOtelJsonRpcServerMetricsMiddleware(
|
|
93
|
+
metricsProvider: () => Pick<JsonRpcServerMetrics, 'recordBatch' | 'recordRejectedRequest'> = getJsonRpcServerMetrics,
|
|
94
|
+
): (ctx: Koa.Context, next: () => Promise<void>) => Promise<void> {
|
|
95
|
+
return async function otelJsonRpcServerMetrics(ctx, next) {
|
|
96
|
+
const timer = new Timer();
|
|
97
|
+
await next();
|
|
98
|
+
|
|
99
|
+
const requestBody = (ctx.request as { body?: unknown }).body;
|
|
100
|
+
if (Array.isArray(requestBody)) {
|
|
101
|
+
metricsProvider().recordBatch(requestBody.length, timer.ms(), Array.isArray(ctx.body));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (const reason of getRejectionReasons(ctx.status, ctx.body)) {
|
|
105
|
+
metricsProvider().recordRejectedRequest(reason);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function splitJsonRpcMethod(fullMethod: string): [service: string | undefined, method: string] {
|
|
111
|
+
const separator = fullMethod.indexOf('_');
|
|
112
|
+
return separator === -1 ? [undefined, fullMethod] : [fullMethod.slice(0, separator), fullMethod.slice(separator + 1)];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getRejectionReasons(status: number, response: unknown): JsonRpcRejectionReason[] {
|
|
116
|
+
if (status === 401) {
|
|
117
|
+
return ['unauthorized'];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const responses = Array.isArray(response) ? response : [response];
|
|
121
|
+
return responses.flatMap(item => {
|
|
122
|
+
const code = getErrorCode(item);
|
|
123
|
+
if (code === -32700) {
|
|
124
|
+
return ['parse_error'];
|
|
125
|
+
}
|
|
126
|
+
if (code === -32601) {
|
|
127
|
+
return ['method_not_found'];
|
|
128
|
+
}
|
|
129
|
+
if (code === -32600) {
|
|
130
|
+
return ['invalid_request'];
|
|
131
|
+
}
|
|
132
|
+
if (code === -32603) {
|
|
133
|
+
return ['internal_error'];
|
|
134
|
+
}
|
|
135
|
+
if (code === -32000) {
|
|
136
|
+
return ['bad_request'];
|
|
137
|
+
}
|
|
138
|
+
return [];
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getErrorCode(response: unknown): number | undefined {
|
|
143
|
+
if (!response || typeof response !== 'object' || !('error' in response)) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
const error = response.error;
|
|
147
|
+
return error && typeof error === 'object' && 'code' in error && typeof error.code === 'number'
|
|
148
|
+
? error.code
|
|
149
|
+
: undefined;
|
|
150
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { EthAddress } from '@aztec-labs/foundation/eth-address';
|
|
2
|
+
import { type Chain, type FallbackTransport, type Hex, type HttpTransport, type PublicClient, formatEther } from 'viem';
|
|
3
|
+
|
|
4
|
+
import { L1_SENDER } from './attributes.js';
|
|
5
|
+
import { L1_BALANCE_ETH, L1_BLOB_BASE_FEE_WEI, L1_BLOCK_HEIGHT, L1_GAS_PRICE_WEI } from './metrics.js';
|
|
6
|
+
import type { BatchObservableResult, Meter, ObservableGauge } from './telemetry.js';
|
|
7
|
+
|
|
8
|
+
export class L1Metrics {
|
|
9
|
+
private l1BlockHeight: ObservableGauge;
|
|
10
|
+
private l1BalanceEth: ObservableGauge;
|
|
11
|
+
private gasPriceWei: ObservableGauge;
|
|
12
|
+
private blobBaseFeeWei: ObservableGauge;
|
|
13
|
+
private addresses: Hex[];
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private meter: Meter,
|
|
17
|
+
private client: PublicClient<FallbackTransport<HttpTransport[]>, Chain>,
|
|
18
|
+
addresses: EthAddress[],
|
|
19
|
+
) {
|
|
20
|
+
this.l1BlockHeight = meter.createObservableGauge(L1_BLOCK_HEIGHT);
|
|
21
|
+
this.l1BalanceEth = meter.createObservableGauge(L1_BALANCE_ETH);
|
|
22
|
+
this.gasPriceWei = meter.createObservableGauge(L1_GAS_PRICE_WEI);
|
|
23
|
+
this.blobBaseFeeWei = meter.createObservableGauge(L1_BLOB_BASE_FEE_WEI);
|
|
24
|
+
|
|
25
|
+
this.addresses = addresses.map(addr => addr.toString());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
start() {
|
|
29
|
+
this.meter.addBatchObservableCallback(this.observe, [this.l1BalanceEth, this.l1BlockHeight]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
stop() {
|
|
33
|
+
this.meter.removeBatchObservableCallback(this.observe, [this.l1BalanceEth, this.l1BlockHeight]);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private observe = async (observer: BatchObservableResult) => {
|
|
37
|
+
const blockNumber = await this.client.getBlockNumber();
|
|
38
|
+
const ethBalances = await Promise.all(
|
|
39
|
+
this.addresses.map(address =>
|
|
40
|
+
this.client.getBalance({
|
|
41
|
+
address,
|
|
42
|
+
blockNumber,
|
|
43
|
+
}),
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const gasPrice = await this.client.getGasPrice();
|
|
48
|
+
const blobFee = await this.client.getBlobBaseFee();
|
|
49
|
+
|
|
50
|
+
observer.observe(this.l1BlockHeight, Number(blockNumber));
|
|
51
|
+
observer.observe(this.gasPriceWei, Number(gasPrice));
|
|
52
|
+
observer.observe(this.blobBaseFeeWei, Number(blobFee));
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < ethBalances.length; i++) {
|
|
55
|
+
const wei = ethBalances[i];
|
|
56
|
+
const address = this.addresses[i];
|
|
57
|
+
const eth = parseFloat(formatEther(wei, 'wei'));
|
|
58
|
+
|
|
59
|
+
observer.observe(this.l1BalanceEth, eth, {
|
|
60
|
+
[L1_SENDER]: address,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as Metrics from './metrics.js';
|
|
2
|
+
import type { AttributesType, BatchObservableResult, Meter, ObservableGauge } from './telemetry.js';
|
|
3
|
+
|
|
4
|
+
export type LmdbStatsCallback = () => Promise<{
|
|
5
|
+
mappingSize: number;
|
|
6
|
+
physicalFileSize: number;
|
|
7
|
+
numItems: number;
|
|
8
|
+
actualSize: number;
|
|
9
|
+
}>;
|
|
10
|
+
|
|
11
|
+
export class LmdbMetrics {
|
|
12
|
+
private dbMapSize: ObservableGauge;
|
|
13
|
+
private dbPhysicalFileSize: ObservableGauge;
|
|
14
|
+
private dbUsedSize: ObservableGauge;
|
|
15
|
+
private dbNumItems: ObservableGauge;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
meter: Meter,
|
|
19
|
+
private attributes?: AttributesType,
|
|
20
|
+
private getStats?: LmdbStatsCallback,
|
|
21
|
+
) {
|
|
22
|
+
this.dbMapSize = meter.createObservableGauge(Metrics.DB_MAP_SIZE);
|
|
23
|
+
this.dbPhysicalFileSize = meter.createObservableGauge(Metrics.DB_PHYSICAL_FILE_SIZE);
|
|
24
|
+
this.dbUsedSize = meter.createObservableGauge(Metrics.DB_USED_SIZE);
|
|
25
|
+
this.dbNumItems = meter.createObservableGauge(Metrics.DB_NUM_ITEMS);
|
|
26
|
+
|
|
27
|
+
meter.addBatchObservableCallback(this.recordDBMetrics, [
|
|
28
|
+
this.dbMapSize,
|
|
29
|
+
this.dbPhysicalFileSize,
|
|
30
|
+
this.dbUsedSize,
|
|
31
|
+
this.dbNumItems,
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private recordDBMetrics = async (observable: BatchObservableResult) => {
|
|
36
|
+
if (!this.getStats) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const metrics = await this.getStats();
|
|
40
|
+
observable.observe(this.dbMapSize, metrics.mappingSize, this.attributes);
|
|
41
|
+
observable.observe(this.dbPhysicalFileSize, metrics.physicalFileSize, this.attributes);
|
|
42
|
+
observable.observe(this.dbNumItems, metrics.numItems, this.attributes);
|
|
43
|
+
observable.observe(this.dbUsedSize, metrics.actualSize, this.attributes);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { AttributeValue, MetricOptions } from '@opentelemetry/api';
|
|
2
|
+
|
|
3
|
+
import type { MetricDefinition } from './metrics.js';
|
|
4
|
+
import type { AllowedAttributeNames, Meter, MetricAttributesType, UpDownCounter } from './telemetry.js';
|
|
5
|
+
|
|
6
|
+
/** Extracts OpenTelemetry MetricOptions from a MetricDefinition */
|
|
7
|
+
export function toMetricOptions(def: MetricDefinition): MetricOptions {
|
|
8
|
+
return {
|
|
9
|
+
description: def.description,
|
|
10
|
+
unit: def.unit,
|
|
11
|
+
valueType: def.valueType,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A mapping of attribute keys to their possible values */
|
|
16
|
+
export type AttributeValuesMap = Partial<Record<AllowedAttributeNames, AttributeValue[]>>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Expands an attribute values map into all combinations of attribute objects.
|
|
20
|
+
* Example: { status: ['ok', 'fail'], type: ['a', 'b'] } =>
|
|
21
|
+
* [{ status: 'ok', type: 'a' }, { status: 'ok', type: 'b' }, { status: 'fail', type: 'a' }, { status: 'fail', type: 'b' }]
|
|
22
|
+
*/
|
|
23
|
+
export function expandAttributeCombinations(attrMap: AttributeValuesMap): MetricAttributesType[] {
|
|
24
|
+
const keys = Object.keys(attrMap) as AllowedAttributeNames[];
|
|
25
|
+
if (keys.length === 0) {
|
|
26
|
+
return [{}];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const result: MetricAttributesType[] = [];
|
|
30
|
+
|
|
31
|
+
function generate(index: number, current: MetricAttributesType) {
|
|
32
|
+
if (index === keys.length) {
|
|
33
|
+
result.push({ ...current });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const key = keys[index];
|
|
37
|
+
for (const value of attrMap[key]!) {
|
|
38
|
+
current[key] = value;
|
|
39
|
+
generate(index + 1, current);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
generate(0, {});
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates an UpDownCounter and initializes it to 0.
|
|
49
|
+
* @param meter - The meter to create the counter on
|
|
50
|
+
* @param metric - The metric definition
|
|
51
|
+
* @param attributes - Optional attributes for initialization. Can be:
|
|
52
|
+
* - AttributeValuesMap: mapping of attribute keys to arrays of possible values (generates cartesian product)
|
|
53
|
+
* - MetricAttributesType[]: explicit array of attribute objects
|
|
54
|
+
* - undefined: initializes without attributes
|
|
55
|
+
*/
|
|
56
|
+
export function createUpDownCounterWithDefault(
|
|
57
|
+
meter: Meter,
|
|
58
|
+
metric: MetricDefinition,
|
|
59
|
+
attributes: AttributeValuesMap | MetricAttributesType[] | undefined = undefined,
|
|
60
|
+
): UpDownCounter {
|
|
61
|
+
const counter = meter.createUpDownCounter(metric);
|
|
62
|
+
if (attributes === undefined) {
|
|
63
|
+
counter.add(0);
|
|
64
|
+
} else if (Array.isArray(attributes)) {
|
|
65
|
+
for (const attrs of attributes) {
|
|
66
|
+
counter.add(0, attrs);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
const combinations = expandAttributeCombinations(attributes);
|
|
70
|
+
for (const attrs of combinations) {
|
|
71
|
+
counter.add(0, attrs);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return counter;
|
|
75
|
+
}
|