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 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
352pub 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
455pub 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#[derive(Clone, Debug)]
479pub struct RegistryService {
480 default_registry: Registry,
482 registries_by_id: Arc<DashMap<Uuid, Registry>>,
483}
484
485impl RegistryService {
486 pub fn new(default_registry: Registry) -> Self {
489 Self {
490 default_registry,
491 registries_by_id: Arc::new(DashMap::new()),
492 }
493 }
494
495 pub fn default_registry(&self) -> Registry {
498 self.default_registry.clone()
499 }
500
501 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 pub fn remove(&self, registry_id: RegistryID) -> bool {
521 self.registries_by_id.remove(®istry_id).is_some()
522 }
523
524 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 pub fn gather_all(&self) -> Vec<prometheus::proto::MetricFamily> {
538 self.get_all().iter().flat_map(|r| r.gather()).collect()
539 }
540}
541
542pub 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
570pub 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
613pub 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 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 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 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 let registry_1_id = registry_service.add(registry_1);
685
686 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 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 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 assert!(registry_service.remove(registry_1_id));
729
730 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}