iota_metrics/
runtime_metrics.rs1use std::{
14 sync::Arc,
15 time::{Duration, Instant},
16};
17
18use prometheus_filtered::{
19 HistogramVec, IntGaugeVec, MetricLevel, Registry, register_histogram_vec_with_registry,
20 register_int_gauge_vec_with_registry,
21};
22use tokio::runtime::Handle;
23
24const SAMPLE_INTERVAL: Duration = Duration::from_secs(5);
26const HEARTBEAT_INTERVAL: Duration = Duration::from_millis(100);
28
29pub struct RuntimeMonitorMetrics {
30 workers: IntGaugeVec,
31 alive_tasks: IntGaugeVec,
32 global_queue_depth: IntGaugeVec,
33 scheduler_lag_seconds: HistogramVec,
34}
35
36impl RuntimeMonitorMetrics {
37 pub fn new(registry: &Registry) -> Arc<Self> {
38 Arc::new(Self {
39 workers: register_int_gauge_vec_with_registry!(
40 "tokio_runtime_workers",
41 "Number of worker threads in the tokio runtime.",
42 &["runtime"],
43 registry;
44 MetricLevel::Warn,
45 )
46 .unwrap(),
47 alive_tasks: register_int_gauge_vec_with_registry!(
48 "tokio_runtime_alive_tasks",
49 "Number of alive (spawned, not yet completed) tasks in the tokio runtime.",
50 &["runtime"],
51 registry;
52 MetricLevel::Warn,
53 )
54 .unwrap(),
55 global_queue_depth: register_int_gauge_vec_with_registry!(
56 "tokio_runtime_global_queue_depth",
57 "Tasks waiting in the runtime's global injection queue. A persistently \
58 non-zero value means the workers cannot keep up with ready tasks.",
59 &["runtime"],
60 registry;
61 MetricLevel::Warn,
62 )
63 .unwrap(),
64 scheduler_lag_seconds: register_histogram_vec_with_registry!(
65 "tokio_runtime_scheduler_lag_seconds",
66 "Delay between when a fixed-interval heartbeat task was scheduled to wake \
67 and when it actually ran. High values indicate worker-thread starvation.",
68 &["runtime"],
69 vec![
70 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0
71 ],
72 registry;
73 MetricLevel::Warn,
74 )
75 .unwrap(),
76 })
77 }
78}
79
80pub fn start_runtime_monitor(
87 runtime: &'static str,
88 handle: &Handle,
89 metrics: Arc<RuntimeMonitorMetrics>,
90) {
91 {
93 let runtime_metrics = handle.metrics();
94 let workers = metrics.workers.with_label_values(&[runtime]);
95 let alive_tasks = metrics.alive_tasks.with_label_values(&[runtime]);
96 let global_queue_depth = metrics.global_queue_depth.with_label_values(&[runtime]);
97 handle.spawn(async move {
98 loop {
99 workers.set(runtime_metrics.num_workers() as i64);
100 alive_tasks.set(runtime_metrics.num_alive_tasks() as i64);
101 global_queue_depth.set(runtime_metrics.global_queue_depth() as i64);
102 tokio::time::sleep(SAMPLE_INTERVAL).await;
103 }
104 });
105 }
106
107 {
110 let lag = metrics.scheduler_lag_seconds.with_label_values(&[runtime]);
111 handle.spawn(async move {
112 loop {
113 let start = Instant::now();
114 tokio::time::sleep(HEARTBEAT_INTERVAL).await;
115 let lag_secs = start
116 .elapsed()
117 .saturating_sub(HEARTBEAT_INTERVAL)
118 .as_secs_f64();
119 lag.observe(lag_secs);
120 }
121 });
122 }
123}