diff --git a/crates/dsx-exchange-consumer/src/metrics.rs b/crates/dsx-exchange-consumer/src/metrics.rs index 0e803bfc40..7221496ef4 100644 --- a/crates/dsx-exchange-consumer/src/metrics.rs +++ b/crates/dsx-exchange-consumer/src/metrics.rs @@ -62,20 +62,15 @@ where .build(); } -// The four message counters are `carbide-instrument` events. Their exposed -// names are grandfathered, and `name_unchecked` keeps them byte-identical: -// the pre-framework counters registered names that already ended in `_total`, -// and the OpenTelemetry Prometheus exporter appends its own `_total` to every -// counter -- so `/metrics` has always shown a doubled `_total_total` suffix. -// The framework strips one `_total` before registering and the exporter -// re-appends it, reproducing the exact name every existing dashboard and alert -// already selects on. +// The four message counters are `carbide-instrument` events. Each declares a +// name ending in a single `_total`: the framework strips one `_total` before +// registering the instrument and the OpenTelemetry Prometheus exporter appends +// its own `_total`, so `/metrics` exposes the name exactly as declared here. /// An MQTT message reached a subscription handler, before any queueing. #[derive(Event)] #[event( - name = "carbide_dsx_exchange_consumer_messages_received_total_total", - name_unchecked, + name = "carbide_dsx_exchange_consumer_messages_received_total", component = "nico-dsx-exchange-consumer", log = off, metric = counter, @@ -87,8 +82,7 @@ pub struct MessageReceived; /// applied (or its alert cleared). #[derive(Event)] #[event( - name = "carbide_dsx_exchange_consumer_messages_processed_total_total", - name_unchecked, + name = "carbide_dsx_exchange_consumer_messages_processed_total", component = "nico-dsx-exchange-consumer", log = off, metric = counter, @@ -102,8 +96,7 @@ pub struct MessageProcessed; /// event only moves the counter beside it. #[derive(Event)] #[event( - name = "carbide_dsx_exchange_consumer_messages_dropped_total_total", - name_unchecked, + name = "carbide_dsx_exchange_consumer_messages_dropped_total", component = "nico-dsx-exchange-consumer", log = off, metric = counter, @@ -118,8 +111,7 @@ pub struct MessageDropped; /// event only moves the counter beside it. #[derive(Event)] #[event( - name = "carbide_dsx_exchange_consumer_dedup_skipped_total_total", - name_unchecked, + name = "carbide_dsx_exchange_consumer_dedup_skipped_total", component = "nico-dsx-exchange-consumer", log = off, metric = counter, @@ -144,8 +136,11 @@ pub struct ConsumerMetrics { impl ConsumerMetrics { pub fn new(meter: &Meter) -> Self { Self { + // The Prometheus exporter appends `_total`, so the registered name + // omits it; that yields the single-`_total` exposed name, matching + // the framework counters above (not a doubled `_total_total`). alerts_detected: meter - .u64_counter(format!("{METRICS_PREFIX}_alerts_detected_total")) + .u64_counter(format!("{METRICS_PREFIX}_alerts_detected")) .with_description("Number of leak alerts detected") .build(), } diff --git a/crates/dsx-exchange-consumer/tests/metric_exposition.rs b/crates/dsx-exchange-consumer/tests/metric_exposition.rs index 75e5a714d8..a080b73da8 100644 --- a/crates/dsx-exchange-consumer/tests/metric_exposition.rs +++ b/crates/dsx-exchange-consumer/tests/metric_exposition.rs @@ -15,9 +15,10 @@ * limitations under the License. */ -//! Pins the exposed names of the four message-counter events byte-for-byte to -//! what the pre-framework hand-rolled counters served, so the conversion never -//! renames a metric a dashboard or alert selects on. +//! Pins the exposed names of the four message-counter events to their single +//! `_total` form: the framework strips the declared name's `_total` before +//! registering and the OTel Prometheus exporter appends exactly one, so +//! `/metrics` shows one suffix, not the historical doubled `_total_total`. //! //! One test in its own binary (its own process-global registry) keeps the //! `counter_delta` measurements deterministic: the crate's other unit tests @@ -29,15 +30,15 @@ use carbide_dsx_exchange_consumer::metrics::{ use carbide_instrument::emit; use carbide_instrument::testing::{MetricsCapture, capture_logs}; -/// Emitting each event once moves exactly its counter, under the doubled -/// `_total_total` name the OTel Prometheus exporter has always produced for -/// these counters (the register name already ended in `_total`, and the -/// exporter appends another). All four events are metric-only (`log = off`): -/// the WARN at each drop site and the TRACE at the dedup site are plain -/// `tracing` lines the reshape left untouched, so they stay at the call sites, -/// not on the events (the dedup line is exercised in `health_updater.rs`). +/// Emitting each event once moves exactly its counter, under the single +/// `_total` name the OTel Prometheus exporter produces (the framework strips +/// the declared name's `_total`, and the exporter appends exactly one). All +/// four events are metric-only (`log = off`): the WARN at each drop site and +/// the TRACE at the dedup site are plain `tracing` lines the reshape left +/// untouched, so they stay at the call sites, not on the events (the dedup +/// line is exercised in `health_updater.rs`). #[test] -fn message_events_preserve_names_and_are_metric_only() { +fn message_events_expose_single_total_names_and_are_metric_only() { let metrics = MetricsCapture::start(); let logs = capture_logs(|| { emit(MessageReceived); @@ -46,12 +47,12 @@ fn message_events_preserve_names_and_are_metric_only() { emit(MessageDeduplicated); }); - // Exposed names are byte-identical to the pre-conversion counters. + // Exposed names end in a single `_total`. for name in [ - "carbide_dsx_exchange_consumer_messages_received_total_total", - "carbide_dsx_exchange_consumer_messages_processed_total_total", - "carbide_dsx_exchange_consumer_messages_dropped_total_total", - "carbide_dsx_exchange_consumer_dedup_skipped_total_total", + "carbide_dsx_exchange_consumer_messages_received_total", + "carbide_dsx_exchange_consumer_messages_processed_total", + "carbide_dsx_exchange_consumer_messages_dropped_total", + "carbide_dsx_exchange_consumer_dedup_skipped_total", ] { assert_eq!( metrics.counter_delta(name, &[]), @@ -61,11 +62,19 @@ fn message_events_preserve_names_and_are_metric_only() { ); } - // The single-`_total` name never appears -- that would be a rename. - assert_eq!( - metrics.counter_delta("carbide_dsx_exchange_consumer_messages_received_total", &[]), - 0.0 - ); + // None of the historical doubled `_total_total` names appear -- this de-doubles them. + let exposition = metrics.render(); + for doubled in [ + "carbide_dsx_exchange_consumer_messages_received_total_total", + "carbide_dsx_exchange_consumer_messages_processed_total_total", + "carbide_dsx_exchange_consumer_messages_dropped_total_total", + "carbide_dsx_exchange_consumer_dedup_skipped_total_total", + ] { + assert!( + !exposition.contains(doubled), + "doubled name {doubled} must be gone; exposition was:\n{exposition}" + ); + } // Metric-only: the events build no log line, so the drop WARN and dedup // TRACE are never doubled -- only the untouched call-site `tracing` lines