1use 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
37pub 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
44pub 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
50pub 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 .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
191pub 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
207pub 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
223pub fn get_server_timing() -> Option<Arc<Mutex<Timer>>> {
225 SERVER_TIMING.try_with(|timer| timer.clone()).ok()
226}
227
228pub 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 $crate::spawn_with_server_timing($fut)
292 };
293}
294
295#[doc(hidden)]
301#[track_caller]
302pub fn spawn_with_server_timing<F>(fut: F) -> tokio::task::JoinHandle<F::Output>
303where
304 F: Future + Send + 'static,
305 F::Output: Send + 'static,
306{
307 let fut: Pin<Box<dyn Future<Output = F::Output> + Send>> = match get_server_timing() {
308 Some(timing) => Box::pin(with_server_timing(timing, fut)),
309 None => Box::pin(fut),
310 };
311 tokio::task::spawn(fut)
312}
313
314#[macro_export]
315macro_rules! spawn_monitored_task {
316 ($fut: expr) => {
317 $crate::forward_server_timing_and_spawn!($crate::monitored_future!(
318 tasks, $fut, "", INFO, false
319 ))
320 };
321}
322
323#[macro_export]
324macro_rules! spawn_logged_monitored_task {
325 ($fut: expr) => {
326 $crate::forward_server_timing_and_spawn!($crate::monitored_future!(
327 tasks, $fut, "", INFO, true
328 ))
329 };
330
331 ($fut: expr, $name: expr) => {
332 $crate::forward_server_timing_and_spawn!($crate::monitored_future!(
333 tasks, $fut, $name, INFO, true
334 ))
335 };
336
337 ($fut: expr, $name: expr, $logging_level: ident) => {
338 $crate::forward_server_timing_and_spawn!($crate::monitored_future!(
339 tasks,
340 $fut,
341 $name,
342 $logging_level,
343 true
344 ))
345 };
346}
347
348pub struct MonitoredScopeGuard {
349 metrics: &'static Metrics,
350 name: &'static str,
351 timer: Instant,
352}
353
354impl Drop for MonitoredScopeGuard {
355 fn drop(&mut self) {
356 self.metrics
357 .scope_duration_ns
358 .with_label_values(&[self.name])
359 .add(self.timer.elapsed().as_nanos() as i64);
360 self.metrics
361 .scope_entrance
362 .with_label_values(&[self.name])
363 .dec();
364 }
365}
366
367pub fn monitored_scope(name: &'static str) -> Option<MonitoredScopeGuard> {
376 let metrics = get_metrics();
377 if let Some(m) = metrics {
378 m.scope_iterations.with_label_values(&[name]).inc();
379 m.scope_entrance.with_label_values(&[name]).inc();
380 Some(MonitoredScopeGuard {
381 metrics: m,
382 name,
383 timer: Instant::now(),
384 })
385 } else {
386 None
387 }
388}
389
390pub trait MonitoredFutureExt: Future + Sized {
391 fn in_monitored_scope(self, name: &'static str) -> MonitoredScopeFuture<Self>;
392}
393
394impl<F: Future> MonitoredFutureExt for F {
395 fn in_monitored_scope(self, name: &'static str) -> MonitoredScopeFuture<Self> {
396 MonitoredScopeFuture {
397 f: Box::pin(self),
398 active_duration_metric: get_metrics()
399 .map(|m| m.future_active_duration_ns.with_label_values(&[name])),
400 _scope: monitored_scope(name),
401 }
402 }
403}
404
405pub struct MonitoredScopeFuture<F: Sized> {
406 f: Pin<Box<F>>,
407 active_duration_metric: Option<GenericGauge<AtomicI64>>,
408 _scope: Option<MonitoredScopeGuard>,
409}
410
411impl<F: Future> Future for MonitoredScopeFuture<F> {
412 type Output = F::Output;
413
414 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
415 let active_timer = Instant::now();
416 let ret = self.f.as_mut().poll(cx);
417 if let Some(m) = &self.active_duration_metric {
418 m.add(active_timer.elapsed().as_nanos() as i64);
419 }
420 ret
421 }
422}
423
424pub struct CancelMonitor<F: Sized> {
425 finished: bool,
426 inner: Pin<Box<F>>,
427}
428
429impl<F> CancelMonitor<F>
430where
431 F: Future,
432{
433 pub fn new(inner: F) -> Self {
434 Self {
435 finished: false,
436 inner: Box::pin(inner),
437 }
438 }
439
440 pub fn is_finished(&self) -> bool {
441 self.finished
442 }
443}
444
445impl<F> Future for CancelMonitor<F>
446where
447 F: Future,
448{
449 type Output = F::Output;
450
451 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
452 match self.inner.as_mut().poll(cx) {
453 Poll::Ready(output) => {
454 self.finished = true;
455 Poll::Ready(output)
456 }
457 Poll::Pending => Poll::Pending,
458 }
459 }
460}
461
462impl<F: Sized> Drop for CancelMonitor<F> {
463 fn drop(&mut self) {
464 if !self.finished {
465 Span::current().record("cancelled", true);
466 }
467 }
468}
469
470pub trait MonitorCancellation {
474 fn monitor_cancellation(self) -> CancelMonitor<Self>
475 where
476 Self: Sized + Future;
477}
478
479impl<T> MonitorCancellation for T
480where
481 T: Future,
482{
483 fn monitor_cancellation(self) -> CancelMonitor<Self> {
484 CancelMonitor::new(self)
485 }
486}
487
488pub type RegistryID = Uuid;
489
490#[derive(Clone, Debug)]
494pub struct RegistryService {
495 default_registry: Registry,
497 registries_by_id: Arc<DashMap<Uuid, Registry>>,
498}
499
500impl RegistryService {
501 pub fn new(default_registry: Registry) -> Self {
504 Self {
505 default_registry,
506 registries_by_id: Arc::new(DashMap::new()),
507 }
508 }
509
510 pub fn default_registry(&self) -> Registry {
513 self.default_registry.clone()
514 }
515
516 pub fn add(&self, registry: Registry) -> RegistryID {
521 let registry_id = Uuid::new_v4();
522 if self
523 .registries_by_id
524 .insert(registry_id, registry)
525 .is_some()
526 {
527 panic!("Other Registry already detected for the same id {registry_id}");
528 }
529
530 registry_id
531 }
532
533 pub fn remove(&self, registry_id: RegistryID) -> bool {
536 self.registries_by_id.remove(®istry_id).is_some()
537 }
538
539 pub fn get_all(&self) -> Vec<Registry> {
541 let mut registries: Vec<Registry> = self
542 .registries_by_id
543 .iter()
544 .map(|r| r.value().clone())
545 .collect();
546 registries.push(self.default_registry.clone());
547
548 registries
549 }
550
551 pub fn gather_all(&self) -> Vec<prometheus::proto::MetricFamily> {
553 self.get_all().iter().flat_map(|r| r.gather()).collect()
554 }
555}
556
557pub fn uptime_metric(
563 process: &str,
564 version: &'static str,
565 chain_identifier: &str,
566) -> Box<dyn prometheus::core::Collector> {
567 let opts = prometheus::opts!("uptime", "uptime of the node service in seconds")
568 .variable_label("process")
569 .variable_label("version")
570 .variable_label("chain_identifier");
571
572 let start_time = std::time::Instant::now();
573 let uptime = move || start_time.elapsed().as_secs();
574 let metric = prometheus_closure_metric::ClosureMetric::new(
575 opts,
576 prometheus_closure_metric::ValueType::Counter,
577 uptime,
578 &[process, version, chain_identifier],
579 )
580 .unwrap();
581
582 Box::new(metric)
583}
584
585pub fn bridge_uptime_metric(
594 process: &str,
595 version: &'static str,
596 sui_chain_identifier: &str,
597 eth_chain_identifier: &str,
598 client_enabled: bool,
599) -> Box<dyn prometheus::core::Collector> {
600 let opts = prometheus::opts!("uptime", "uptime of the node service in seconds")
601 .variable_label("process")
602 .variable_label("version")
603 .variable_label("sui_chain_identifier")
604 .variable_label("eth_chain_identifier")
605 .variable_label("client_enabled");
606
607 let start_time = std::time::Instant::now();
608 let uptime = move || start_time.elapsed().as_secs();
609 let metric = prometheus_closure_metric::ClosureMetric::new(
610 opts,
611 prometheus_closure_metric::ValueType::Counter,
612 uptime,
613 &[
614 process,
615 version,
616 sui_chain_identifier,
617 eth_chain_identifier,
618 if client_enabled { "true" } else { "false" },
619 ],
620 )
621 .unwrap();
622
623 Box::new(metric)
624}
625
626pub const METRICS_ROUTE: &str = "/metrics";
627
628pub fn start_prometheus_server(addr: SocketAddr) -> RegistryService {
632 let registry = Registry::new();
633
634 let registry_service = RegistryService::new(registry);
635
636 if cfg!(msim) {
637 warn!("not starting prometheus server in simulator");
640 return registry_service;
641 }
642
643 let app = Router::new()
644 .route(METRICS_ROUTE, get(metrics))
645 .layer(Extension(registry_service.clone()));
646
647 tokio::spawn(async move {
648 let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
649 axum::serve(listener, app.into_make_service())
650 .await
651 .unwrap();
652 });
653
654 registry_service
655}
656
657pub async fn metrics(
658 Extension(registry_service): Extension<RegistryService>,
659) -> (StatusCode, String) {
660 let metrics_families = registry_service.gather_all();
661 match TextEncoder.encode_to_string(&metrics_families) {
662 Ok(metrics) => (StatusCode::OK, metrics),
663 Err(error) => (
664 StatusCode::INTERNAL_SERVER_ERROR,
665 format!("unable to encode metrics: {error}"),
666 ),
667 }
668}
669
670#[cfg(test)]
671mod tests {
672 use crate::RegistryService;
673 use prometheus::IntCounter;
674 use prometheus::Registry;
675
676 #[test]
677 fn registry_service() {
678 let default_registry = Registry::new_custom(Some("default".to_string()), None).unwrap();
680
681 let registry_service = RegistryService::new(default_registry.clone());
682 let default_counter = IntCounter::new("counter", "counter_desc").unwrap();
683 default_counter.inc();
684 default_registry
685 .register(Box::new(default_counter))
686 .unwrap();
687
688 let registry_1 = Registry::new_custom(Some("narwhal".to_string()), None).unwrap();
692 registry_1
693 .register(Box::new(
694 IntCounter::new("counter_1", "counter_1_desc").unwrap(),
695 ))
696 .unwrap();
697
698 let registry_1_id = registry_service.add(registry_1);
700
701 let mut metrics = registry_service.gather_all();
703 metrics.sort_by(|m1, m2| Ord::cmp(m1.name(), m2.name()));
704
705 assert_eq!(metrics.len(), 2);
706
707 let metric_default = metrics.remove(0);
708 assert_eq!(metric_default.name(), "default_counter");
709 assert_eq!(metric_default.help(), "counter_desc");
710
711 let metric_1 = metrics.remove(0);
712 assert_eq!(metric_1.name(), "narwhal_counter_1");
713 assert_eq!(metric_1.help(), "counter_1_desc");
714
715 let registry_2 = Registry::new_custom(Some("sui".to_string()), None).unwrap();
717 registry_2
718 .register(Box::new(
719 IntCounter::new("counter_2", "counter_2_desc").unwrap(),
720 ))
721 .unwrap();
722 let _registry_2_id = registry_service.add(registry_2);
723
724 let mut metrics = registry_service.gather_all();
726 metrics.sort_by(|m1, m2| Ord::cmp(m1.name(), m2.name()));
727
728 assert_eq!(metrics.len(), 3);
729
730 let metric_default = metrics.remove(0);
731 assert_eq!(metric_default.name(), "default_counter");
732 assert_eq!(metric_default.help(), "counter_desc");
733
734 let metric_1 = metrics.remove(0);
735 assert_eq!(metric_1.name(), "narwhal_counter_1");
736 assert_eq!(metric_1.help(), "counter_1_desc");
737
738 let metric_2 = metrics.remove(0);
739 assert_eq!(metric_2.name(), "sui_counter_2");
740 assert_eq!(metric_2.help(), "counter_2_desc");
741
742 assert!(registry_service.remove(registry_1_id));
744
745 let mut metrics = registry_service.gather_all();
747 metrics.sort_by(|m1, m2| Ord::cmp(m1.name(), m2.name()));
748
749 assert_eq!(metrics.len(), 2);
750
751 let metric_default = metrics.remove(0);
752 assert_eq!(metric_default.name(), "default_counter");
753 assert_eq!(metric_default.help(), "counter_desc");
754
755 let metric_1 = metrics.remove(0);
756 assert_eq!(metric_1.name(), "sui_counter_2");
757 assert_eq!(metric_1.help(), "counter_2_desc");
758 }
759}