Skip to main content

kmp_observability/
metrics.rs

1use opentelemetry::metrics::{Counter, Histogram, Meter};
2use opentelemetry_otlp::WithExportConfig;
3use opentelemetry_otlp::WithTonicConfig;
4use opentelemetry_sdk::metrics::SdkMeterProvider;
5
6/// Kernel-wide metric instruments.
7///
8/// When `OTEL_EXPORTER_OTLP_ENDPOINT` is set, these export via OTLP.
9/// When not set, the instruments are still valid but discard data (noop meter).
10pub struct KernelMetrics {
11    pub rpc_duration: Histogram<f64>,
12    pub bundle_nodes: Histogram<u64>,
13    pub bundle_relationships: Histogram<u64>,
14    pub bundle_details: Histogram<u64>,
15    pub rendered_tokens: Histogram<u64>,
16    pub truncation_total: Counter<u64>,
17    pub projection_lag: Histogram<f64>,
18}
19
20impl KernelMetrics {
21    pub fn new(meter: &Meter) -> Self {
22        Self {
23            rpc_duration: meter
24                .f64_histogram("rehydration.rpc.duration")
25                .with_description("RPC latency in seconds")
26                .with_unit("s")
27                .build(),
28            bundle_nodes: meter
29                .u64_histogram("rehydration.bundle.nodes")
30                .with_description("Number of nodes in rehydrated bundle")
31                .build(),
32            bundle_relationships: meter
33                .u64_histogram("rehydration.bundle.relationships")
34                .with_description("Number of relationships in rehydrated bundle")
35                .build(),
36            bundle_details: meter
37                .u64_histogram("rehydration.bundle.details")
38                .with_description("Number of node details in rehydrated bundle")
39                .build(),
40            rendered_tokens: meter
41                .u64_histogram("rehydration.rendered.tokens")
42                .with_description("Rendered token count after budget enforcement")
43                .build(),
44            truncation_total: meter
45                .u64_counter("rehydration.truncation.total")
46                .with_description("Number of renders that required truncation")
47                .build(),
48            projection_lag: meter
49                .f64_histogram("rehydration.projection.lag")
50                .with_description("Projection processing lag in seconds")
51                .with_unit("s")
52                .build(),
53        }
54    }
55}
56
57pub(crate) fn init_otel_metrics(service_name: &str) -> Option<SdkMeterProvider> {
58    let endpoint = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").ok()?;
59    if endpoint.trim().is_empty() {
60        return None;
61    }
62
63    let mut builder = opentelemetry_otlp::MetricExporter::builder()
64        .with_tonic()
65        .with_endpoint(endpoint);
66    if let Some(tls_config) = super::build_otlp_tls_config() {
67        builder = builder.with_tls_config(tls_config);
68    }
69    let exporter = builder.build().ok()?;
70
71    let reader = opentelemetry_sdk::metrics::PeriodicReader::builder(exporter).build();
72
73    let provider = SdkMeterProvider::builder()
74        .with_resource(
75            opentelemetry_sdk::Resource::builder()
76                .with_service_name(service_name.to_string())
77                .build(),
78        )
79        .with_reader(reader)
80        .build();
81
82    Some(provider)
83}