Skip to main content

mysten_metrics/
lib.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use axum::{Router, extract::Extension, http::StatusCode, routing::get};
5use dashmap::DashMap;
6use parking_lot::Mutex;
7use prometheus::core::{AtomicI64, GenericGauge};
8use simple_server_timing_header::Timer;
9use std::future::Future;
10use std::net::SocketAddr;
11use std::pin::Pin;
12use std::sync::Arc;
13use std::task::{Context, Poll};
14use std::time::Instant;
15
16use once_cell::sync::OnceCell;
17use prometheus::{
18    Histogram, IntCounterVec, IntGaugeVec, Registry, TextEncoder, register_histogram_with_registry,
19    register_int_counter_vec_with_registry, register_int_gauge_vec_with_registry,
20};
21use tap::TapFallible;
22use tracing::{Span, warn};
23
24pub use scopeguard;
25use uuid::Uuid;
26
27mod guards;
28pub mod histogram;
29pub mod metered_channel;
30pub mod monitored_mpsc;
31pub mod thread_stall_monitor;
32pub use guards::*;
33
34pub const TX_TYPE_SINGLE_WRITER_TX: &str = "single_writer";
35pub const TX_TYPE_SHARED_OBJ_TX: &str = "shared_object";
36
37/// Used when latency is mostly sub-second.
38pub const SUBSECOND_LATENCY_SEC_BUCKETS: &[f64] = &[
39    0.001, 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.125, 0.15, 0.175, 0.2, 0.225, 0.25, 0.275, 0.3,
40    0.325, 0.35, 0.375, 0.4, 0.425, 0.45, 0.475, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9,
41    0.95, 1., 2., 5., 10., 20., 30., 60., 90.,
42];
43
44/// Used when we don't care about fine-grained values.
45pub const COARSE_LATENCY_SEC_BUCKETS: &[f64] = &[
46    0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.2, 0.3, 0.5, 0.7, 1., 2., 3., 5., 10., 20., 30., 60.,
47    90.,
48];
49
50/// Used when latency is usually < 10s. Expensive because of the number of buckets.
51pub const LATENCY_SEC_BUCKETS: &[f64] = &[
52    0.001, 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.125, 0.15, 0.175, 0.2, 0.225, 0.25, 0.275, 0.3,
53    0.325, 0.35, 0.375, 0.4, 0.425, 0.45, 0.475, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9,
54    0.95, 1., 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2., 2.5, 3., 3.5, 4., 4.5, 5., 6., 7.,
55    8., 9., 10., 15., 20., 25., 30., 60., 90.,
56];
57
58pub const COUNT_BUCKETS: &[f64] = &[
59    1., 2., 3., 4., 5., 7., 10., 15., 20., 25., 30., 40., 50., 75., 100., 150., 200., 500., 1000.,
60    2000., 5000., 10000.,
61];
62
63pub const BYTES_BUCKETS: &[f64] = &[
64    1., 4., 16., 64., 256., 1024., 4096., 8192., 16384., 32768., 65536., 131072., 262144., 524288.,
65    1048576., 2097152., 4194304., 8388608., 16777216., 33554432., 67108864.,
66];
67
68#[derive(Debug)]
69pub struct Metrics {
70    pub tasks: IntGaugeVec,
71    pub futures: IntGaugeVec,
72    pub channel_inflight: IntGaugeVec,
73    pub channel_sent: IntGaugeVec,
74    pub channel_received: IntGaugeVec,
75    pub future_active_duration_ns: IntGaugeVec,
76    pub scope_iterations: IntGaugeVec,
77    pub scope_duration_ns: IntGaugeVec,
78    pub scope_entrance: IntGaugeVec,
79    pub thread_stall_duration_sec: Histogram,
80    pub system_invariant_violations: IntCounterVec,
81    pub execution_bump_only_exits: IntCounterVec,
82}
83
84impl Metrics {
85    fn new(registry: &Registry) -> Self {
86        Self {
87            tasks: register_int_gauge_vec_with_registry!(
88                "monitored_tasks",
89                "Number of running tasks per callsite.",
90                &["callsite"],
91                registry,
92            )
93            .unwrap(),
94            futures: register_int_gauge_vec_with_registry!(
95                "monitored_futures",
96                "Number of pending futures per callsite.",
97                &["callsite"],
98                registry,
99            )
100            .unwrap(),
101            channel_inflight: register_int_gauge_vec_with_registry!(
102                "monitored_channel_inflight",
103                "Inflight items in channels.",
104                &["name"],
105                registry,
106            )
107            .unwrap(),
108            channel_sent: register_int_gauge_vec_with_registry!(
109                "monitored_channel_sent",
110                "Sent items in channels.",
111                &["name"],
112                registry,
113            )
114            .unwrap(),
115            channel_received: register_int_gauge_vec_with_registry!(
116                "monitored_channel_received",
117                "Received items in channels.",
118                &["name"],
119                registry,
120            )
121            .unwrap(),
122            future_active_duration_ns: register_int_gauge_vec_with_registry!(
123                "monitored_future_active_duration_ns",
124                "Total duration in nanosecs where the monitored future is active (consuming CPU time)",
125                &["name"],
126                registry,
127            )
128            .unwrap(),
129            scope_entrance: register_int_gauge_vec_with_registry!(
130                "monitored_scope_entrance",
131                "Number of entrance in the scope.",
132                &["name"],
133                registry,
134            )
135            .unwrap(),
136            scope_iterations: register_int_gauge_vec_with_registry!(
137                "monitored_scope_iterations",
138                "Total number of times where the monitored scope runs",
139                &["name"],
140                registry,
141            )
142            .unwrap(),
143            scope_duration_ns: register_int_gauge_vec_with_registry!(
144                "monitored_scope_duration_ns",
145                "Total duration in nanosecs where the monitored scope is running",
146                &["name"],
147                registry,
148            )
149            .unwrap(),
150            thread_stall_duration_sec: register_histogram_with_registry!(
151                "thread_stall_duration_sec",
152                "Duration of thread stalls in seconds.",
153                registry,
154            )
155            .unwrap(),
156            system_invariant_violations: register_int_counter_vec_with_registry!(
157                "system_invariant_violations",
158                "Number of system invariant violations",
159                &["name"],
160                registry,
161            ).unwrap(),
162            execution_bump_only_exits: register_int_counter_vec_with_registry!(
163                "execution_bump_only_exits",
164                "Number of transactions that bailed to the BumpOnly execution exit, \
165                 excluding the expected InsufficientFundsForWithdraw short-circuit. Labelled by the \
166                 stage that bailed. Any non-zero value is an execution invariant failure.",
167                &["reason"],
168                registry,
169            ).unwrap(),
170        }
171    }
172}
173
174static METRICS: OnceCell<Metrics> = OnceCell::new();
175
176pub fn init_metrics(registry: &Registry) {
177    let _ = METRICS
178        .set(Metrics::new(registry))
179        // this happens many times during tests
180        .tap_err(|_| warn!("init_metrics registry overwritten"));
181}
182
183pub fn get_metrics() -> Option<&'static Metrics> {
184    METRICS.get()
185}
186
187tokio::task_local! {
188    static SERVER_TIMING: Arc<Mutex<Timer>>;
189}
190
191/// Create a new task-local ServerTiming context and run the provided future within it.
192/// Should be used at the top-most level of a request handler. Can be added to an axum router
193/// as a layer by using mysten_service::server_timing_middleware.
194pub async fn with_new_server_timing<T>(fut: impl Future<Output = T> + Send + 'static) -> T {
195    let timer = Arc::new(Mutex::new(Timer::new()));
196
197    let mut ret = None;
198    SERVER_TIMING
199        .scope(timer, async {
200            ret = Some(fut.await);
201        })
202        .await;
203
204    ret.unwrap()
205}
206
207/// Create a new task-local ServerTiming context and run the provided future within it.
208/// Only intended for use by macros within this module.
209pub async fn with_server_timing<T>(
210    timer: Arc<Mutex<Timer>>,
211    fut: impl Future<Output = T> + Send + 'static,
212) -> T {
213    let mut ret = None;
214    SERVER_TIMING
215        .scope(timer, async {
216            ret = Some(fut.await);
217        })
218        .await;
219
220    ret.unwrap()
221}
222
223/// Get the currently active ServerTiming context. Only intended for use by macros within this module.
224pub fn get_server_timing() -> Option<Arc<Mutex<Timer>>> {
225    SERVER_TIMING.try_with(|timer| timer.clone()).ok()
226}
227
228/// Add a new entry to the ServerTiming header.
229/// If the caller is not currently in a ServerTiming context (created with `with_new_server_timing`),
230/// an error is logged.
231pub fn add_server_timing(name: &str) {
232    let res = SERVER_TIMING.try_with(|timer| {
233        timer.lock().add(name);
234    });
235
236    if res.is_err() {
237        tracing::error!("Server timing context not found");
238    }
239}
240
241#[macro_export]
242macro_rules! monitored_future {
243    ($fut: expr) => {{ monitored_future!(futures, $fut, "", INFO, false) }};
244
245    ($metric: ident, $fut: expr, $name: expr, $logging_level: ident, $logging_enabled: expr) => {{
246        let location: &str = if $name.is_empty() {
247            concat!(file!(), ':', line!())
248        } else {
249            concat!(file!(), ':', $name)
250        };
251
252        async move {
253            let metrics = $crate::get_metrics();
254
255            let _metrics_guard = if let Some(m) = metrics {
256                m.$metric.with_label_values(&[location]).inc();
257                Some($crate::scopeguard::guard(m, |_| {
258                    m.$metric.with_label_values(&[location]).dec();
259                }))
260            } else {
261                None
262            };
263            let _logging_guard = if $logging_enabled {
264                Some($crate::scopeguard::guard((), |_| {
265                    tracing::event!(
266                        tracing::Level::$logging_level,
267                        "Future {} completed",
268                        location
269                    );
270                }))
271            } else {
272                None
273            };
274
275            if $logging_enabled {
276                tracing::event!(
277                    tracing::Level::$logging_level,
278                    "Spawning future {}",
279                    location
280                );
281            }
282
283            $fut.await
284        }
285    }};
286}
287
288#[macro_export]
289macro_rules! forward_server_timing_and_spawn {
290    ($fut: expr) => {
291        if let Some(timing) = $crate::get_server_timing() {
292            tokio::task::spawn(async move { $crate::with_server_timing(timing, $fut).await })
293        } else {
294            tokio::task::spawn($fut)
295        }
296    };
297}
298
299#[macro_export]
300macro_rules! spawn_monitored_task {
301    ($fut: expr) => {
302        $crate::forward_server_timing_and_spawn!($crate::monitored_future!(
303            tasks, $fut, "", INFO, false
304        ))
305    };
306}
307
308#[macro_export]
309macro_rules! spawn_logged_monitored_task {
310    ($fut: expr) => {
311        $crate::forward_server_timing_and_spawn!($crate::monitored_future!(
312            tasks, $fut, "", INFO, true
313        ))
314    };
315
316    ($fut: expr, $name: expr) => {
317        $crate::forward_server_timing_and_spawn!($crate::monitored_future!(
318            tasks, $fut, $name, INFO, true
319        ))
320    };
321
322    ($fut: expr, $name: expr, $logging_level: ident) => {
323        $crate::forward_server_timing_and_spawn!($crate::monitored_future!(
324            tasks,
325            $fut,
326            $name,
327            $logging_level,
328            true
329        ))
330    };
331}
332
333pub struct MonitoredScopeGuard {
334    metrics: &'static Metrics,
335    name: &'static str,
336    timer: Instant,
337}
338
339impl Drop for MonitoredScopeGuard {
340    fn drop(&mut self) {
341        self.metrics
342            .scope_duration_ns
343            .with_label_values(&[self.name])
344            .add(self.timer.elapsed().as_nanos() as i64);
345        self.metrics
346            .scope_entrance
347            .with_label_values(&[self.name])
348            .dec();
349    }
350}
351
352/// This function creates a named scoped object, that keeps track of
353/// - the total iterations where the scope is called in the `monitored_scope_iterations` metric.
354/// - and the total duration of the scope in the `monitored_scope_duration_ns` metric.
355///
356/// The monitored scope should be single threaded, e.g. the scoped object encompass the lifetime of
357/// a select loop or guarded by mutex.
358/// Then the rate of `monitored_scope_duration_ns`, converted to the unit of sec / sec, would be
359/// how full the single threaded scope is running.
360pub fn monitored_scope(name: &'static str) -> Option<MonitoredScopeGuard> {
361    let metrics = get_metrics();
362    if let Some(m) = metrics {
363        m.scope_iterations.with_label_values(&[name]).inc();
364        m.scope_entrance.with_label_values(&[name]).inc();
365        Some(MonitoredScopeGuard {
366            metrics: m,
367            name,
368            timer: Instant::now(),
369        })
370    } else {
371        None
372    }
373}
374
375pub trait MonitoredFutureExt: Future + Sized {
376    fn in_monitored_scope(self, name: &'static str) -> MonitoredScopeFuture<Self>;
377}
378
379impl<F: Future> MonitoredFutureExt for F {
380    fn in_monitored_scope(self, name: &'static str) -> MonitoredScopeFuture<Self> {
381        MonitoredScopeFuture {
382            f: Box::pin(self),
383            active_duration_metric: get_metrics()
384                .map(|m| m.future_active_duration_ns.with_label_values(&[name])),
385            _scope: monitored_scope(name),
386        }
387    }
388}
389
390pub struct MonitoredScopeFuture<F: Sized> {
391    f: Pin<Box<F>>,
392    active_duration_metric: Option<GenericGauge<AtomicI64>>,
393    _scope: Option<MonitoredScopeGuard>,
394}
395
396impl<F: Future> Future for MonitoredScopeFuture<F> {
397    type Output = F::Output;
398
399    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
400        let active_timer = Instant::now();
401        let ret = self.f.as_mut().poll(cx);
402        if let Some(m) = &self.active_duration_metric {
403            m.add(active_timer.elapsed().as_nanos() as i64);
404        }
405        ret
406    }
407}
408
409pub struct CancelMonitor<F: Sized> {
410    finished: bool,
411    inner: Pin<Box<F>>,
412}
413
414impl<F> CancelMonitor<F>
415where
416    F: Future,
417{
418    pub fn new(inner: F) -> Self {
419        Self {
420            finished: false,
421            inner: Box::pin(inner),
422        }
423    }
424
425    pub fn is_finished(&self) -> bool {
426        self.finished
427    }
428}
429
430impl<F> Future for CancelMonitor<F>
431where
432    F: Future,
433{
434    type Output = F::Output;
435
436    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
437        match self.inner.as_mut().poll(cx) {
438            Poll::Ready(output) => {
439                self.finished = true;
440                Poll::Ready(output)
441            }
442            Poll::Pending => Poll::Pending,
443        }
444    }
445}
446
447impl<F: Sized> Drop for CancelMonitor<F> {
448    fn drop(&mut self) {
449        if !self.finished {
450            Span::current().record("cancelled", true);
451        }
452    }
453}
454
455/// MonitorCancellation records a cancelled = true span attribute if the future it
456/// is decorating is dropped before completion. The cancelled attribute must be added
457/// at span creation, as you cannot add new attributes after the span is created.
458pub trait MonitorCancellation {
459    fn monitor_cancellation(self) -> CancelMonitor<Self>
460    where
461        Self: Sized + Future;
462}
463
464impl<T> MonitorCancellation for T
465where
466    T: Future,
467{
468    fn monitor_cancellation(self) -> CancelMonitor<Self> {
469        CancelMonitor::new(self)
470    }
471}
472
473pub type RegistryID = Uuid;
474
475/// A service to manage the prometheus registries. This service allow us to create
476/// a new Registry on demand and keep it accessible for processing/polling.
477/// The service can be freely cloned/shared across threads.
478#[derive(Clone, Debug)]
479pub struct RegistryService {
480    // Holds a Registry that is supposed to be used
481    default_registry: Registry,
482    registries_by_id: Arc<DashMap<Uuid, Registry>>,
483}
484
485impl RegistryService {
486    // Creates a new registry service and also adds the main/default registry that is supposed to
487    // be preserved and never get removed
488    pub fn new(default_registry: Registry) -> Self {
489        Self {
490            default_registry,
491            registries_by_id: Arc::new(DashMap::new()),
492        }
493    }
494
495    // Returns the default registry for the service that someone can use
496    // if they don't want to create a new one.
497    pub fn default_registry(&self) -> Registry {
498        self.default_registry.clone()
499    }
500
501    // Adds a new registry to the service. The corresponding RegistryID is returned so can later be
502    // used for removing the Registry. Method panics if we try to insert a registry with the same id.
503    // As this can be quite serious for the operation of the node we don't want to accidentally
504    // swap an existing registry - we expected a removal to happen explicitly.
505    pub fn add(&self, registry: Registry) -> RegistryID {
506        let registry_id = Uuid::new_v4();
507        if self
508            .registries_by_id
509            .insert(registry_id, registry)
510            .is_some()
511        {
512            panic!("Other Registry already detected for the same id {registry_id}");
513        }
514
515        registry_id
516    }
517
518    // Removes the registry from the service. If Registry existed then this method returns true,
519    // otherwise false is returned instead.
520    pub fn remove(&self, registry_id: RegistryID) -> bool {
521        self.registries_by_id.remove(&registry_id).is_some()
522    }
523
524    // Returns all the registries of the service
525    pub fn get_all(&self) -> Vec<Registry> {
526        let mut registries: Vec<Registry> = self
527            .registries_by_id
528            .iter()
529            .map(|r| r.value().clone())
530            .collect();
531        registries.push(self.default_registry.clone());
532
533        registries
534    }
535
536    // Returns all the metric families from the registries that a service holds.
537    pub fn gather_all(&self) -> Vec<prometheus::proto::MetricFamily> {
538        self.get_all().iter().flat_map(|r| r.gather()).collect()
539    }
540}
541
542/// Create a metric that measures the uptime from when this metric was constructed.
543/// The metric is labeled with:
544/// - 'process': the process type, differentiating between validator and fullnode
545/// - 'version': binary version, generally be of the format: 'semver-gitrevision'
546/// - 'chain_identifier': the identifier of the network which this process is part of
547pub fn uptime_metric(
548    process: &str,
549    version: &'static str,
550    chain_identifier: &str,
551) -> Box<dyn prometheus::core::Collector> {
552    let opts = prometheus::opts!("uptime", "uptime of the node service in seconds")
553        .variable_label("process")
554        .variable_label("version")
555        .variable_label("chain_identifier");
556
557    let start_time = std::time::Instant::now();
558    let uptime = move || start_time.elapsed().as_secs();
559    let metric = prometheus_closure_metric::ClosureMetric::new(
560        opts,
561        prometheus_closure_metric::ValueType::Counter,
562        uptime,
563        &[process, version, chain_identifier],
564    )
565    .unwrap();
566
567    Box::new(metric)
568}
569
570/// Similar to `uptime_metric`, but for the bridge node with different labels.
571/// Create a metric that measures the uptime from when this metric was constructed.
572/// The metric is labeled with:
573/// - 'process': the process type. We keep this label to be able to distinguish between different binaries.
574/// - 'version': binary version, generally be of the format: 'semver-gitrevision'
575/// - 'sui_chain_identifier': the identifier of sui network which this process is part of
576/// - 'eth_chain_identifier': the identifier of eth network which this process is part of
577/// - 'client_enabled': whether the bridge node is running as a client
578pub fn bridge_uptime_metric(
579    process: &str,
580    version: &'static str,
581    sui_chain_identifier: &str,
582    eth_chain_identifier: &str,
583    client_enabled: bool,
584) -> Box<dyn prometheus::core::Collector> {
585    let opts = prometheus::opts!("uptime", "uptime of the node service in seconds")
586        .variable_label("process")
587        .variable_label("version")
588        .variable_label("sui_chain_identifier")
589        .variable_label("eth_chain_identifier")
590        .variable_label("client_enabled");
591
592    let start_time = std::time::Instant::now();
593    let uptime = move || start_time.elapsed().as_secs();
594    let metric = prometheus_closure_metric::ClosureMetric::new(
595        opts,
596        prometheus_closure_metric::ValueType::Counter,
597        uptime,
598        &[
599            process,
600            version,
601            sui_chain_identifier,
602            eth_chain_identifier,
603            if client_enabled { "true" } else { "false" },
604        ],
605    )
606    .unwrap();
607
608    Box::new(metric)
609}
610
611pub const METRICS_ROUTE: &str = "/metrics";
612
613// Creates a new http server that has as a sole purpose to expose
614// and endpoint that prometheus agent can use to poll for the metrics.
615// A RegistryService is returned that can be used to get access in prometheus Registries.
616pub fn start_prometheus_server(addr: SocketAddr) -> RegistryService {
617    let registry = Registry::new();
618
619    let registry_service = RegistryService::new(registry);
620
621    if cfg!(msim) {
622        // prometheus uses difficult-to-support features such as TcpSocket::from_raw_fd(), so we
623        // can't yet run it in the simulator.
624        warn!("not starting prometheus server in simulator");
625        return registry_service;
626    }
627
628    let app = Router::new()
629        .route(METRICS_ROUTE, get(metrics))
630        .layer(Extension(registry_service.clone()));
631
632    tokio::spawn(async move {
633        let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
634        axum::serve(listener, app.into_make_service())
635            .await
636            .unwrap();
637    });
638
639    registry_service
640}
641
642pub async fn metrics(
643    Extension(registry_service): Extension<RegistryService>,
644) -> (StatusCode, String) {
645    let metrics_families = registry_service.gather_all();
646    match TextEncoder.encode_to_string(&metrics_families) {
647        Ok(metrics) => (StatusCode::OK, metrics),
648        Err(error) => (
649            StatusCode::INTERNAL_SERVER_ERROR,
650            format!("unable to encode metrics: {error}"),
651        ),
652    }
653}
654
655#[cfg(test)]
656mod tests {
657    use crate::RegistryService;
658    use prometheus::IntCounter;
659    use prometheus::Registry;
660
661    #[test]
662    fn registry_service() {
663        // GIVEN
664        let default_registry = Registry::new_custom(Some("default".to_string()), None).unwrap();
665
666        let registry_service = RegistryService::new(default_registry.clone());
667        let default_counter = IntCounter::new("counter", "counter_desc").unwrap();
668        default_counter.inc();
669        default_registry
670            .register(Box::new(default_counter))
671            .unwrap();
672
673        // AND add a metric to the default registry
674
675        // AND a registry with one metric
676        let registry_1 = Registry::new_custom(Some("narwhal".to_string()), None).unwrap();
677        registry_1
678            .register(Box::new(
679                IntCounter::new("counter_1", "counter_1_desc").unwrap(),
680            ))
681            .unwrap();
682
683        // WHEN
684        let registry_1_id = registry_service.add(registry_1);
685
686        // THEN
687        let mut metrics = registry_service.gather_all();
688        metrics.sort_by(|m1, m2| Ord::cmp(m1.name(), m2.name()));
689
690        assert_eq!(metrics.len(), 2);
691
692        let metric_default = metrics.remove(0);
693        assert_eq!(metric_default.name(), "default_counter");
694        assert_eq!(metric_default.help(), "counter_desc");
695
696        let metric_1 = metrics.remove(0);
697        assert_eq!(metric_1.name(), "narwhal_counter_1");
698        assert_eq!(metric_1.help(), "counter_1_desc");
699
700        // AND add a second registry with a metric
701        let registry_2 = Registry::new_custom(Some("sui".to_string()), None).unwrap();
702        registry_2
703            .register(Box::new(
704                IntCounter::new("counter_2", "counter_2_desc").unwrap(),
705            ))
706            .unwrap();
707        let _registry_2_id = registry_service.add(registry_2);
708
709        // THEN all the metrics should be returned
710        let mut metrics = registry_service.gather_all();
711        metrics.sort_by(|m1, m2| Ord::cmp(m1.name(), m2.name()));
712
713        assert_eq!(metrics.len(), 3);
714
715        let metric_default = metrics.remove(0);
716        assert_eq!(metric_default.name(), "default_counter");
717        assert_eq!(metric_default.help(), "counter_desc");
718
719        let metric_1 = metrics.remove(0);
720        assert_eq!(metric_1.name(), "narwhal_counter_1");
721        assert_eq!(metric_1.help(), "counter_1_desc");
722
723        let metric_2 = metrics.remove(0);
724        assert_eq!(metric_2.name(), "sui_counter_2");
725        assert_eq!(metric_2.help(), "counter_2_desc");
726
727        // AND remove first registry
728        assert!(registry_service.remove(registry_1_id));
729
730        // THEN metrics should now not contain metric of registry_1
731        let mut metrics = registry_service.gather_all();
732        metrics.sort_by(|m1, m2| Ord::cmp(m1.name(), m2.name()));
733
734        assert_eq!(metrics.len(), 2);
735
736        let metric_default = metrics.remove(0);
737        assert_eq!(metric_default.name(), "default_counter");
738        assert_eq!(metric_default.help(), "counter_desc");
739
740        let metric_1 = metrics.remove(0);
741        assert_eq!(metric_1.name(), "sui_counter_2");
742        assert_eq!(metric_1.help(), "counter_2_desc");
743    }
744}