@aztec/telemetry-client 0.0.0-test.1 → 0.0.1-commit.0208eb9
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 +33 -9
- package/dest/attributes.d.ts.map +1 -1
- package/dest/attributes.js +22 -8
- package/dest/bench.d.ts +6 -1
- package/dest/bench.d.ts.map +1 -1
- package/dest/bench.js +31 -14
- package/dest/config.d.ts +7 -2
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +29 -1
- package/dest/index.d.ts +2 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -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 +5 -3
- package/dest/lmdb_metrics.d.ts.map +1 -1
- package/dest/lmdb_metrics.js +7 -15
- 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 +252 -122
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +1265 -120
- 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 +9 -4
- package/dest/noop.d.ts.map +1 -1
- package/dest/noop.js +34 -1
- package/dest/otel.d.ts +13 -7
- package/dest/otel.d.ts.map +1 -1
- package/dest/otel.js +142 -21
- package/dest/otel_filter_metric_exporter.d.ts +12 -4
- package/dest/otel_filter_metric_exporter.d.ts.map +1 -1
- package/dest/otel_filter_metric_exporter.js +38 -4
- package/dest/otel_logger_provider.d.ts +1 -1
- package/dest/otel_propagation.d.ts +1 -1
- package/dest/otel_resource.d.ts +1 -1
- package/dest/otel_resource.d.ts.map +1 -1
- package/dest/otel_resource.js +43 -2
- package/dest/prom_otel_adapter.d.ts +61 -12
- package/dest/prom_otel_adapter.d.ts.map +1 -1
- package/dest/prom_otel_adapter.js +157 -48
- package/dest/start.d.ts +3 -2
- package/dest/start.d.ts.map +1 -1
- package/dest/start.js +8 -7
- package/dest/telemetry.d.ts +63 -34
- package/dest/telemetry.d.ts.map +1 -1
- package/dest/telemetry.js +47 -24
- package/dest/vendor/attributes.d.ts +1 -1
- package/dest/vendor/otel-pino-stream.d.ts +1 -2
- package/dest/vendor/otel-pino-stream.d.ts.map +1 -1
- package/dest/vendor/otel-pino-stream.js +2 -2
- package/dest/with_tracer.d.ts +1 -1
- package/dest/with_tracer.d.ts.map +1 -1
- package/dest/wrappers/fetch.d.ts +2 -2
- package/dest/wrappers/fetch.d.ts.map +1 -1
- package/dest/wrappers/fetch.js +7 -5
- package/dest/wrappers/index.d.ts +1 -1
- package/dest/wrappers/json_rpc_server.d.ts +2 -2
- package/dest/wrappers/json_rpc_server.d.ts.map +1 -1
- package/dest/wrappers/l2_block_stream.d.ts +4 -3
- package/dest/wrappers/l2_block_stream.d.ts.map +1 -1
- package/dest/wrappers/l2_block_stream.js +385 -11
- package/package.json +21 -15
- package/src/attributes.ts +42 -11
- package/src/bench.ts +37 -15
- package/src/config.ts +54 -2
- package/src/index.ts +1 -0
- package/src/l1_metrics.ts +65 -0
- package/src/lmdb_metrics.ts +24 -24
- package/src/metric-utils.ts +75 -0
- package/src/metrics.ts +1340 -145
- package/src/nodejs_metrics_monitor.ts +135 -0
- package/src/noop.ts +65 -4
- package/src/otel.ts +155 -26
- package/src/otel_filter_metric_exporter.ts +47 -5
- package/src/otel_resource.ts +57 -2
- package/src/prom_otel_adapter.ts +195 -70
- package/src/start.ts +12 -8
- package/src/telemetry.ts +153 -76
- package/src/vendor/otel-pino-stream.ts +1 -4
- package/src/wrappers/fetch.ts +24 -31
- package/src/wrappers/json_rpc_server.ts +1 -1
- package/src/wrappers/l2_block_stream.ts +6 -2
- package/dest/event_loop_monitor.d.ts +0 -18
- package/dest/event_loop_monitor.d.ts.map +0 -1
- package/dest/event_loop_monitor.js +0 -93
- package/src/event_loop_monitor.ts +0 -119
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { Observable } from '@opentelemetry/api';
|
|
2
|
+
import { type EventLoopUtilization, type IntervalHistogram, monitorEventLoopDelay, performance } from 'node:perf_hooks';
|
|
3
|
+
|
|
4
|
+
import * as Attributes from './attributes.js';
|
|
5
|
+
import { createUpDownCounterWithDefault } from './metric-utils.js';
|
|
6
|
+
import * as Metrics from './metrics.js';
|
|
7
|
+
import type { BatchObservableResult, Meter, ObservableGauge, UpDownCounter } from './telemetry.js';
|
|
8
|
+
|
|
9
|
+
/** Monitors Node.js runtime metrics */
|
|
10
|
+
export class NodejsMetricsMonitor {
|
|
11
|
+
private eventLoopDelayGauges: {
|
|
12
|
+
min: ObservableGauge;
|
|
13
|
+
max: ObservableGauge;
|
|
14
|
+
mean: ObservableGauge;
|
|
15
|
+
stddev: ObservableGauge;
|
|
16
|
+
p50: ObservableGauge;
|
|
17
|
+
p90: ObservableGauge;
|
|
18
|
+
p99: ObservableGauge;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// skip `rss` because that's already tracked by @opentelemetry/host-metrics
|
|
22
|
+
// description of each field here https://nodejs.org/api/process.html#processmemoryusage
|
|
23
|
+
private memoryGauges: Record<Exclude<keyof NodeJS.MemoryUsage, 'rss'>, ObservableGauge>;
|
|
24
|
+
|
|
25
|
+
private eventLoopUilization: ObservableGauge;
|
|
26
|
+
private eventLoopTime: UpDownCounter;
|
|
27
|
+
|
|
28
|
+
private started = false;
|
|
29
|
+
|
|
30
|
+
private lastELU: EventLoopUtilization | undefined;
|
|
31
|
+
private eventLoopDelay: IntervalHistogram;
|
|
32
|
+
|
|
33
|
+
constructor(private meter: Meter) {
|
|
34
|
+
this.eventLoopDelayGauges = {
|
|
35
|
+
min: meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_DELAY_MIN),
|
|
36
|
+
mean: meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_DELAY_MEAN),
|
|
37
|
+
max: meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_DELAY_MAX),
|
|
38
|
+
stddev: meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_DELAY_STDDEV),
|
|
39
|
+
p50: meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_DELAY_P50),
|
|
40
|
+
p90: meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_DELAY_P90),
|
|
41
|
+
p99: meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_DELAY_P99),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
this.eventLoopUilization = meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_UTILIZATION);
|
|
45
|
+
this.eventLoopTime = createUpDownCounterWithDefault(meter, Metrics.NODEJS_EVENT_LOOP_TIME, {
|
|
46
|
+
[Attributes.NODEJS_EVENT_LOOP_STATE]: ['idle', 'active'],
|
|
47
|
+
});
|
|
48
|
+
this.eventLoopDelay = monitorEventLoopDelay();
|
|
49
|
+
|
|
50
|
+
this.memoryGauges = {
|
|
51
|
+
heapUsed: meter.createObservableGauge(Metrics.NODEJS_MEMORY_HEAP_USAGE),
|
|
52
|
+
heapTotal: meter.createObservableGauge(Metrics.NODEJS_MEMORY_HEAP_TOTAL),
|
|
53
|
+
arrayBuffers: meter.createObservableGauge(Metrics.NODEJS_MEMORY_BUFFER_USAGE),
|
|
54
|
+
external: meter.createObservableGauge(Metrics.NODEJS_MEMORY_NATIVE_USAGE),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
start(): void {
|
|
59
|
+
if (this.started) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.lastELU = performance.eventLoopUtilization();
|
|
64
|
+
this.eventLoopDelay.enable();
|
|
65
|
+
this.meter.addBatchObservableCallback(this.measure, [
|
|
66
|
+
this.eventLoopUilization,
|
|
67
|
+
...Object.values(this.eventLoopDelayGauges),
|
|
68
|
+
...Object.values(this.memoryGauges),
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
stop(): void {
|
|
73
|
+
if (!this.started) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.meter.removeBatchObservableCallback(this.measure, [
|
|
77
|
+
this.eventLoopUilization,
|
|
78
|
+
...Object.values(this.eventLoopDelayGauges),
|
|
79
|
+
...Object.values(this.memoryGauges),
|
|
80
|
+
]);
|
|
81
|
+
this.eventLoopDelay.disable();
|
|
82
|
+
this.eventLoopDelay.reset();
|
|
83
|
+
this.lastELU = undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private measure = (obs: BatchObservableResult): void => {
|
|
87
|
+
this.measureMemoryUsage(obs);
|
|
88
|
+
this.measureEventLoopDelay(obs);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
private measureMemoryUsage = (observer: BatchObservableResult) => {
|
|
92
|
+
const mem = process.memoryUsage();
|
|
93
|
+
|
|
94
|
+
observer.observe(this.memoryGauges.heapUsed, mem.heapUsed);
|
|
95
|
+
observer.observe(this.memoryGauges.heapTotal, mem.heapTotal);
|
|
96
|
+
observer.observe(this.memoryGauges.arrayBuffers, mem.arrayBuffers);
|
|
97
|
+
observer.observe(this.memoryGauges.external, mem.external);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
private measureEventLoopDelay = (obs: BatchObservableResult): void => {
|
|
101
|
+
const newELU = performance.eventLoopUtilization();
|
|
102
|
+
const delta = performance.eventLoopUtilization(newELU, this.lastELU);
|
|
103
|
+
this.lastELU = newELU;
|
|
104
|
+
|
|
105
|
+
// `utilization` [0,1] represents how much the event loop is busy vs waiting for new events to come in
|
|
106
|
+
// This should be corelated with CPU usage to gauge the performance characteristics of services
|
|
107
|
+
// 100% utilization leads to high latency because the event loop is _always_ busy, there's no breathing room for events to be processed quickly.
|
|
108
|
+
// Docs and examples:
|
|
109
|
+
// - https://nodesource.com/blog/event-loop-utilization-nodejs
|
|
110
|
+
// - https://youtu.be/WetXnEPraYM
|
|
111
|
+
obs.observe(this.eventLoopUilization, delta.utilization);
|
|
112
|
+
|
|
113
|
+
this.eventLoopTime.add(Math.trunc(delta.idle), { [Attributes.NODEJS_EVENT_LOOP_STATE]: 'idle' });
|
|
114
|
+
this.eventLoopTime.add(Math.trunc(delta.active), { [Attributes.NODEJS_EVENT_LOOP_STATE]: 'active' });
|
|
115
|
+
|
|
116
|
+
safeObserveInt(obs, this.eventLoopDelayGauges.min, this.eventLoopDelay.min);
|
|
117
|
+
safeObserveInt(obs, this.eventLoopDelayGauges.mean, this.eventLoopDelay.mean);
|
|
118
|
+
safeObserveInt(obs, this.eventLoopDelayGauges.max, this.eventLoopDelay.max);
|
|
119
|
+
safeObserveInt(obs, this.eventLoopDelayGauges.stddev, this.eventLoopDelay.stddev);
|
|
120
|
+
safeObserveInt(obs, this.eventLoopDelayGauges.p50, this.eventLoopDelay.percentile(50));
|
|
121
|
+
safeObserveInt(obs, this.eventLoopDelayGauges.p90, this.eventLoopDelay.percentile(90));
|
|
122
|
+
safeObserveInt(obs, this.eventLoopDelayGauges.p99, this.eventLoopDelay.percentile(99));
|
|
123
|
+
|
|
124
|
+
this.eventLoopDelay.reset();
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function safeObserveInt(observer: BatchObservableResult, metric: Observable, value: number, attrs?: object) {
|
|
129
|
+
// discard NaN, Infinity, -Infinity
|
|
130
|
+
if (!Number.isFinite(value)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
observer.observe(metric, Math.trunc(value), attrs);
|
|
135
|
+
}
|
package/src/noop.ts
CHANGED
|
@@ -1,10 +1,63 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Context, type Span, type SpanContext, type Tracer, createNoopMeter } from '@opentelemetry/api';
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { MetricDefinition } from './metrics.js';
|
|
4
|
+
import type {
|
|
5
|
+
Gauge,
|
|
6
|
+
Histogram,
|
|
7
|
+
Meter,
|
|
8
|
+
ObservableGauge,
|
|
9
|
+
ObservableUpDownCounter,
|
|
10
|
+
TelemetryClient,
|
|
11
|
+
UpDownCounter,
|
|
12
|
+
} from './telemetry.js';
|
|
13
|
+
|
|
14
|
+
/** A no-op meter that implements our custom Meter interface */
|
|
15
|
+
class NoopMeter implements Meter {
|
|
16
|
+
private otelMeter = createNoopMeter();
|
|
17
|
+
|
|
18
|
+
createGauge(_metric: MetricDefinition): Gauge {
|
|
19
|
+
return this.otelMeter.createGauge('');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
createObservableGauge(_metric: MetricDefinition): ObservableGauge {
|
|
23
|
+
return this.otelMeter.createObservableGauge('');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
createHistogram(_metric: MetricDefinition, _extraOptions?: Parameters<Meter['createHistogram']>[1]): Histogram {
|
|
27
|
+
return this.otelMeter.createHistogram('');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
createUpDownCounter(_metric: MetricDefinition): UpDownCounter {
|
|
31
|
+
return this.otelMeter.createUpDownCounter('');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
createObservableUpDownCounter(_metric: MetricDefinition): ObservableUpDownCounter {
|
|
35
|
+
return this.otelMeter.createObservableUpDownCounter('');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
addBatchObservableCallback(
|
|
39
|
+
callback: Parameters<Meter['addBatchObservableCallback']>[0],
|
|
40
|
+
observables: Parameters<Meter['addBatchObservableCallback']>[1],
|
|
41
|
+
): void {
|
|
42
|
+
this.otelMeter.addBatchObservableCallback(callback, observables);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
removeBatchObservableCallback(
|
|
46
|
+
callback: Parameters<Meter['removeBatchObservableCallback']>[0],
|
|
47
|
+
observables: Parameters<Meter['removeBatchObservableCallback']>[1],
|
|
48
|
+
): void {
|
|
49
|
+
this.otelMeter.removeBatchObservableCallback(callback, observables);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
4
52
|
|
|
5
53
|
export class NoopTelemetryClient implements TelemetryClient {
|
|
54
|
+
private meter = new NoopMeter();
|
|
55
|
+
|
|
56
|
+
setExportedPublicTelemetry(_prefixes: string[]): void {}
|
|
57
|
+
setPublicTelemetryCollectFrom(_roles: string[]): void {}
|
|
58
|
+
|
|
6
59
|
getMeter(): Meter {
|
|
7
|
-
return
|
|
60
|
+
return this.meter;
|
|
8
61
|
}
|
|
9
62
|
|
|
10
63
|
getTracer(): Tracer {
|
|
@@ -22,6 +75,14 @@ export class NoopTelemetryClient implements TelemetryClient {
|
|
|
22
75
|
isEnabled() {
|
|
23
76
|
return false;
|
|
24
77
|
}
|
|
78
|
+
|
|
79
|
+
getTraceContext(): string | undefined {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
extractPropagatedContext(_traceContext: string): Context | undefined {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
25
86
|
}
|
|
26
87
|
|
|
27
88
|
// @opentelemetry/api internally uses NoopTracer and NoopSpan but they're not exported
|
|
@@ -32,7 +93,7 @@ export class NoopTracer implements Tracer {
|
|
|
32
93
|
return new NoopSpan();
|
|
33
94
|
}
|
|
34
95
|
|
|
35
|
-
startActiveSpan<F extends (...args: any[]) => any>(_name: string, ...args:
|
|
96
|
+
startActiveSpan<F extends (...args: any[]) => any>(_name: string, ...args: unknown[]): ReturnType<F> {
|
|
36
97
|
// there are three different signatures for startActiveSpan, grab the function, we don't care about the rest
|
|
37
98
|
const fn = args.find(arg => typeof arg === 'function') as F;
|
|
38
99
|
return fn(new NoopSpan());
|
package/src/otel.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { type LogData, type Logger, addLogDataHandler } from '@aztec/foundation/log';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
type Context,
|
|
4
5
|
DiagConsoleLogger,
|
|
5
6
|
DiagLogLevel,
|
|
6
|
-
type Meter,
|
|
7
|
+
type Meter as OtelMeter,
|
|
8
|
+
ROOT_CONTEXT,
|
|
7
9
|
type Tracer,
|
|
8
10
|
type TracerProvider,
|
|
9
11
|
context,
|
|
10
12
|
diag,
|
|
11
13
|
isSpanContextValid,
|
|
14
|
+
propagation,
|
|
12
15
|
trace,
|
|
13
16
|
} from '@opentelemetry/api';
|
|
17
|
+
import { W3CTraceContextPropagator } from '@opentelemetry/core';
|
|
14
18
|
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
|
|
15
19
|
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
16
20
|
import { HostMetrics } from '@opentelemetry/host-metrics';
|
|
@@ -28,18 +32,67 @@ import { BatchSpanProcessor, NodeTracerProvider } from '@opentelemetry/sdk-trace
|
|
|
28
32
|
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
|
|
29
33
|
|
|
30
34
|
import type { TelemetryClientConfig } from './config.js';
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
35
|
+
import { toMetricOptions } from './metric-utils.js';
|
|
36
|
+
import type { MetricDefinition } from './metrics.js';
|
|
37
|
+
import { NodejsMetricsMonitor } from './nodejs_metrics_monitor.js';
|
|
38
|
+
import { OtelFilterMetricExporter, PublicOtelFilterMetricExporter } from './otel_filter_metric_exporter.js';
|
|
33
39
|
import { registerOtelLoggerProvider } from './otel_logger_provider.js';
|
|
34
40
|
import { getOtelResource } from './otel_resource.js';
|
|
35
|
-
import type {
|
|
41
|
+
import type {
|
|
42
|
+
Gauge,
|
|
43
|
+
Histogram,
|
|
44
|
+
Meter,
|
|
45
|
+
ObservableGauge,
|
|
46
|
+
ObservableUpDownCounter,
|
|
47
|
+
TelemetryClient,
|
|
48
|
+
UpDownCounter,
|
|
49
|
+
} from './telemetry.js';
|
|
50
|
+
|
|
51
|
+
/** Wraps an OpenTelemetry Meter to implement our custom Meter interface */
|
|
52
|
+
class WrappedMeter implements Meter {
|
|
53
|
+
constructor(private otelMeter: OtelMeter) {}
|
|
54
|
+
|
|
55
|
+
createGauge(metric: MetricDefinition): Gauge {
|
|
56
|
+
return this.otelMeter.createGauge(metric.name, toMetricOptions(metric));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
createObservableGauge(metric: MetricDefinition): ObservableGauge {
|
|
60
|
+
return this.otelMeter.createObservableGauge(metric.name, toMetricOptions(metric));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
createHistogram(metric: MetricDefinition, extraOptions?: Parameters<Meter['createHistogram']>[1]): Histogram {
|
|
64
|
+
return this.otelMeter.createHistogram(metric.name, { ...toMetricOptions(metric), ...extraOptions });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
createUpDownCounter(metric: MetricDefinition): UpDownCounter {
|
|
68
|
+
return this.otelMeter.createUpDownCounter(metric.name, toMetricOptions(metric));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
createObservableUpDownCounter(metric: MetricDefinition): ObservableUpDownCounter {
|
|
72
|
+
return this.otelMeter.createObservableUpDownCounter(metric.name, toMetricOptions(metric));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
addBatchObservableCallback(
|
|
76
|
+
callback: Parameters<Meter['addBatchObservableCallback']>[0],
|
|
77
|
+
observables: Parameters<Meter['addBatchObservableCallback']>[1],
|
|
78
|
+
): void {
|
|
79
|
+
this.otelMeter.addBatchObservableCallback(callback, observables);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
removeBatchObservableCallback(
|
|
83
|
+
callback: Parameters<Meter['removeBatchObservableCallback']>[0],
|
|
84
|
+
observables: Parameters<Meter['removeBatchObservableCallback']>[1],
|
|
85
|
+
): void {
|
|
86
|
+
this.otelMeter.removeBatchObservableCallback(callback, observables);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
36
89
|
|
|
37
90
|
export type OpenTelemetryClientFactory = (resource: IResource, log: Logger) => OpenTelemetryClient;
|
|
38
91
|
|
|
39
92
|
export class OpenTelemetryClient implements TelemetryClient {
|
|
40
93
|
hostMetrics: HostMetrics | undefined;
|
|
41
|
-
|
|
42
|
-
private meters: Map<string,
|
|
94
|
+
nodejsMetricsMonitor: NodejsMetricsMonitor | undefined;
|
|
95
|
+
private meters: Map<string, WrappedMeter> = new Map<string, WrappedMeter>();
|
|
43
96
|
private tracers: Map<string, Tracer> = new Map<string, Tracer>();
|
|
44
97
|
|
|
45
98
|
protected constructor(
|
|
@@ -47,13 +100,23 @@ export class OpenTelemetryClient implements TelemetryClient {
|
|
|
47
100
|
private meterProvider: MeterProvider,
|
|
48
101
|
private traceProvider: TracerProvider,
|
|
49
102
|
private loggerProvider: LoggerProvider | undefined,
|
|
103
|
+
private publicMetricExporter: PublicOtelFilterMetricExporter | undefined,
|
|
50
104
|
private log: Logger,
|
|
51
105
|
) {}
|
|
52
106
|
|
|
107
|
+
setExportedPublicTelemetry(metrics: string[]): void {
|
|
108
|
+
this.publicMetricExporter?.setMetricPrefixes(metrics);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
setPublicTelemetryCollectFrom(roles: string[]): void {
|
|
112
|
+
this.publicMetricExporter?.setAllowedRoles(roles);
|
|
113
|
+
}
|
|
114
|
+
|
|
53
115
|
getMeter(name: string): Meter {
|
|
54
116
|
let meter = this.meters.get(name);
|
|
55
117
|
if (!meter) {
|
|
56
|
-
|
|
118
|
+
const otelMeter = this.meterProvider.getMeter(name, this.resource.attributes[ATTR_SERVICE_VERSION] as string);
|
|
119
|
+
meter = new WrappedMeter(otelMeter);
|
|
57
120
|
this.meters.set(name, meter);
|
|
58
121
|
}
|
|
59
122
|
return meter;
|
|
@@ -91,12 +154,13 @@ export class OpenTelemetryClient implements TelemetryClient {
|
|
|
91
154
|
meterProvider: this.meterProvider,
|
|
92
155
|
});
|
|
93
156
|
|
|
94
|
-
|
|
157
|
+
const nodejsMeter = new WrappedMeter(
|
|
95
158
|
this.meterProvider.getMeter(this.resource.attributes[ATTR_SERVICE_NAME] as string),
|
|
96
159
|
);
|
|
160
|
+
this.nodejsMetricsMonitor = new NodejsMetricsMonitor(nodejsMeter);
|
|
97
161
|
|
|
98
162
|
this.hostMetrics.start();
|
|
99
|
-
this.
|
|
163
|
+
this.nodejsMetricsMonitor.start();
|
|
100
164
|
}
|
|
101
165
|
|
|
102
166
|
public isEnabled() {
|
|
@@ -112,7 +176,7 @@ export class OpenTelemetryClient implements TelemetryClient {
|
|
|
112
176
|
}
|
|
113
177
|
|
|
114
178
|
public async stop() {
|
|
115
|
-
this.
|
|
179
|
+
this.nodejsMetricsMonitor?.stop();
|
|
116
180
|
|
|
117
181
|
const flushAndShutdown = async (provider?: { forceFlush: () => Promise<void>; shutdown: () => Promise<void> }) => {
|
|
118
182
|
if (!provider) {
|
|
@@ -129,18 +193,46 @@ export class OpenTelemetryClient implements TelemetryClient {
|
|
|
129
193
|
]);
|
|
130
194
|
}
|
|
131
195
|
|
|
196
|
+
public getTraceContext(): string | undefined {
|
|
197
|
+
const carrier: Record<string, string> = {};
|
|
198
|
+
propagation.inject(context.active(), carrier);
|
|
199
|
+
return carrier['traceparent'];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
public extractPropagatedContext(traceContext: string): Context {
|
|
203
|
+
const extractedContext = propagation.extract(ROOT_CONTEXT, {
|
|
204
|
+
traceparent: traceContext,
|
|
205
|
+
});
|
|
206
|
+
return extractedContext;
|
|
207
|
+
}
|
|
208
|
+
|
|
132
209
|
public static createMeterProvider(
|
|
133
210
|
resource: IResource,
|
|
134
|
-
|
|
211
|
+
exporters: Array<PeriodicExportingMetricReaderOptions>,
|
|
135
212
|
): MeterProvider {
|
|
136
213
|
return new MeterProvider({
|
|
137
214
|
resource,
|
|
138
|
-
readers: options
|
|
139
|
-
? [new PeriodicExportingMetricReader(options as PeriodicExportingMetricReaderOptions)]
|
|
140
|
-
: [],
|
|
215
|
+
readers: exporters.map(options => new PeriodicExportingMetricReader(options)),
|
|
141
216
|
|
|
142
217
|
views: [
|
|
143
218
|
// Every histogram matching the selector (type + unit) gets these custom buckets assigned
|
|
219
|
+
new View({
|
|
220
|
+
instrumentType: InstrumentType.HISTOGRAM,
|
|
221
|
+
instrumentUnit: 'Mmana',
|
|
222
|
+
aggregation: new ExplicitBucketHistogramAggregation(
|
|
223
|
+
[0.1, 0.5, 1, 2, 4, 8, 10, 25, 50, 100, 500, 1000, 5000, 10000],
|
|
224
|
+
true,
|
|
225
|
+
),
|
|
226
|
+
}),
|
|
227
|
+
new View({
|
|
228
|
+
instrumentType: InstrumentType.HISTOGRAM,
|
|
229
|
+
instrumentUnit: 'tx',
|
|
230
|
+
aggregation: new ExplicitBucketHistogramAggregation(
|
|
231
|
+
// TPS
|
|
232
|
+
[0.1 * 36, 0.2 * 36, 0.5 * 36, 1 * 36, 2 * 36, 5 * 36, 10 * 36, 15 * 36].map(Math.ceil),
|
|
233
|
+
true,
|
|
234
|
+
),
|
|
235
|
+
}),
|
|
144
236
|
new View({
|
|
145
237
|
instrumentType: InstrumentType.HISTOGRAM,
|
|
146
238
|
instrumentUnit: 's',
|
|
@@ -255,22 +347,59 @@ export class OpenTelemetryClient implements TelemetryClient {
|
|
|
255
347
|
: [],
|
|
256
348
|
});
|
|
257
349
|
|
|
258
|
-
tracerProvider.register(
|
|
259
|
-
|
|
260
|
-
const meterProvider = OpenTelemetryClient.createMeterProvider(resource, {
|
|
261
|
-
exporter: config.metricsCollectorUrl
|
|
262
|
-
? new OtelFilterMetricExporter(
|
|
263
|
-
new OTLPMetricExporter({ url: config.metricsCollectorUrl.href }),
|
|
264
|
-
config.otelExcludeMetrics ?? [],
|
|
265
|
-
)
|
|
266
|
-
: undefined,
|
|
267
|
-
exportTimeoutMillis: config.otelExportTimeoutMs,
|
|
268
|
-
exportIntervalMillis: config.otelCollectIntervalMs,
|
|
350
|
+
tracerProvider.register({
|
|
351
|
+
propagator: new W3CTraceContextPropagator(),
|
|
269
352
|
});
|
|
270
353
|
|
|
354
|
+
const exporters: PeriodicExportingMetricReaderOptions[] = [];
|
|
355
|
+
if (config.metricsCollectorUrl) {
|
|
356
|
+
// Default to a blacklist that is empty (allow all metrics)
|
|
357
|
+
let filter: string[] = [];
|
|
358
|
+
let mode: 'allow' | 'deny' = 'deny';
|
|
359
|
+
if (config.otelExcludeMetrics.length > 0) {
|
|
360
|
+
// Implement a blacklist as specified in config
|
|
361
|
+
log.info(`Excluding metrics from export: ${config.otelExcludeMetrics}`);
|
|
362
|
+
filter = config.otelExcludeMetrics;
|
|
363
|
+
mode = 'deny';
|
|
364
|
+
} else if (config.otelIncludeMetrics.length > 0) {
|
|
365
|
+
// Implement a whitelist as specified in config
|
|
366
|
+
log.info(`Including only specified metrics for export: ${config.otelIncludeMetrics}`);
|
|
367
|
+
filter = config.otelIncludeMetrics;
|
|
368
|
+
mode = 'allow';
|
|
369
|
+
}
|
|
370
|
+
exporters.push({
|
|
371
|
+
exporter: new OtelFilterMetricExporter(
|
|
372
|
+
new OTLPMetricExporter({ url: config.metricsCollectorUrl.href }),
|
|
373
|
+
filter,
|
|
374
|
+
mode,
|
|
375
|
+
),
|
|
376
|
+
exportTimeoutMillis: config.otelExportTimeoutMs,
|
|
377
|
+
exportIntervalMillis: config.otelCollectIntervalMs,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
let publicExporter: PublicOtelFilterMetricExporter | undefined;
|
|
382
|
+
if (config.publicMetricsCollectorUrl && !config.publicMetricsOptOut) {
|
|
383
|
+
log.info(`Exporting public metrics: ${config.publicIncludeMetrics}`, {
|
|
384
|
+
publicMetrics: config.publicIncludeMetrics,
|
|
385
|
+
collectorUrl: config.publicMetricsCollectorUrl,
|
|
386
|
+
});
|
|
387
|
+
publicExporter = new PublicOtelFilterMetricExporter(
|
|
388
|
+
config.publicMetricsCollectFrom,
|
|
389
|
+
new OTLPMetricExporter({ url: config.publicMetricsCollectorUrl.href }),
|
|
390
|
+
config.publicIncludeMetrics,
|
|
391
|
+
);
|
|
392
|
+
exporters.push({
|
|
393
|
+
exporter: publicExporter,
|
|
394
|
+
exportTimeoutMillis: config.otelExportTimeoutMs,
|
|
395
|
+
exportIntervalMillis: config.otelCollectIntervalMs,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const meterProvider = OpenTelemetryClient.createMeterProvider(resource, exporters);
|
|
271
400
|
const loggerProvider = registerOtelLoggerProvider(resource, config.logsCollectorUrl);
|
|
272
401
|
|
|
273
|
-
return new OpenTelemetryClient(resource, meterProvider, tracerProvider, loggerProvider, log);
|
|
402
|
+
return new OpenTelemetryClient(resource, meterProvider, tracerProvider, loggerProvider, publicExporter, log);
|
|
274
403
|
};
|
|
275
404
|
}
|
|
276
405
|
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ExportResult, ExportResultCode } from '@opentelemetry/core';
|
|
2
2
|
import type { MetricData, PushMetricExporter, ResourceMetrics } from '@opentelemetry/sdk-metrics';
|
|
3
3
|
|
|
4
|
+
import { AZTEC_NODE_ROLE } from './attributes.js';
|
|
5
|
+
|
|
4
6
|
export class OtelFilterMetricExporter implements PushMetricExporter {
|
|
5
|
-
constructor(
|
|
7
|
+
constructor(
|
|
8
|
+
private readonly exporter: PushMetricExporter,
|
|
9
|
+
private metricPrefix: string[],
|
|
10
|
+
private readonly filter: 'allow' | 'deny' = 'deny',
|
|
11
|
+
) {
|
|
6
12
|
if (exporter.selectAggregation) {
|
|
7
13
|
(this as PushMetricExporter).selectAggregation = exporter.selectAggregation.bind(exporter);
|
|
8
14
|
}
|
|
@@ -23,9 +29,17 @@ export class OtelFilterMetricExporter implements PushMetricExporter {
|
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
private filterMetrics(metrics: MetricData[]): MetricData[] {
|
|
26
|
-
return metrics.filter(
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
return metrics.filter(metric => {
|
|
33
|
+
const matched = this.metricPrefix.some(prefix => metric.descriptor.name.startsWith(prefix));
|
|
34
|
+
|
|
35
|
+
if (this.filter === 'deny') {
|
|
36
|
+
return !matched;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (this.filter === 'allow') {
|
|
40
|
+
return matched;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
29
43
|
}
|
|
30
44
|
|
|
31
45
|
public forceFlush(): Promise<void> {
|
|
@@ -35,4 +49,32 @@ export class OtelFilterMetricExporter implements PushMetricExporter {
|
|
|
35
49
|
public shutdown(): Promise<void> {
|
|
36
50
|
return this.exporter.shutdown();
|
|
37
51
|
}
|
|
52
|
+
|
|
53
|
+
public setMetricPrefixes(metrics: string[]) {
|
|
54
|
+
this.metricPrefix = metrics;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class PublicOtelFilterMetricExporter extends OtelFilterMetricExporter {
|
|
59
|
+
constructor(
|
|
60
|
+
private allowedRoles: string[],
|
|
61
|
+
exporter: PushMetricExporter,
|
|
62
|
+
metricPrefix: string[],
|
|
63
|
+
) {
|
|
64
|
+
super(exporter, metricPrefix, 'allow');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public override export(metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void): void {
|
|
68
|
+
const role = String(metrics.resource.attributes[AZTEC_NODE_ROLE] ?? '');
|
|
69
|
+
if (!role || !this.allowedRoles.includes(role)) {
|
|
70
|
+
// noop
|
|
71
|
+
return resultCallback({ code: ExportResultCode.SUCCESS });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
super.export(metrics, resultCallback);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public setAllowedRoles(roles: string[]) {
|
|
78
|
+
this.allowedRoles = roles;
|
|
79
|
+
}
|
|
38
80
|
}
|
package/src/otel_resource.ts
CHANGED
|
@@ -1,16 +1,71 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type DetectorSync,
|
|
2
3
|
type IResource,
|
|
4
|
+
Resource,
|
|
3
5
|
detectResourcesSync,
|
|
4
6
|
envDetectorSync,
|
|
5
7
|
osDetectorSync,
|
|
6
|
-
processDetectorSync,
|
|
7
8
|
serviceInstanceIdDetectorSync,
|
|
8
9
|
} from '@opentelemetry/resources';
|
|
10
|
+
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
|
|
11
|
+
import { readFileSync } from 'fs';
|
|
12
|
+
import { dirname, resolve } from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
|
|
15
|
+
import { AZTEC_NODE_ROLE, AZTEC_REGISTRY_ADDRESS, AZTEC_ROLLUP_ADDRESS, AZTEC_ROLLUP_VERSION } from './attributes.js';
|
|
16
|
+
|
|
17
|
+
/** Reads the Aztec client version from the release manifest. */
|
|
18
|
+
function getAztecVersion(): string | undefined {
|
|
19
|
+
try {
|
|
20
|
+
const releasePleasePath = resolve(
|
|
21
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
22
|
+
'../../../.release-please-manifest.json',
|
|
23
|
+
);
|
|
24
|
+
return JSON.parse(readFileSync(releasePleasePath, 'utf-8'))['.'];
|
|
25
|
+
} catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
9
29
|
|
|
10
30
|
export function getOtelResource(): IResource {
|
|
11
31
|
const resource = detectResourcesSync({
|
|
12
|
-
detectors: [
|
|
32
|
+
detectors: [
|
|
33
|
+
aztecNetworkDetectorSync,
|
|
34
|
+
osDetectorSync,
|
|
35
|
+
envDetectorSync,
|
|
36
|
+
// this detector is disabled because:
|
|
37
|
+
// 1. our software runs in a docker container, a lot of the attributes detected would be identical across different machines (e.g. all run node v22, executing the same script, running PID 1, etc)
|
|
38
|
+
// 2. it catures process.argv which could contain sensitive values in plain text (e.g. validator private keys)
|
|
39
|
+
// processDetectorSync,
|
|
40
|
+
serviceInstanceIdDetectorSync,
|
|
41
|
+
],
|
|
13
42
|
});
|
|
14
43
|
|
|
15
44
|
return resource;
|
|
16
45
|
}
|
|
46
|
+
|
|
47
|
+
const aztecNetworkDetectorSync: DetectorSync = {
|
|
48
|
+
detect(): IResource {
|
|
49
|
+
let role: string | undefined;
|
|
50
|
+
if (process.argv.includes('--sequencer')) {
|
|
51
|
+
role = 'sequencer';
|
|
52
|
+
} else if (process.argv.includes('--prover-node')) {
|
|
53
|
+
role = 'prover-node';
|
|
54
|
+
} else if (process.argv.includes('--node')) {
|
|
55
|
+
role = 'node';
|
|
56
|
+
} else if (process.argv.includes('--p2p-bootstrap')) {
|
|
57
|
+
role = 'bootnode';
|
|
58
|
+
}
|
|
59
|
+
const aztecAttributes = {
|
|
60
|
+
// this gets overwritten by OTEL_RESOURCE_ATTRIBUTES (if set)
|
|
61
|
+
[ATTR_SERVICE_NAME]: role ? `aztec-${role}` : undefined,
|
|
62
|
+
[ATTR_SERVICE_VERSION]: getAztecVersion(),
|
|
63
|
+
[AZTEC_NODE_ROLE]: role,
|
|
64
|
+
[AZTEC_ROLLUP_VERSION]: process.env.ROLLUP_VERSION ?? 'canonical',
|
|
65
|
+
[AZTEC_ROLLUP_ADDRESS]: process.env.ROLLUP_CONTRACT_ADDRESS,
|
|
66
|
+
[AZTEC_REGISTRY_ADDRESS]: process.env.REGISTRY_CONTRACT_ADDRESS,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return new Resource(aztecAttributes);
|
|
70
|
+
},
|
|
71
|
+
};
|