saluki_app/metrics/
mod.rs1use std::{sync::OnceLock, time::Duration};
4
5use async_trait::async_trait;
6use metrics::{gauge, Gauge, Level};
7use saluki_common::sync::shutdown::ShutdownHandle;
8use saluki_core::{
9 observability::metrics::MetricsFlusherWorker,
10 runtime::{InitializationError, Supervisable, SupervisorFuture},
11};
12use saluki_error::GenericError;
13use saluki_metrics::static_metrics;
14use tokio::{runtime::Handle, select, time::sleep};
15
16mod api;
17pub use self::api::{MetricsAPIHandler, MetricsOverrideWorker};
18
19pub(crate) struct MetricsWorkers {
26 pub runtime: RuntimeMetricsWorker,
27 pub flusher: MetricsFlusherWorker,
28 pub override_processor: MetricsOverrideWorker,
29}
30
31pub(crate) async fn initialize_metrics(
44 metrics_prefix: impl Into<String>, default_level: Level,
45) -> Result<MetricsWorkers, GenericError> {
46 let (filter_handle, flusher) =
52 saluki_core::observability::metrics::initialize_metrics(metrics_prefix.into(), default_level).await?;
53
54 let override_processor = MetricsOverrideWorker::new(filter_handle);
55
56 let runtime = RuntimeMetricsWorker::new("primary", Handle::current());
59
60 Ok(MetricsWorkers {
61 runtime,
62 flusher,
63 override_processor,
64 })
65}
66
67pub fn emit_startup_metrics() {
74 static RUNNING: OnceLock<Gauge> = OnceLock::new();
76
77 let app_details = saluki_metadata::get_app_details();
78 let app_version = if app_details.is_dev_build() {
79 format!("{}-dev-{}", app_details.version().raw(), app_details.git_hash(),)
80 } else {
81 app_details.version().raw().to_string()
82 };
83
84 let running = RUNNING.get_or_init(|| gauge!("running", "version" => app_version));
86 running.set(1.0);
87}
88
89pub async fn collect_runtime_metrics(runtime_id: &str, handle: Handle) {
94 let runtime_metrics = RuntimeMetrics::with_workers(runtime_id, handle.metrics().num_workers());
96
97 loop {
99 let latest_runtime_metrics = handle.metrics();
100 runtime_metrics.update(&latest_runtime_metrics);
101
102 sleep(Duration::from_secs(5)).await;
103 }
104}
105
106pub struct RuntimeMetricsWorker {
111 runtime_id: String,
112 handle: Handle,
113}
114
115impl RuntimeMetricsWorker {
116 pub fn new<S: Into<String>>(runtime_id: S, handle: Handle) -> Self {
118 Self {
119 runtime_id: runtime_id.into(),
120 handle,
121 }
122 }
123}
124
125#[async_trait]
126impl Supervisable for RuntimeMetricsWorker {
127 fn name(&self) -> &str {
128 "tokio-runtime-metrics-collector"
129 }
130
131 async fn initialize(&self, process_shutdown: ShutdownHandle) -> Result<SupervisorFuture, InitializationError> {
132 let runtime_id = self.runtime_id.clone();
133 let handle = self.handle.clone();
134
135 Ok(Box::pin(async move {
136 select! {
137 _ = process_shutdown => {},
138 _ = collect_runtime_metrics(&runtime_id, handle) => {},
139 }
140
141 Ok(())
142 }))
143 }
144}
145
146#[static_metrics(prefix = runtime_worker, labels(runtime_id, worker_idx))]
147#[derive(Clone)]
148struct WorkerMetrics {
149 #[metric(level = trace)]
150 local_queue_depth: Gauge,
151 #[metric(level = trace)]
152 local_schedule_count: Gauge,
153 #[metric(level = trace)]
154 mean_poll_time: Gauge,
155 #[metric(level = trace)]
156 noop_count: Gauge,
157 #[metric(level = trace)]
158 overflow_count: Gauge,
159 #[metric(level = trace)]
160 park_count: Gauge,
161 #[metric(level = trace)]
162 park_unpark_count: Gauge,
163 #[metric(level = trace)]
164 poll_count: Gauge,
165 #[metric(level = trace)]
166 steal_count: Gauge,
167 #[metric(level = trace)]
168 steal_operations: Gauge,
169 #[metric(level = trace)]
170 total_busy_duration: Gauge,
171}
172
173impl WorkerMetrics {
174 fn with_worker_idx(runtime_id: &str, worker_idx: usize) -> Self {
175 Self::new(runtime_id, worker_idx)
176 }
177
178 fn update(&self, worker_idx: usize, metrics: &tokio::runtime::RuntimeMetrics) {
179 self.local_queue_depth()
180 .set(metrics.worker_local_queue_depth(worker_idx) as f64);
181 self.local_schedule_count()
182 .set(metrics.worker_local_schedule_count(worker_idx) as f64);
183 self.mean_poll_time()
184 .set(metrics.worker_mean_poll_time(worker_idx).as_nanos() as f64);
185 self.noop_count().set(metrics.worker_noop_count(worker_idx) as f64);
186 self.overflow_count()
187 .set(metrics.worker_overflow_count(worker_idx) as f64);
188 self.park_count().set(metrics.worker_park_count(worker_idx) as f64);
189 self.park_unpark_count()
190 .set(metrics.worker_park_unpark_count(worker_idx) as f64);
191 self.poll_count().set(metrics.worker_poll_count(worker_idx) as f64);
192 self.steal_count().set(metrics.worker_steal_count(worker_idx) as f64);
193 self.steal_operations()
194 .set(metrics.worker_steal_operations(worker_idx) as f64);
195 self.total_busy_duration()
196 .set(metrics.worker_total_busy_duration(worker_idx).as_nanos() as f64);
197 }
198}
199
200#[static_metrics(prefix = runtime, labels(runtime_id))]
201#[derive(Clone)]
202struct GlobalRuntimeMetrics {
203 #[metric(level = debug)]
204 num_alive_tasks: Gauge,
205 #[metric(level = debug)]
206 blocking_queue_depth: Gauge,
207 #[metric(level = debug)]
208 budget_forced_yield_count: Gauge,
209 #[metric(level = debug)]
210 global_queue_depth: Gauge,
211 #[metric(level = debug)]
212 io_driver_fd_deregistered_count: Gauge,
213 #[metric(level = debug)]
214 io_driver_fd_registered_count: Gauge,
215 #[metric(level = debug)]
216 io_driver_ready_count: Gauge,
217 #[metric(level = debug)]
218 num_blocking_threads: Gauge,
219 #[metric(level = debug)]
220 num_idle_blocking_threads: Gauge,
221 #[metric(level = debug)]
222 num_workers: Gauge,
223 #[metric(level = debug)]
224 remote_schedule_count: Gauge,
225 #[metric(level = debug)]
226 spawned_tasks_count: Gauge,
227}
228
229struct RuntimeMetrics {
230 global: GlobalRuntimeMetrics,
231 workers: Vec<WorkerMetrics>,
232}
233
234impl RuntimeMetrics {
235 fn with_workers(runtime_id: &str, workers_len: usize) -> Self {
236 let mut workers = Vec::with_capacity(workers_len);
237 for i in 0..workers_len {
238 workers.push(WorkerMetrics::with_worker_idx(runtime_id, i));
239 }
240
241 Self {
242 global: GlobalRuntimeMetrics::new(runtime_id),
243 workers,
244 }
245 }
246
247 fn update(&self, metrics: &tokio::runtime::RuntimeMetrics) {
248 self.global.num_alive_tasks().set(metrics.num_alive_tasks() as f64);
249 self.global
250 .blocking_queue_depth()
251 .set(metrics.blocking_queue_depth() as f64);
252 self.global
253 .budget_forced_yield_count()
254 .set(metrics.budget_forced_yield_count() as f64);
255 self.global
256 .global_queue_depth()
257 .set(metrics.global_queue_depth() as f64);
258 self.global
259 .io_driver_fd_deregistered_count()
260 .set(metrics.io_driver_fd_deregistered_count() as f64);
261 self.global
262 .io_driver_fd_registered_count()
263 .set(metrics.io_driver_fd_registered_count() as f64);
264 self.global
265 .io_driver_ready_count()
266 .set(metrics.io_driver_ready_count() as f64);
267 self.global
268 .num_blocking_threads()
269 .set(metrics.num_blocking_threads() as f64);
270 self.global
271 .num_idle_blocking_threads()
272 .set(metrics.num_idle_blocking_threads() as f64);
273 self.global.num_workers().set(metrics.num_workers() as f64);
274 self.global
275 .remote_schedule_count()
276 .set(metrics.remote_schedule_count() as f64);
277 self.global
278 .spawned_tasks_count()
279 .set(metrics.spawned_tasks_count() as f64);
280
281 for (worker_idx, worker) in self.workers.iter().enumerate() {
282 worker.update(worker_idx, metrics);
283 }
284 }
285}