1use std::{
6 future::Future,
7 net::SocketAddr,
8 path::Path,
9 pin::Pin,
10 sync::Arc,
11 task::{Context, Poll},
12 time::Instant,
13};
14
15use axum::{
16 Router,
17 extract::{Extension, Request},
18 http::StatusCode,
19 middleware::Next,
20 response::Response,
21 routing::get,
22};
23use dashmap::DashMap;
24use once_cell::sync::OnceCell;
25use parking_lot::Mutex;
26use prometheus_filtered::{
27 Filter, Histogram, IntCounterVec, IntGaugeVec, Registry, TextEncoder,
28 core::{AtomicI64, GenericGauge},
29 register_histogram_with_registry, register_int_counter_vec_with_registry,
30 register_int_gauge_vec_with_registry,
31};
32pub use scopeguard;
33use simple_server_timing_header::Timer;
34use tap::TapFallible;
35use tracing::{Span, warn};
36use uuid::Uuid;
37
38mod guards;
39pub mod hardware_metrics;
40pub mod histogram;
41pub mod metered_channel;
42pub mod metric_groups;
43pub mod metrics_network;
44pub mod monitored_mpsc;
45pub mod thread_stall_monitor;
46pub use guards::*;
47pub use metric_groups::{MetricGroups, MetricLevel};
48
49pub const TX_TYPE_SINGLE_WRITER_TX: &str = "single_writer";
50pub const TX_TYPE_SHARED_OBJ_TX: &str = "shared_object";
51
52pub const SUBSECOND_LATENCY_SEC_BUCKETS: &[f64] = &[
53 0.005, 0.01, 0.02, 0.03, 0.05, 0.075, 0.1, 0.2, 0.3, 0.5, 0.7, 1.,
54];
55
56pub const COARSE_LATENCY_SEC_BUCKETS: &[f64] = &[
57 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.,
58];
59
60pub const LATENCY_SEC_BUCKETS: &[f64] = &[
61 0.001, 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.6,
62 0.7, 0.8, 0.9, 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.,
63 6., 7., 8., 9., 10., 15., 20., 25., 30., 60., 90.,
64];
65
66pub const COUNT_BUCKETS: &[f64] = &[
67 2., 5., 10., 20., 50., 100., 200., 500., 1000., 2000., 5000., 10000.,
68];
69
70pub const BYTES_BUCKETS: &[f64] = &[
71 1024., 4096., 16384., 65536., 262144., 524288., 1048576., 2097152., 4194304., 8388608.,
72 16777216., 33554432., 67108864.,
73];
74
75#[derive(Debug)]
76pub struct Metrics {
77 pub tasks: IntGaugeVec,
78 pub futures: IntGaugeVec,
79 pub channel_inflight: IntGaugeVec,
80 pub channel_sent: IntGaugeVec,
81 pub channel_received: IntGaugeVec,
82 pub future_active_duration_ns: IntGaugeVec,
83 pub scope_iterations: IntGaugeVec,
84 pub scope_duration_ns: IntGaugeVec,
85 pub scope_entrance: IntGaugeVec,
86 pub thread_stall_duration_sec: Histogram,
87 pub system_invariant_violations: IntCounterVec,
88}
89
90impl Metrics {
91 fn new(registry: &Registry) -> Self {
99 Self {
100 tasks: register_int_gauge_vec_with_registry!(
101 "monitored_tasks",
102 "Number of running tasks per callsite.",
103 &["callsite"],
104 registry;
105 MetricLevel::Warn,
106 )
107 .unwrap(),
108 futures: register_int_gauge_vec_with_registry!(
109 "monitored_futures",
110 "Number of pending futures per callsite.",
111 &["callsite"],
112 registry;
113 MetricLevel::Warn,
114 )
115 .unwrap(),
116 channel_inflight: register_int_gauge_vec_with_registry!(
117 "monitored_channel_inflight",
118 "Inflight items in channels.",
119 &["name"],
120 registry;
121 MetricLevel::Warn,
122 )
123 .unwrap(),
124 channel_sent: register_int_gauge_vec_with_registry!(
125 "monitored_channel_sent",
126 "Sent items in channels.",
127 &["name"],
128 registry,
129 )
130 .unwrap(),
131 channel_received: register_int_gauge_vec_with_registry!(
132 "monitored_channel_received",
133 "Received items in channels.",
134 &["name"],
135 registry,
136 )
137 .unwrap(),
138 future_active_duration_ns: register_int_gauge_vec_with_registry!(
139 "monitored_future_active_duration_ns",
140 "Total duration in nanosecs where the monitored future is active (consuming CPU time)",
141 &["name"],
142 registry,
143 )
144 .unwrap(),
145 scope_entrance: register_int_gauge_vec_with_registry!(
146 "monitored_scope_entrance",
147 "Number of entrance in the scope.",
148 &["name"],
149 registry,
150 )
151 .unwrap(),
152 scope_iterations: register_int_gauge_vec_with_registry!(
153 "monitored_scope_iterations",
154 "Total number of times where the monitored scope runs",
155 &["name"],
156 registry,
157 )
158 .unwrap(),
159 scope_duration_ns: register_int_gauge_vec_with_registry!(
160 "monitored_scope_duration_ns",
161 "Total duration in nanosecs where the monitored scope is running",
162 &["name"],
163 registry,
164 )
165 .unwrap(),
166 thread_stall_duration_sec: register_histogram_with_registry!(
167 "thread_stall_duration_sec",
168 "Duration of thread stalls in seconds.",
169 registry,
170 )
171 .unwrap(),
172 system_invariant_violations: register_int_counter_vec_with_registry!(
173 "system_invariant_violations",
174 "Number of system invariant violations",
175 &["name"],
176 registry,
177 ).unwrap(),
178 }
179 }
180}
181
182static METRICS: OnceCell<Metrics> = OnceCell::new();
183
184pub fn init_metrics(registry: &Registry) {
190 let _ = METRICS
191 .set(Metrics::new(registry))
192 .tap_err(|_| warn!("init_metrics registry overwritten"));
194}
195
196pub fn get_metrics() -> Option<&'static Metrics> {
198 METRICS.get()
199}
200
201tokio::task_local! {
202 static SERVER_TIMING: Arc<Mutex<Timer>>;
203}
204
205pub async fn with_new_server_timing<T>(fut: impl Future<Output = T> + Send + 'static) -> T {
210 let timer = Arc::new(Mutex::new(Timer::new()));
211
212 let mut ret = None;
213 SERVER_TIMING
214 .scope(timer, async {
215 ret = Some(fut.await);
216 })
217 .await;
218
219 ret.unwrap()
220}
221
222pub fn server_timing_header_key() -> &'static str {
224 Timer::header_key()
225}
226
227pub fn finish_and_set_server_timing_header(headers: &mut http::HeaderMap) {
230 let Some(timer) = get_server_timing() else {
231 return;
232 };
233 let header_value = {
234 let mut timer = timer.lock();
235 timer.add("finish_request");
236 timer.header_value()
237 };
238 if let Ok(value) = http::HeaderValue::try_from(header_value) {
239 headers.insert(server_timing_header_key(), value);
240 }
241}
242
243pub async fn server_timing_middleware(request: Request, next: Next) -> Response {
244 with_new_server_timing(async move {
245 let mut response = next.run(request).await;
246 finish_and_set_server_timing_header(response.headers_mut());
247 response
248 })
249 .await
250}
251
252pub async fn with_server_timing<T>(
255 timer: Arc<Mutex<Timer>>,
256 fut: impl Future<Output = T> + Send + 'static,
257) -> T {
258 let mut ret = None;
259 SERVER_TIMING
260 .scope(timer, async {
261 ret = Some(fut.await);
262 })
263 .await;
264
265 ret.unwrap()
266}
267
268pub fn get_server_timing() -> Option<Arc<Mutex<Timer>>> {
271 SERVER_TIMING.try_with(|timer| timer.clone()).ok()
272}
273
274pub fn add_server_timing(name: &str) {
278 let res = SERVER_TIMING.try_with(|timer| {
279 timer.lock().add(name);
280 });
281
282 if res.is_err() {
283 tracing::error!("Server timing context not found");
284 }
285}
286
287#[macro_export]
288macro_rules! monitored_future {
289 ($fut: expr) => {{ monitored_future!(futures, $fut, "", INFO, false) }};
290
291 ($metric: ident, $fut: expr, $name: expr, $logging_level: ident, $logging_enabled: expr) => {{
292 let location: &str = if $name.is_empty() {
293 concat!(file!(), ':', line!())
294 } else {
295 concat!(file!(), ':', $name)
296 };
297
298 async move {
299 let metrics = $crate::get_metrics();
300
301 let _metrics_guard = if let Some(m) = metrics {
302 m.$metric.with_label_values(&[location]).inc();
303 Some($crate::scopeguard::guard(m, |_| {
304 m.$metric.with_label_values(&[location]).dec();
305 }))
306 } else {
307 None
308 };
309 let _logging_guard = if $logging_enabled {
310 Some($crate::scopeguard::guard((), |_| {
311 tracing::event!(
312 tracing::Level::$logging_level,
313 "Future {} completed",
314 location
315 );
316 }))
317 } else {
318 None
319 };
320
321 if $logging_enabled {
322 tracing::event!(
323 tracing::Level::$logging_level,
324 "Spawning future {}",
325 location
326 );
327 }
328
329 $fut.await
330 }
331 }};
332}
333
334#[macro_export]
335macro_rules! forward_server_timing_and_spawn {
336 ($fut: expr) => {
337 if let Some(timing) = $crate::get_server_timing() {
338 tokio::task::spawn(async move { $crate::with_server_timing(timing, $fut).await })
339 } else {
340 tokio::task::spawn($fut)
341 }
342 };
343}
344
345#[macro_export]
346macro_rules! spawn_monitored_task {
347 ($fut: expr) => {
348 $crate::forward_server_timing_and_spawn!($crate::monitored_future!(
349 tasks, $fut, "", INFO, false
350 ))
351 };
352}
353
354#[macro_export]
355macro_rules! spawn_logged_monitored_task {
356 ($fut: expr) => {
357 $crate::forward_server_timing_and_spawn!($crate::monitored_future!(
358 tasks, $fut, "", INFO, true
359 ))
360 };
361
362 ($fut: expr, $name: expr) => {
363 $crate::forward_server_timing_and_spawn!($crate::monitored_future!(
364 tasks, $fut, $name, INFO, true
365 ))
366 };
367
368 ($fut: expr, $name: expr, $logging_level: ident) => {
369 $crate::forward_server_timing_and_spawn!($crate::monitored_future!(
370 tasks,
371 $fut,
372 $name,
373 $logging_level,
374 true
375 ))
376 };
377}
378
379pub struct MonitoredScopeGuard {
380 metrics: &'static Metrics,
381 name: &'static str,
382 timer: Instant,
383}
384
385impl Drop for MonitoredScopeGuard {
386 fn drop(&mut self) {
387 self.metrics
388 .scope_duration_ns
389 .with_label_values(&[self.name])
390 .add(self.timer.elapsed().as_nanos() as i64);
391 self.metrics
392 .scope_entrance
393 .with_label_values(&[self.name])
394 .dec();
395 }
396}
397
398pub fn monitored_scope(name: &'static str) -> Option<MonitoredScopeGuard> {
409 let metrics = get_metrics();
410 if let Some(m) = metrics {
411 m.scope_iterations.with_label_values(&[name]).inc();
412 m.scope_entrance.with_label_values(&[name]).inc();
413 Some(MonitoredScopeGuard {
414 metrics: m,
415 name,
416 timer: Instant::now(),
417 })
418 } else {
419 None
420 }
421}
422
423pub trait MonitoredFutureExt: Future + Sized {
428 fn in_monitored_scope(self, name: &'static str) -> MonitoredScopeFuture<Self>;
432}
433
434impl<F: Future> MonitoredFutureExt for F {
435 fn in_monitored_scope(self, name: &'static str) -> MonitoredScopeFuture<Self> {
436 MonitoredScopeFuture {
437 f: Box::pin(self),
438 active_duration_metric: get_metrics()
439 .map(|m| m.future_active_duration_ns.with_label_values(&[name])),
440 _scope: monitored_scope(name),
441 }
442 }
443}
444
445pub struct MonitoredScopeFuture<F: Sized> {
450 f: Pin<Box<F>>,
451 active_duration_metric: Option<GenericGauge<AtomicI64>>,
452 _scope: Option<MonitoredScopeGuard>,
453}
454
455impl<F: Future> Future for MonitoredScopeFuture<F> {
456 type Output = F::Output;
457
458 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
459 let active_timer = Instant::now();
460 let ret = self.f.as_mut().poll(cx);
461 if let Some(m) = &self.active_duration_metric {
462 m.add(active_timer.elapsed().as_nanos() as i64);
463 }
464 ret
465 }
466}
467
468pub struct CancelMonitor<F: Sized> {
473 finished: bool,
474 inner: Pin<Box<F>>,
475}
476
477impl<F> CancelMonitor<F>
478where
479 F: Future,
480{
481 pub fn new(inner: F) -> Self {
484 Self {
485 finished: false,
486 inner: Box::pin(inner),
487 }
488 }
489
490 pub fn is_finished(&self) -> bool {
492 self.finished
493 }
494}
495
496impl<F> Future for CancelMonitor<F>
497where
498 F: Future,
499{
500 type Output = F::Output;
501
502 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
507 match self.inner.as_mut().poll(cx) {
508 Poll::Ready(output) => {
509 self.finished = true;
510 Poll::Ready(output)
511 }
512 Poll::Pending => Poll::Pending,
513 }
514 }
515}
516
517impl<F: Sized> Drop for CancelMonitor<F> {
518 fn drop(&mut self) {
523 if !self.finished {
524 Span::current().record("cancelled", true);
525 }
526 }
527}
528
529pub trait MonitorCancellation {
534 fn monitor_cancellation(self) -> CancelMonitor<Self>
535 where
536 Self: Sized + Future;
537}
538
539impl<T> MonitorCancellation for T
540where
541 T: Future,
542{
543 fn monitor_cancellation(self) -> CancelMonitor<Self> {
544 CancelMonitor::new(self)
545 }
546}
547
548pub type RegistryID = Uuid;
549
550#[derive(Clone)]
554pub struct RegistryService {
555 default_registry: Registry,
557 registries_by_id: Arc<DashMap<Uuid, Registry>>,
558 filter: Arc<Filter>,
559}
560
561impl RegistryService {
562 pub fn new(default_registry: Registry) -> Self {
565 Self {
566 filter: default_registry.filter(),
567 default_registry,
568 registries_by_id: Arc::new(DashMap::new()),
569 }
570 }
571
572 pub fn default_registry(&self) -> Registry {
575 self.default_registry.clone()
576 }
577
578 pub fn new_registry_custom(
582 &self,
583 prefix: Option<String>,
584 labels: Option<std::collections::HashMap<String, String>>,
585 ) -> prometheus_filtered::Result<Registry> {
586 Registry::new_custom(prefix, labels, Some(self.filter.clone()))
587 }
588
589 pub fn add(&self, registry: Registry) -> RegistryID {
595 let registry_id = Uuid::new_v4();
596 if self
597 .registries_by_id
598 .insert(registry_id, registry)
599 .is_some()
600 {
601 panic!("Other Registry already detected for the same id {registry_id}");
602 }
603
604 registry_id
605 }
606
607 pub fn remove(&self, registry_id: RegistryID) -> bool {
610 self.registries_by_id.remove(®istry_id).is_some()
611 }
612
613 pub fn get_all(&self) -> Vec<Registry> {
615 let mut registries: Vec<Registry> = self
616 .registries_by_id
617 .iter()
618 .map(|r| r.value().clone())
619 .collect();
620 registries.push(self.default_registry.clone());
621
622 registries
623 }
624
625 pub fn gather_all(&self) -> Vec<prometheus_filtered::proto::MetricFamily> {
627 self.get_all().iter().flat_map(|r| r.gather()).collect()
628 }
629}
630
631pub fn uptime_metric(
640 process: &str,
641 version: &'static str,
642 chain_identifier: &str,
643) -> Box<dyn prometheus_filtered::core::Collector> {
644 let opts = prometheus_filtered::opts!("uptime", "uptime of the node service in seconds")
645 .variable_label("process")
646 .variable_label("version")
647 .variable_label("chain_identifier")
648 .variable_label("os_version")
649 .variable_label("is_docker");
650
651 let start_time = std::time::Instant::now();
652 let uptime = move || start_time.elapsed().as_secs();
653 let metric = prometheus_closure_metric::ClosureMetric::new(
654 opts,
655 prometheus_closure_metric::ValueType::Counter,
656 uptime,
657 &[
658 process,
659 version,
660 chain_identifier,
661 &sysinfo::System::long_os_version()
662 .unwrap_or_else(|| "os_version_unavailable".to_string()),
663 &is_running_in_docker().to_string(),
664 ],
665 )
666 .unwrap();
667
668 Box::new(metric)
669}
670
671pub fn is_running_in_docker() -> bool {
672 Path::new("/.dockerenv").exists()
675}
676
677pub const METRICS_ROUTE: &str = "/metrics";
678
679pub fn start_prometheus_server(addr: SocketAddr) -> RegistryService {
684 start_prometheus_server_with_filter(addr, Filter::resolve(None))
685}
686
687pub fn start_prometheus_server_with_filter(addr: SocketAddr, filter: Filter) -> RegistryService {
688 let registry = Registry::new_custom(None, None, Some(Arc::new(filter)))
691 .expect("unprefixed registry is infallible");
692
693 let registry_service = RegistryService::new(registry);
694
695 if cfg!(msim) {
696 warn!("not starting prometheus server in simulator");
699 return registry_service;
700 }
701
702 let app = Router::new()
703 .route(METRICS_ROUTE, get(metrics))
704 .layer(Extension(registry_service.clone()));
705
706 tokio::spawn(async move {
707 let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
708 axum::serve(listener, app.into_make_service())
709 .await
710 .unwrap();
711 });
712
713 registry_service
714}
715
716pub async fn metrics(
724 Extension(registry_service): Extension<RegistryService>,
725) -> (StatusCode, String) {
726 let metrics_families = registry_service.gather_all();
727 match TextEncoder.encode_to_string(&metrics_families) {
728 Ok(metrics) => (StatusCode::OK, metrics),
729 Err(error) => (
730 StatusCode::INTERNAL_SERVER_ERROR,
731 format!("unable to encode metrics: {error}"),
732 ),
733 }
734}
735
736#[cfg(test)]
737mod tests {
738 use prometheus_filtered::{IntCounter, Registry};
739
740 use crate::RegistryService;
741
742 #[test]
743 fn registry_service() {
744 let default_registry =
746 Registry::new_custom(Some("default".to_string()), None, None).unwrap();
747
748 let registry_service = RegistryService::new(default_registry.clone());
749 let default_counter = IntCounter::new("counter", "counter_desc").unwrap();
750 default_counter.inc();
751 default_registry
752 .register(Box::new(default_counter))
753 .unwrap();
754
755 let registry_1 = Registry::new_custom(Some("iota".to_string()), None, None).unwrap();
759 registry_1
760 .register(Box::new(
761 IntCounter::new("counter_1", "counter_1_desc").unwrap(),
762 ))
763 .unwrap();
764
765 let registry_1_id = registry_service.add(registry_1);
767
768 let mut metrics = registry_service.gather_all();
770 metrics.sort_by(|m1, m2| Ord::cmp(m1.name(), m2.name()));
771
772 assert_eq!(metrics.len(), 2);
773
774 let metric_default = metrics.remove(0);
775 assert_eq!(metric_default.name(), "default_counter");
776 assert_eq!(metric_default.help(), "counter_desc");
777
778 let metric_1: prometheus_filtered::proto::MetricFamily = metrics.remove(0);
779 assert_eq!(metric_1.name(), "iota_counter_1");
780 assert_eq!(metric_1.help(), "counter_1_desc");
781
782 let registry_2 = Registry::new_custom(Some("iota".to_string()), None, None).unwrap();
784 registry_2
785 .register(Box::new(
786 IntCounter::new("counter_2", "counter_2_desc").unwrap(),
787 ))
788 .unwrap();
789 let _registry_2_id = registry_service.add(registry_2);
790
791 let mut metrics = registry_service.gather_all();
793 metrics.sort_by(|m1, m2| Ord::cmp(m1.name(), m2.name()));
794
795 assert_eq!(metrics.len(), 3);
796
797 let metric_default = metrics.remove(0);
798 assert_eq!(metric_default.name(), "default_counter");
799 assert_eq!(metric_default.help(), "counter_desc");
800
801 let metric_1 = metrics.remove(0);
802 assert_eq!(metric_1.name(), "iota_counter_1");
803 assert_eq!(metric_1.help(), "counter_1_desc");
804
805 let metric_2 = metrics.remove(0);
806 assert_eq!(metric_2.name(), "iota_counter_2");
807 assert_eq!(metric_2.help(), "counter_2_desc");
808
809 assert!(registry_service.remove(registry_1_id));
811
812 let mut metrics = registry_service.gather_all();
814 metrics.sort_by(|m1, m2| Ord::cmp(m1.name(), m2.name()));
815
816 assert_eq!(metrics.len(), 2);
817
818 let metric_default = metrics.remove(0);
819 assert_eq!(metric_default.name(), "default_counter");
820 assert_eq!(metric_default.help(), "counter_desc");
821
822 let metric_1 = metrics.remove(0);
823 assert_eq!(metric_1.name(), "iota_counter_2");
824 assert_eq!(metric_1.help(), "counter_2_desc");
825 }
826}