tokio_metrics/runtime.rs
1use crate::derived_metrics::derived_metrics;
2#[cfg(tokio_unstable)]
3use std::ops::Range;
4use std::time::{Duration, Instant};
5use tokio::runtime;
6
7// `PollTimeHistogram` and `HistogramBucket` are plain data types (they only
8// hold `Duration`s and counts), so they are always available with the `rt`
9// feature. Only *populating* the histogram requires `tokio_unstable`. Keeping
10// the types ungated ensures `RuntimeMetrics::poll_time_histogram` resolves even
11// when `tokio_unstable` is not set (e.g. when a derive macro re-emits the
12// struct and drops the field's cfg gate). See
13// https://github.com/tokio-rs/tokio-metrics/issues/128.
14mod poll_time_histogram;
15pub use poll_time_histogram::{HistogramBucket, PollTimeHistogram};
16
17#[cfg(feature = "metrics-rs-integration")]
18pub(crate) mod metrics_rs_integration;
19
20/// Monitors key metrics of the tokio runtime.
21///
22/// ### Usage
23/// ```
24/// use std::time::Duration;
25/// use tokio_metrics::RuntimeMonitor;
26///
27/// #[tokio::main]
28/// async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
29/// let handle = tokio::runtime::Handle::current();
30///
31/// // print runtime metrics every 500ms
32/// {
33/// let runtime_monitor = RuntimeMonitor::new(&handle);
34/// tokio::spawn(async move {
35/// for interval in runtime_monitor.intervals() {
36/// // pretty-print the metric interval
37/// println!("{:?}", interval);
38/// // wait 500ms
39/// tokio::time::sleep(Duration::from_millis(500)).await;
40/// }
41/// });
42/// }
43///
44/// // await some tasks
45/// tokio::join![
46/// do_work(),
47/// do_work(),
48/// do_work(),
49/// ];
50///
51/// Ok(())
52/// }
53///
54/// async fn do_work() {
55/// for _ in 0..25 {
56/// tokio::task::yield_now().await;
57/// tokio::time::sleep(Duration::from_millis(100)).await;
58/// }
59/// }
60/// ```
61#[derive(Debug)]
62pub struct RuntimeMonitor {
63 /// Handle to the runtime
64 runtime: runtime::RuntimeMetrics,
65}
66
67macro_rules! define_runtime_metrics {
68 (
69 stable {
70 $(
71 $(#[$($attributes:tt)*])*
72 $vis:vis $name:ident: $ty:ty
73 ),*
74 $(,)?
75 }
76 unstable {
77 $(
78 $(#[$($unstable_attributes:tt)*])*
79 $unstable_vis:vis $unstable_name:ident: $unstable_ty:ty
80 ),*
81 $(,)?
82 }
83 ) => {
84 /// Key runtime metrics.
85 #[non_exhaustive]
86 #[cfg_attr(feature = "metrique-integration", metrique::unit_of_work::metrics(subfield_owned))]
87 #[derive(Default, Debug, Clone)]
88 pub struct RuntimeMetrics {
89 $(
90 $(#[$($attributes)*])*
91 #[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
92 $vis $name: $ty,
93 )*
94 $(
95 $(#[$($unstable_attributes)*])*
96 #[cfg(tokio_unstable)]
97 #[cfg_attr(docsrs, doc(cfg(all(feature = "rt", tokio_unstable))))]
98 $unstable_vis $unstable_name: $unstable_ty,
99 )*
100 }
101 };
102}
103
104define_runtime_metrics! {
105 stable {
106 /// The number of worker threads used by the runtime.
107 ///
108 /// This metric is static for a runtime.
109 ///
110 /// This metric is always equal to [`tokio::runtime::RuntimeMetrics::num_workers`].
111 /// When using the `current_thread` runtime, the return value is always `1`.
112 ///
113 /// The number of workers is set by configuring
114 /// [`worker_threads`][`tokio::runtime::Builder::worker_threads`] with
115 /// [`tokio::runtime::Builder`], or by parameterizing [`tokio::main`].
116 ///
117 /// ##### Examples
118 /// In the below example, the number of workers is set by parameterizing [`tokio::main`]:
119 /// ```
120 /// use tokio::runtime::Handle;
121 ///
122 /// #[tokio::main(flavor = "multi_thread", worker_threads = 10)]
123 /// async fn main() {
124 /// let handle = tokio::runtime::Handle::current();
125 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
126 /// let mut intervals = monitor.intervals();
127 /// let mut next_interval = || intervals.next().unwrap();
128 ///
129 /// assert_eq!(next_interval().workers_count, 10);
130 /// }
131 /// ```
132 ///
133 /// [`tokio::main`]: https://docs.rs/tokio/latest/tokio/attr.main.html
134 ///
135 /// When using the `current_thread` runtime, the return value is always `1`; e.g.:
136 /// ```
137 /// use tokio::runtime::Handle;
138 ///
139 /// #[tokio::main(flavor = "current_thread")]
140 /// async fn main() {
141 /// let handle = tokio::runtime::Handle::current();
142 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
143 /// let mut intervals = monitor.intervals();
144 /// let mut next_interval = || intervals.next().unwrap();
145 ///
146 /// assert_eq!(next_interval().workers_count, 1);
147 /// }
148 /// ```
149 ///
150 /// This metric is always equal to [`tokio::runtime::RuntimeMetrics::num_workers`]; e.g.:
151 /// ```
152 /// use tokio::runtime::Handle;
153 ///
154 /// #[tokio::main]
155 /// async fn main() {
156 /// let handle = Handle::current();
157 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
158 /// let mut intervals = monitor.intervals();
159 /// let mut next_interval = || intervals.next().unwrap();
160 ///
161 /// assert_eq!(next_interval().workers_count, handle.metrics().num_workers());
162 /// }
163 /// ```
164 pub workers_count: usize,
165
166 /// The current number of alive tasks in the runtime.
167 ///
168 /// ##### Definition
169 /// This metric is derived from [`tokio::runtime::RuntimeMetrics::num_alive_tasks`].
170 pub live_tasks_count: usize,
171
172 /// The number of times worker threads parked.
173 ///
174 /// The worker park count increases by one each time the worker parks the thread waiting for
175 /// new inbound events to process. This usually means the worker has processed all pending work
176 /// and is currently idle.
177 ///
178 /// ##### Definition
179 /// This metric is derived from the sum of [`tokio::runtime::RuntimeMetrics::worker_park_count`]
180 /// across all worker threads.
181 ///
182 /// ##### See also
183 /// - [`RuntimeMetrics::max_park_count`]
184 /// - [`RuntimeMetrics::min_park_count`]
185 ///
186 /// ##### Examples
187 /// ```
188 /// #[tokio::main(flavor = "multi_thread", worker_threads = 2)]
189 /// async fn main() {
190 /// let handle = tokio::runtime::Handle::current();
191 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
192 /// let mut intervals = monitor.intervals();
193 /// let mut next_interval = || intervals.next().unwrap();
194 ///
195 /// let interval = next_interval(); // end of interval 1
196 /// assert_eq!(interval.total_park_count, 0);
197 ///
198 /// induce_parks().await;
199 ///
200 /// let interval = next_interval(); // end of interval 2
201 /// assert!(interval.total_park_count >= 1); // usually 1 or 2 parks
202 /// }
203 ///
204 /// async fn induce_parks() {
205 /// let _ = tokio::time::timeout(std::time::Duration::ZERO, async {
206 /// loop { tokio::task::yield_now().await; }
207 /// }).await;
208 /// }
209 /// ```
210 pub total_park_count: u64,
211
212 /// The maximum number of times any worker thread parked.
213 ///
214 /// ##### Definition
215 /// This metric is derived from the maximum of
216 /// [`tokio::runtime::RuntimeMetrics::worker_park_count`] across all worker threads.
217 ///
218 /// ##### See also
219 /// - [`RuntimeMetrics::total_park_count`]
220 /// - [`RuntimeMetrics::min_park_count`]
221 pub max_park_count: u64,
222
223 /// The minimum number of times any worker thread parked.
224 ///
225 /// ##### Definition
226 /// This metric is derived from the maximum of
227 /// [`tokio::runtime::RuntimeMetrics::worker_park_count`] across all worker threads.
228 ///
229 /// ##### See also
230 /// - [`RuntimeMetrics::total_park_count`]
231 /// - [`RuntimeMetrics::max_park_count`]
232 pub min_park_count: u64,
233
234 /// The amount of time worker threads were busy.
235 ///
236 /// The worker busy duration increases whenever the worker is spending time processing work.
237 /// Using this value can indicate the total load of workers.
238 ///
239 /// ##### Definition
240 /// This metric is derived from the sum of
241 /// [`tokio::runtime::RuntimeMetrics::worker_total_busy_duration`] across all worker threads.
242 ///
243 /// ##### See also
244 /// - [`RuntimeMetrics::min_busy_duration`]
245 /// - [`RuntimeMetrics::max_busy_duration`]
246 ///
247 /// ##### Examples
248 /// In the below example, tasks spend a total of 3s busy:
249 /// ```
250 /// use tokio::time::Duration;
251 ///
252 /// fn main() {
253 /// let start = tokio::time::Instant::now();
254 ///
255 /// let rt = tokio::runtime::Builder::new_current_thread()
256 /// .enable_all()
257 /// .build()
258 /// .unwrap();
259 ///
260 /// let handle = rt.handle();
261 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
262 /// let mut intervals = monitor.intervals();
263 /// let mut next_interval = || intervals.next().unwrap();
264 ///
265 /// let delay_1s = Duration::from_secs(1);
266 /// let delay_3s = Duration::from_secs(3);
267 ///
268 /// rt.block_on(async {
269 /// // keep the main task busy for 1s
270 /// spin_for(delay_1s);
271 ///
272 /// // spawn a task and keep it busy for 2s
273 /// let _ = tokio::spawn(async move {
274 /// spin_for(delay_3s);
275 /// }).await;
276 /// });
277 ///
278 /// // flush metrics
279 /// drop(rt);
280 ///
281 /// let elapsed = start.elapsed();
282 ///
283 /// let interval = next_interval(); // end of interval 2
284 /// assert!(interval.total_busy_duration >= delay_1s + delay_3s);
285 /// assert!(interval.total_busy_duration <= elapsed);
286 /// }
287 ///
288 /// fn time<F>(task: F) -> Duration
289 /// where
290 /// F: Fn() -> ()
291 /// {
292 /// let start = tokio::time::Instant::now();
293 /// task();
294 /// start.elapsed()
295 /// }
296 ///
297 /// /// Block the current thread for a given `duration`.
298 /// fn spin_for(duration: Duration) {
299 /// let start = tokio::time::Instant::now();
300 /// while start.elapsed() <= duration {}
301 /// }
302 /// ```
303 ///
304 /// Busy times may not accumulate as the above example suggests (FIXME: Why?); e.g., if we
305 /// remove the three second delay, the time spent busy falls to mere microseconds:
306 /// ```should_panic
307 /// use tokio::time::Duration;
308 ///
309 /// fn main() {
310 /// let rt = tokio::runtime::Builder::new_current_thread()
311 /// .enable_all()
312 /// .build()
313 /// .unwrap();
314 ///
315 /// let handle = rt.handle();
316 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
317 /// let mut intervals = monitor.intervals();
318 /// let mut next_interval = || intervals.next().unwrap();
319 ///
320 /// let delay_1s = Duration::from_secs(1);
321 ///
322 /// let elapsed = time(|| rt.block_on(async {
323 /// // keep the main task busy for 1s
324 /// spin_for(delay_1s);
325 /// }));
326 ///
327 /// // flush metrics
328 /// drop(rt);
329 ///
330 /// let interval = next_interval(); // end of interval 2
331 /// assert!(interval.total_busy_duration >= delay_1s); // FAIL
332 /// assert!(interval.total_busy_duration <= elapsed);
333 /// }
334 ///
335 /// fn time<F>(task: F) -> Duration
336 /// where
337 /// F: Fn() -> ()
338 /// {
339 /// let start = tokio::time::Instant::now();
340 /// task();
341 /// start.elapsed()
342 /// }
343 ///
344 /// /// Block the current thread for a given `duration`.
345 /// fn spin_for(duration: Duration) {
346 /// let start = tokio::time::Instant::now();
347 /// while start.elapsed() <= duration {}
348 /// }
349 /// ```
350 pub total_busy_duration: Duration,
351
352 /// The maximum amount of time a worker thread was busy.
353 ///
354 /// ##### Definition
355 /// This metric is derived from the maximum of
356 /// [`tokio::runtime::RuntimeMetrics::worker_total_busy_duration`] across all worker threads.
357 ///
358 /// ##### See also
359 /// - [`RuntimeMetrics::total_busy_duration`]
360 /// - [`RuntimeMetrics::min_busy_duration`]
361 pub max_busy_duration: Duration,
362
363 /// The minimum amount of time a worker thread was busy.
364 ///
365 /// ##### Definition
366 /// This metric is derived from the minimum of
367 /// [`tokio::runtime::RuntimeMetrics::worker_total_busy_duration`] across all worker threads.
368 ///
369 /// ##### See also
370 /// - [`RuntimeMetrics::total_busy_duration`]
371 /// - [`RuntimeMetrics::max_busy_duration`]
372 pub min_busy_duration: Duration,
373
374 /// The number of tasks currently scheduled in the runtime's global queue.
375 ///
376 /// Tasks that are spawned or notified from a non-runtime thread are scheduled using the
377 /// runtime's global queue. This metric returns the **current** number of tasks pending in
378 /// the global queue. As such, the returned value may increase or decrease as new tasks are
379 /// scheduled and processed.
380 ///
381 /// ##### Definition
382 /// This metric is derived from [`tokio::runtime::RuntimeMetrics::global_queue_depth`].
383 ///
384 /// ##### Example
385 /// ```
386 /// # let current_thread = tokio::runtime::Builder::new_current_thread()
387 /// # .enable_all()
388 /// # .build()
389 /// # .unwrap();
390 /// #
391 /// # let multi_thread = tokio::runtime::Builder::new_multi_thread()
392 /// # .worker_threads(2)
393 /// # .enable_all()
394 /// # .build()
395 /// # .unwrap();
396 /// #
397 /// # for runtime in [current_thread, multi_thread] {
398 /// let handle = runtime.handle().clone();
399 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
400 /// let mut intervals = monitor.intervals();
401 /// let mut next_interval = || intervals.next().unwrap();
402 ///
403 /// let interval = next_interval(); // end of interval 1
404 /// # #[cfg(tokio_unstable)]
405 /// assert_eq!(interval.num_remote_schedules, 0);
406 ///
407 /// // spawn a system thread outside of the runtime
408 /// std::thread::spawn(move || {
409 /// // spawn two tasks from this non-runtime thread
410 /// handle.spawn(async {});
411 /// handle.spawn(async {});
412 /// }).join().unwrap();
413 ///
414 /// // flush metrics
415 /// drop(runtime);
416 ///
417 /// let interval = next_interval(); // end of interval 2
418 /// # #[cfg(tokio_unstable)]
419 /// assert_eq!(interval.num_remote_schedules, 2);
420 /// # }
421 /// ```
422 pub global_queue_depth: usize,
423
424 /// Total amount of time elapsed since observing runtime metrics.
425 pub elapsed: Duration,
426 }
427 unstable {
428 /// The average duration of a single invocation of poll on a task.
429 ///
430 /// This average is an exponentially-weighted moving average of the duration
431 /// of task polls on all runtime workers.
432 ///
433 /// ##### Examples
434 /// ```
435 /// #[tokio::main(flavor = "multi_thread", worker_threads = 2)]
436 /// async fn main() {
437 /// let handle = tokio::runtime::Handle::current();
438 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
439 /// let mut intervals = monitor.intervals();
440 /// let mut next_interval = || intervals.next().unwrap();
441 ///
442 /// let interval = next_interval();
443 /// println!("mean task poll duration is {:?}", interval.mean_poll_duration);
444 /// }
445 /// ```
446 pub mean_poll_duration: Duration,
447
448 /// The average duration of a single invocation of poll on a task on the
449 /// worker with the lowest value.
450 ///
451 /// This average is an exponentially-weighted moving average of the duration
452 /// of task polls on the runtime worker with the lowest value.
453 ///
454 /// ##### Examples
455 /// ```
456 /// #[tokio::main(flavor = "multi_thread", worker_threads = 2)]
457 /// async fn main() {
458 /// let handle = tokio::runtime::Handle::current();
459 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
460 /// let mut intervals = monitor.intervals();
461 /// let mut next_interval = || intervals.next().unwrap();
462 ///
463 /// let interval = next_interval();
464 /// println!("min mean task poll duration is {:?}", interval.mean_poll_duration_worker_min);
465 /// }
466 /// ```
467 pub mean_poll_duration_worker_min: Duration,
468
469 /// The average duration of a single invocation of poll on a task on the
470 /// worker with the highest value.
471 ///
472 /// This average is an exponentially-weighted moving average of the duration
473 /// of task polls on the runtime worker with the highest value.
474 ///
475 /// ##### Examples
476 /// ```
477 /// #[tokio::main(flavor = "multi_thread", worker_threads = 2)]
478 /// async fn main() {
479 /// let handle = tokio::runtime::Handle::current();
480 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
481 /// let mut intervals = monitor.intervals();
482 /// let mut next_interval = || intervals.next().unwrap();
483 ///
484 /// let interval = next_interval();
485 /// println!("max mean task poll duration is {:?}", interval.mean_poll_duration_worker_max);
486 /// }
487 /// ```
488 pub mean_poll_duration_worker_max: Duration,
489
490 /// A histogram of task polls since the previous probe grouped by poll
491 /// times.
492 ///
493 /// Each bucket contains the configured [`Duration`] range and the count
494 /// of task polls that fell into that range during the interval. Use
495 /// [`PollTimeHistogram::as_counts`] to get just the raw counts as a
496 /// `Vec<u64>`.
497 ///
498 /// This metric must be explicitly enabled when creating the runtime with
499 /// [`enable_metrics_poll_time_histogram`][tokio::runtime::Builder::enable_metrics_poll_time_histogram];
500 /// if it is not enabled, the histogram will contain no buckets. Bucket
501 /// sizes are fixed and configured at the runtime level. See
502 /// configuration options on
503 /// [`runtime::Builder`][tokio::runtime::Builder::enable_metrics_poll_time_histogram].
504 ///
505 /// ##### Examples
506 /// ```
507 /// use tokio::runtime::HistogramConfiguration;
508 /// use std::time::Duration;
509 ///
510 /// let config = HistogramConfiguration::linear(Duration::from_micros(50), 12);
511 ///
512 /// let rt = tokio::runtime::Builder::new_multi_thread()
513 /// .enable_metrics_poll_time_histogram()
514 /// .metrics_poll_time_histogram_configuration(config)
515 /// .build()
516 /// .unwrap();
517 ///
518 /// rt.block_on(async {
519 /// let handle = tokio::runtime::Handle::current();
520 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
521 /// let mut intervals = monitor.intervals();
522 /// let mut next_interval = || intervals.next().unwrap();
523 ///
524 /// let interval = next_interval();
525 /// for bucket in interval.poll_time_histogram.buckets() {
526 /// println!("{:?}..{:?} => {} polls", bucket.range_start(), bucket.range_end(), bucket.count());
527 /// }
528 /// });
529 /// ```
530 pub poll_time_histogram: PollTimeHistogram,
531
532 /// The number of times worker threads unparked but performed no work before parking again.
533 ///
534 /// The worker no-op count increases by one each time the worker unparks the thread but finds
535 /// no new work and goes back to sleep. This indicates a false-positive wake up.
536 ///
537 /// ##### Definition
538 /// This metric is derived from the sum of [`tokio::runtime::RuntimeMetrics::worker_noop_count`]
539 /// across all worker threads.
540 ///
541 /// ##### Examples
542 /// Unfortunately, there isn't a great way to reliably induce no-op parks, as they occur as
543 /// false-positive events under concurrency.
544 ///
545 /// The below example triggers fewer than two parks in the single-threaded runtime:
546 /// ```
547 /// #[tokio::main(flavor = "current_thread")]
548 /// async fn main() {
549 /// let handle = tokio::runtime::Handle::current();
550 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
551 /// let mut intervals = monitor.intervals();
552 /// let mut next_interval = || intervals.next().unwrap();
553 ///
554 /// assert_eq!(next_interval().total_park_count, 0);
555 ///
556 /// async {
557 /// tokio::time::sleep(std::time::Duration::from_millis(1)).await;
558 /// }.await;
559 ///
560 /// assert!(next_interval().total_park_count > 0);
561 /// }
562 /// ```
563 ///
564 /// The below example triggers fewer than two parks in the multi-threaded runtime:
565 /// ```
566 /// #[tokio::main(flavor = "multi_thread")]
567 /// async fn main() {
568 /// let handle = tokio::runtime::Handle::current();
569 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
570 /// let mut intervals = monitor.intervals();
571 /// let mut next_interval = || intervals.next().unwrap();
572 ///
573 /// async {
574 /// tokio::time::sleep(std::time::Duration::from_millis(1)).await;
575 /// }.await;
576 ///
577 /// assert!(next_interval().total_noop_count > 0);
578 /// }
579 /// ```
580 pub total_noop_count: u64,
581
582 /// The maximum number of times any worker thread unparked but performed no work before parking
583 /// again.
584 ///
585 /// ##### Definition
586 /// This metric is derived from the maximum of
587 /// [`tokio::runtime::RuntimeMetrics::worker_noop_count`] across all worker threads.
588 ///
589 /// ##### See also
590 /// - [`RuntimeMetrics::total_noop_count`]
591 /// - [`RuntimeMetrics::min_noop_count`]
592 pub max_noop_count: u64,
593
594 /// The minimum number of times any worker thread unparked but performed no work before parking
595 /// again.
596 ///
597 /// ##### Definition
598 /// This metric is derived from the minimum of
599 /// [`tokio::runtime::RuntimeMetrics::worker_noop_count`] across all worker threads.
600 ///
601 /// ##### See also
602 /// - [`RuntimeMetrics::total_noop_count`]
603 /// - [`RuntimeMetrics::max_noop_count`]
604 pub min_noop_count: u64,
605
606 /// The number of tasks worker threads stole from another worker thread.
607 ///
608 /// The worker steal count increases by the amount of stolen tasks each time the worker
609 /// has processed its scheduled queue and successfully steals more pending tasks from another
610 /// worker.
611 ///
612 /// This metric only applies to the **multi-threaded** runtime and will always return `0` when
613 /// using the current thread runtime.
614 ///
615 /// ##### Definition
616 /// This metric is derived from the sum of [`tokio::runtime::RuntimeMetrics::worker_steal_count`] for
617 /// all worker threads.
618 ///
619 /// ##### See also
620 /// - [`RuntimeMetrics::min_steal_count`]
621 /// - [`RuntimeMetrics::max_steal_count`]
622 ///
623 /// ##### Examples
624 /// In the below example, a blocking channel is used to backup one worker thread:
625 /// ```
626 /// #[tokio::main(flavor = "multi_thread", worker_threads = 2)]
627 /// async fn main() {
628 /// let handle = tokio::runtime::Handle::current();
629 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
630 /// let mut intervals = monitor.intervals();
631 /// let mut next_interval = || intervals.next().unwrap();
632 ///
633 /// let interval = next_interval(); // end of first sampling interval
634 /// assert_eq!(interval.total_steal_count, 0);
635 /// assert_eq!(interval.min_steal_count, 0);
636 /// assert_eq!(interval.max_steal_count, 0);
637 ///
638 /// // induce a steal
639 /// async {
640 /// let (tx, rx) = std::sync::mpsc::channel();
641 /// // Move to the runtime.
642 /// tokio::spawn(async move {
643 /// // Spawn the task that sends to the channel
644 /// tokio::spawn(async move {
645 /// tx.send(()).unwrap();
646 /// });
647 /// // Spawn a task that bumps the previous task out of the "next
648 /// // scheduled" slot.
649 /// tokio::spawn(async {});
650 /// // Blocking receive on the channel.
651 /// rx.recv().unwrap();
652 /// flush_metrics().await;
653 /// }).await.unwrap();
654 /// flush_metrics().await;
655 /// }.await;
656 ///
657 /// let interval = { flush_metrics().await; next_interval() }; // end of interval 2
658 /// println!("total={}; min={}; max={}", interval.total_steal_count, interval.min_steal_count, interval.max_steal_count);
659 ///
660 /// let interval = { flush_metrics().await; next_interval() }; // end of interval 3
661 /// println!("total={}; min={}; max={}", interval.total_steal_count, interval.min_steal_count, interval.max_steal_count);
662 /// }
663 ///
664 /// async fn flush_metrics() {
665 /// let _ = tokio::time::sleep(std::time::Duration::ZERO).await;
666 /// }
667 /// ```
668 pub total_steal_count: u64,
669
670 /// The maximum number of tasks any worker thread stole from another worker thread.
671 ///
672 /// ##### Definition
673 /// This metric is derived from the maximum of [`tokio::runtime::RuntimeMetrics::worker_steal_count`]
674 /// across all worker threads.
675 ///
676 /// ##### See also
677 /// - [`RuntimeMetrics::total_steal_count`]
678 /// - [`RuntimeMetrics::min_steal_count`]
679 pub max_steal_count: u64,
680
681 /// The minimum number of tasks any worker thread stole from another worker thread.
682 ///
683 /// ##### Definition
684 /// This metric is derived from the minimum of [`tokio::runtime::RuntimeMetrics::worker_steal_count`]
685 /// across all worker threads.
686 ///
687 /// ##### See also
688 /// - [`RuntimeMetrics::total_steal_count`]
689 /// - [`RuntimeMetrics::max_steal_count`]
690 pub min_steal_count: u64,
691
692 /// The number of times worker threads stole tasks from another worker thread.
693 ///
694 /// The worker steal operations increases by one each time the worker has processed its
695 /// scheduled queue and successfully steals more pending tasks from another worker.
696 ///
697 /// This metric only applies to the **multi-threaded** runtime and will always return `0` when
698 /// using the current thread runtime.
699 ///
700 /// ##### Definition
701 /// This metric is derived from the sum of [`tokio::runtime::RuntimeMetrics::worker_steal_operations`]
702 /// for all worker threads.
703 ///
704 /// ##### See also
705 /// - [`RuntimeMetrics::min_steal_operations`]
706 /// - [`RuntimeMetrics::max_steal_operations`]
707 ///
708 /// ##### Examples
709 /// In the below example, a blocking channel is used to backup one worker thread:
710 /// ```
711 /// #[tokio::main(flavor = "multi_thread", worker_threads = 2)]
712 /// async fn main() {
713 /// let handle = tokio::runtime::Handle::current();
714 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
715 /// let mut intervals = monitor.intervals();
716 /// let mut next_interval = || intervals.next().unwrap();
717 ///
718 /// let interval = next_interval(); // end of first sampling interval
719 /// assert_eq!(interval.total_steal_operations, 0);
720 /// assert_eq!(interval.min_steal_operations, 0);
721 /// assert_eq!(interval.max_steal_operations, 0);
722 ///
723 /// // induce a steal
724 /// async {
725 /// let (tx, rx) = std::sync::mpsc::channel();
726 /// // Move to the runtime.
727 /// tokio::spawn(async move {
728 /// // Spawn the task that sends to the channel
729 /// tokio::spawn(async move {
730 /// tx.send(()).unwrap();
731 /// });
732 /// // Spawn a task that bumps the previous task out of the "next
733 /// // scheduled" slot.
734 /// tokio::spawn(async {});
735 /// // Blocking receive on the channe.
736 /// rx.recv().unwrap();
737 /// flush_metrics().await;
738 /// }).await.unwrap();
739 /// flush_metrics().await;
740 /// }.await;
741 ///
742 /// let interval = { flush_metrics().await; next_interval() }; // end of interval 2
743 /// println!("total={}; min={}; max={}", interval.total_steal_operations, interval.min_steal_operations, interval.max_steal_operations);
744 ///
745 /// let interval = { flush_metrics().await; next_interval() }; // end of interval 3
746 /// println!("total={}; min={}; max={}", interval.total_steal_operations, interval.min_steal_operations, interval.max_steal_operations);
747 /// }
748 ///
749 /// async fn flush_metrics() {
750 /// let _ = tokio::time::sleep(std::time::Duration::ZERO).await;
751 /// }
752 /// ```
753 pub total_steal_operations: u64,
754
755 /// The maximum number of times any worker thread stole tasks from another worker thread.
756 ///
757 /// ##### Definition
758 /// This metric is derived from the maximum of [`tokio::runtime::RuntimeMetrics::worker_steal_operations`]
759 /// across all worker threads.
760 ///
761 /// ##### See also
762 /// - [`RuntimeMetrics::total_steal_operations`]
763 /// - [`RuntimeMetrics::min_steal_operations`]
764 pub max_steal_operations: u64,
765
766 /// The minimum number of times any worker thread stole tasks from another worker thread.
767 ///
768 /// ##### Definition
769 /// This metric is derived from the minimum of [`tokio::runtime::RuntimeMetrics::worker_steal_operations`]
770 /// across all worker threads.
771 ///
772 /// ##### See also
773 /// - [`RuntimeMetrics::total_steal_operations`]
774 /// - [`RuntimeMetrics::max_steal_operations`]
775 pub min_steal_operations: u64,
776
777 /// The number of tasks scheduled from **outside** of the runtime.
778 ///
779 /// The remote schedule count increases by one each time a task is woken from **outside** of
780 /// the runtime. This usually means that a task is spawned or notified from a non-runtime
781 /// thread and must be queued using the Runtime's global queue, which tends to be slower.
782 ///
783 /// ##### Definition
784 /// This metric is derived from [`tokio::runtime::RuntimeMetrics::remote_schedule_count`].
785 ///
786 /// ##### Examples
787 /// In the below example, a remote schedule is induced by spawning a system thread, then
788 /// spawning a tokio task from that system thread:
789 /// ```
790 /// #[tokio::main(flavor = "multi_thread", worker_threads = 2)]
791 /// async fn main() {
792 /// let handle = tokio::runtime::Handle::current();
793 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
794 /// let mut intervals = monitor.intervals();
795 /// let mut next_interval = || intervals.next().unwrap();
796 ///
797 /// let interval = next_interval(); // end of first sampling interval
798 /// assert_eq!(interval.num_remote_schedules, 0);
799 ///
800 /// // spawn a non-runtime thread
801 /// std::thread::spawn(move || {
802 /// // spawn two tasks from this non-runtime thread
803 /// async move {
804 /// handle.spawn(async {}).await;
805 /// handle.spawn(async {}).await;
806 /// }
807 /// }).join().unwrap().await;
808 ///
809 /// let interval = next_interval(); // end of second sampling interval
810 /// assert_eq!(interval.num_remote_schedules, 2);
811 ///
812 /// let interval = next_interval(); // end of third sampling interval
813 /// assert_eq!(interval.num_remote_schedules, 0);
814 /// }
815 /// ```
816 pub num_remote_schedules: u64,
817
818 /// The number of tasks scheduled from worker threads.
819 ///
820 /// The local schedule count increases by one each time a task is woken from **inside** of the
821 /// runtime. This usually means that a task is spawned or notified from within a runtime thread
822 /// and will be queued on the worker-local queue.
823 ///
824 /// ##### Definition
825 /// This metric is derived from the sum of
826 /// [`tokio::runtime::RuntimeMetrics::worker_local_schedule_count`] across all worker threads.
827 ///
828 /// ##### See also
829 /// - [`RuntimeMetrics::min_local_schedule_count`]
830 /// - [`RuntimeMetrics::max_local_schedule_count`]
831 ///
832 /// ##### Examples
833 /// ###### With `current_thread` runtime
834 /// In the below example, two tasks are spawned from the context of a third tokio task:
835 /// ```
836 /// #[tokio::main(flavor = "current_thread")]
837 /// async fn main() {
838 /// let handle = tokio::runtime::Handle::current();
839 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
840 /// let mut intervals = monitor.intervals();
841 /// let mut next_interval = || intervals.next().unwrap();
842 ///
843 /// let interval = { flush_metrics().await; next_interval() }; // end interval 2
844 /// assert_eq!(interval.total_local_schedule_count, 0);
845 ///
846 /// let task = async {
847 /// tokio::spawn(async {}); // local schedule 1
848 /// tokio::spawn(async {}); // local schedule 2
849 /// };
850 ///
851 /// let handle = tokio::spawn(task); // local schedule 3
852 ///
853 /// let interval = { flush_metrics().await; next_interval() }; // end interval 2
854 /// assert_eq!(interval.total_local_schedule_count, 3);
855 ///
856 /// let _ = handle.await;
857 ///
858 /// let interval = { flush_metrics().await; next_interval() }; // end interval 3
859 /// assert_eq!(interval.total_local_schedule_count, 0);
860 /// }
861 ///
862 /// async fn flush_metrics() {
863 /// tokio::task::yield_now().await;
864 /// }
865 /// ```
866 ///
867 /// ###### With `multi_thread` runtime
868 /// In the below example, 100 tasks are spawned:
869 /// ```
870 /// #[tokio::main(flavor = "multi_thread", worker_threads = 2)]
871 /// async fn main() {
872 /// let handle = tokio::runtime::Handle::current();
873 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
874 /// let mut intervals = monitor.intervals();
875 /// let mut next_interval = || intervals.next().unwrap();
876 ///
877 /// let interval = next_interval(); // end of interval 1
878 /// assert_eq!(interval.total_local_schedule_count, 0);
879 ///
880 /// use std::sync::atomic::{AtomicBool, Ordering};
881 /// static SPINLOCK: AtomicBool = AtomicBool::new(true);
882 ///
883 /// // block the other worker thread
884 /// tokio::spawn(async {
885 /// while SPINLOCK.load(Ordering::SeqCst) {}
886 /// });
887 ///
888 /// // FIXME: why does this need to be in a `spawn`?
889 /// let _ = tokio::spawn(async {
890 /// // spawn 100 tasks
891 /// for _ in 0..100 {
892 /// tokio::spawn(async {});
893 /// }
894 /// // this spawns 1 more task
895 /// flush_metrics().await;
896 /// }).await;
897 ///
898 /// // unblock the other worker thread
899 /// SPINLOCK.store(false, Ordering::SeqCst);
900 ///
901 /// let interval = { flush_metrics().await; next_interval() }; // end of interval 2
902 /// assert_eq!(interval.total_local_schedule_count, 100 + 1);
903 /// }
904 ///
905 /// async fn flush_metrics() {
906 /// let _ = tokio::time::sleep(std::time::Duration::ZERO).await;
907 /// }
908 /// ```
909 pub total_local_schedule_count: u64,
910
911 /// The maximum number of tasks scheduled from any one worker thread.
912 ///
913 /// ##### Definition
914 /// This metric is derived from the maximum of
915 /// [`tokio::runtime::RuntimeMetrics::worker_local_schedule_count`] for all worker threads.
916 ///
917 /// ##### See also
918 /// - [`RuntimeMetrics::total_local_schedule_count`]
919 /// - [`RuntimeMetrics::min_local_schedule_count`]
920 pub max_local_schedule_count: u64,
921
922 /// The minimum number of tasks scheduled from any one worker thread.
923 ///
924 /// ##### Definition
925 /// This metric is derived from the minimum of
926 /// [`tokio::runtime::RuntimeMetrics::worker_local_schedule_count`] for all worker threads.
927 ///
928 /// ##### See also
929 /// - [`RuntimeMetrics::total_local_schedule_count`]
930 /// - [`RuntimeMetrics::max_local_schedule_count`]
931 pub min_local_schedule_count: u64,
932
933 /// The number of times worker threads saturated their local queues.
934 ///
935 /// The worker steal count increases by one each time the worker attempts to schedule a task
936 /// locally, but its local queue is full. When this happens, half of the
937 /// local queue is moved to the global queue.
938 ///
939 /// This metric only applies to the **multi-threaded** scheduler.
940 ///
941 /// ##### Definition
942 /// This metric is derived from the sum of
943 /// [`tokio::runtime::RuntimeMetrics::worker_overflow_count`] across all worker threads.
944 ///
945 /// ##### See also
946 /// - [`RuntimeMetrics::min_overflow_count`]
947 /// - [`RuntimeMetrics::max_overflow_count`]
948 ///
949 /// ##### Examples
950 /// ```
951 /// #[tokio::main(flavor = "multi_thread", worker_threads = 1)]
952 /// async fn main() {
953 /// let handle = tokio::runtime::Handle::current();
954 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
955 /// let mut intervals = monitor.intervals();
956 /// let mut next_interval = || intervals.next().unwrap();
957 ///
958 /// let interval = next_interval(); // end of interval 1
959 /// assert_eq!(interval.total_overflow_count, 0);
960 ///
961 /// use std::sync::atomic::{AtomicBool, Ordering};
962 ///
963 /// // spawn a ton of tasks
964 /// let _ = tokio::spawn(async {
965 /// // we do this in a `tokio::spawn` because it is impossible to
966 /// // overflow the main task
967 /// for _ in 0..300 {
968 /// tokio::spawn(async {});
969 /// }
970 /// }).await;
971 ///
972 /// let interval = { flush_metrics().await; next_interval() }; // end of interval 2
973 /// assert_eq!(interval.total_overflow_count, 1);
974 /// }
975 ///
976 /// async fn flush_metrics() {
977 /// let _ = tokio::time::sleep(std::time::Duration::from_millis(1)).await;
978 /// }
979 /// ```
980 pub total_overflow_count: u64,
981
982 /// The maximum number of times any one worker saturated its local queue.
983 ///
984 /// ##### Definition
985 /// This metric is derived from the maximum of
986 /// [`tokio::runtime::RuntimeMetrics::worker_overflow_count`] across all worker threads.
987 ///
988 /// ##### See also
989 /// - [`RuntimeMetrics::total_overflow_count`]
990 /// - [`RuntimeMetrics::min_overflow_count`]
991 pub max_overflow_count: u64,
992
993 /// The minimum number of times any one worker saturated its local queue.
994 ///
995 /// ##### Definition
996 /// This metric is derived from the maximum of
997 /// [`tokio::runtime::RuntimeMetrics::worker_overflow_count`] across all worker threads.
998 ///
999 /// ##### See also
1000 /// - [`RuntimeMetrics::total_overflow_count`]
1001 /// - [`RuntimeMetrics::max_overflow_count`]
1002 pub min_overflow_count: u64,
1003
1004 /// The number of tasks that have been polled across all worker threads.
1005 ///
1006 /// The worker poll count increases by one each time a worker polls a scheduled task.
1007 ///
1008 /// ##### Definition
1009 /// This metric is derived from the sum of
1010 /// [`tokio::runtime::RuntimeMetrics::worker_poll_count`] across all worker threads.
1011 ///
1012 /// ##### See also
1013 /// - [`RuntimeMetrics::min_polls_count`]
1014 /// - [`RuntimeMetrics::max_polls_count`]
1015 ///
1016 /// ##### Examples
1017 /// In the below example, 42 tasks are spawned and polled:
1018 /// ```
1019 /// #[tokio::main(flavor = "current_thread")]
1020 /// async fn main() {
1021 /// let handle = tokio::runtime::Handle::current();
1022 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
1023 /// let mut intervals = monitor.intervals();
1024 /// let mut next_interval = || intervals.next().unwrap();
1025 ///
1026 /// let interval = { flush_metrics().await; next_interval() }; // end of interval 1
1027 /// assert_eq!(interval.total_polls_count, 0);
1028 /// assert_eq!(interval.min_polls_count, 0);
1029 /// assert_eq!(interval.max_polls_count, 0);
1030 ///
1031 /// const N: u64 = 42;
1032 ///
1033 /// for _ in 0..N {
1034 /// let _ = tokio::spawn(async {}).await;
1035 /// }
1036 ///
1037 /// let interval = { flush_metrics().await; next_interval() }; // end of interval 2
1038 /// assert_eq!(interval.total_polls_count, N);
1039 /// assert_eq!(interval.min_polls_count, N);
1040 /// assert_eq!(interval.max_polls_count, N);
1041 /// }
1042 ///
1043 /// async fn flush_metrics() {
1044 /// let _ = tokio::task::yield_now().await;
1045 /// }
1046 /// ```
1047 pub total_polls_count: u64,
1048
1049 /// The maximum number of tasks that have been polled in any worker thread.
1050 ///
1051 /// ##### Definition
1052 /// This metric is derived from the maximum of
1053 /// [`tokio::runtime::RuntimeMetrics::worker_poll_count`] across all worker threads.
1054 ///
1055 /// ##### See also
1056 /// - [`RuntimeMetrics::total_polls_count`]
1057 /// - [`RuntimeMetrics::min_polls_count`]
1058 pub max_polls_count: u64,
1059
1060 /// The minimum number of tasks that have been polled in any worker thread.
1061 ///
1062 /// ##### Definition
1063 /// This metric is derived from the minimum of
1064 /// [`tokio::runtime::RuntimeMetrics::worker_poll_count`] across all worker threads.
1065 ///
1066 /// ##### See also
1067 /// - [`RuntimeMetrics::total_polls_count`]
1068 /// - [`RuntimeMetrics::max_polls_count`]
1069 pub min_polls_count: u64,
1070
1071 /// The total number of tasks currently scheduled in workers' local queues.
1072 ///
1073 /// Tasks that are spawned or notified from within a runtime thread are scheduled using that
1074 /// worker's local queue. This metric returns the **current** number of tasks pending in all
1075 /// workers' local queues. As such, the returned value may increase or decrease as new tasks
1076 /// are scheduled and processed.
1077 ///
1078 /// ##### Definition
1079 /// This metric is derived from [`tokio::runtime::RuntimeMetrics::worker_local_queue_depth`].
1080 ///
1081 /// ##### See also
1082 /// - [`RuntimeMetrics::min_local_queue_depth`]
1083 /// - [`RuntimeMetrics::max_local_queue_depth`]
1084 ///
1085 /// ##### Example
1086 ///
1087 /// ###### With `current_thread` runtime
1088 /// The below example spawns 100 tasks:
1089 /// ```
1090 /// #[tokio::main(flavor = "current_thread")]
1091 /// async fn main() {
1092 /// const N: usize = 100;
1093 ///
1094 /// let handle = tokio::runtime::Handle::current();
1095 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
1096 /// let mut intervals = monitor.intervals();
1097 /// let mut next_interval = || intervals.next().unwrap();
1098 ///
1099 /// let interval = next_interval(); // end of interval 1
1100 /// assert_eq!(interval.total_local_queue_depth, 0);
1101 ///
1102 ///
1103 /// for _ in 0..N {
1104 /// tokio::spawn(async {});
1105 /// }
1106 /// let interval = next_interval(); // end of interval 2
1107 /// assert_eq!(interval.total_local_queue_depth, N);
1108 /// }
1109 /// ```
1110 ///
1111 /// ###### With `multi_thread` runtime
1112 /// The below example spawns 100 tasks and observes them in the
1113 /// local queue:
1114 /// ```
1115 /// #[tokio::main(flavor = "multi_thread", worker_threads = 2)]
1116 /// async fn main() {
1117 /// use std::sync::mpsc;
1118 /// use tokio::sync::oneshot;
1119 ///
1120 /// const N: usize = 100;
1121 ///
1122 /// let handle = tokio::runtime::Handle::current();
1123 ///
1124 /// // block one worker so the other is the only one running
1125 /// let (block_tx, block_rx) = mpsc::channel::<()>();
1126 /// let (started_tx, started_rx) = oneshot::channel();
1127 /// tokio::spawn(async move {
1128 /// let _ = started_tx.send(());
1129 /// let _ = block_rx.recv();
1130 /// });
1131 /// let _ = started_rx.await;
1132 ///
1133 /// // spawn + sample from the free worker thread
1134 /// let (depth_tx, depth_rx) = oneshot::channel();
1135 /// tokio::spawn(async move {
1136 /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
1137 /// let mut intervals = monitor.intervals();
1138 /// let _ = intervals.next().unwrap(); // baseline
1139 ///
1140 /// for _ in 0..N {
1141 /// tokio::spawn(async {});
1142 /// }
1143 ///
1144 /// let depth = intervals.next().unwrap().total_local_queue_depth;
1145 /// let _ = depth_tx.send(depth);
1146 /// });
1147 ///
1148 /// let depth = depth_rx.await.unwrap();
1149 ///
1150 /// // Tokio may place one spawned task in a LIFO slot rather than the
1151 /// // local queue, which may not be reflected in `worker_local_queue_depth`,
1152 /// // so accept N or N - 1.
1153 /// assert!(depth == N || depth == N - 1, "depth = {depth}");
1154 ///
1155 /// let _ = block_tx.send(());
1156 /// }
1157 /// ```
1158 pub total_local_queue_depth: usize,
1159
1160 /// The maximum number of tasks currently scheduled any worker's local queue.
1161 ///
1162 /// ##### Definition
1163 /// This metric is derived from the maximum of
1164 /// [`tokio::runtime::RuntimeMetrics::worker_local_queue_depth`] across all worker threads.
1165 ///
1166 /// ##### See also
1167 /// - [`RuntimeMetrics::total_local_queue_depth`]
1168 /// - [`RuntimeMetrics::min_local_queue_depth`]
1169 pub max_local_queue_depth: usize,
1170
1171 /// The minimum number of tasks currently scheduled any worker's local queue.
1172 ///
1173 /// ##### Definition
1174 /// This metric is derived from the minimum of
1175 /// [`tokio::runtime::RuntimeMetrics::worker_local_queue_depth`] across all worker threads.
1176 ///
1177 /// ##### See also
1178 /// - [`RuntimeMetrics::total_local_queue_depth`]
1179 /// - [`RuntimeMetrics::max_local_queue_depth`]
1180 pub min_local_queue_depth: usize,
1181
1182 /// The number of tasks currently waiting to be executed in the runtime's blocking threadpool.
1183 ///
1184 /// ##### Definition
1185 /// This metric is derived from [`tokio::runtime::RuntimeMetrics::blocking_queue_depth`].
1186 pub blocking_queue_depth: usize,
1187
1188 /// The number of additional threads spawned by the runtime.
1189 ///
1190 /// ##### Definition
1191 /// This metric is derived from [`tokio::runtime::RuntimeMetrics::num_blocking_threads`].
1192 pub blocking_threads_count: usize,
1193
1194 /// The number of idle threads, which have spawned by the runtime for `spawn_blocking` calls.
1195 ///
1196 /// ##### Definition
1197 /// This metric is derived from [`tokio::runtime::RuntimeMetrics::num_idle_blocking_threads`].
1198 pub idle_blocking_threads_count: usize,
1199
1200 /// Returns the number of times that tasks have been forced to yield back to the scheduler after exhausting their task budgets.
1201 ///
1202 /// This count starts at zero when the runtime is created and increases by one each time a task yields due to exhausting its budget.
1203 ///
1204 /// The counter is monotonically increasing. It is never decremented or reset to zero.
1205 ///
1206 /// ##### Definition
1207 /// This metric is derived from [`tokio::runtime::RuntimeMetrics::budget_forced_yield_count`].
1208 pub budget_forced_yield_count: u64,
1209
1210 /// Returns the number of ready events processed by the runtime’s I/O driver.
1211 ///
1212 /// ##### Definition
1213 /// This metric is derived from [`tokio::runtime::RuntimeMetrics::io_driver_ready_count`].
1214 pub io_driver_ready_count: u64,
1215 }
1216}
1217
1218macro_rules! define_semi_stable {
1219 (
1220 $(#[$($attributes:tt)*])*
1221 $vis:vis struct $name:ident {
1222 stable {
1223 $($stable_name:ident: $stable_ty:ty),*
1224 $(,)?
1225 }
1226 $(,)?
1227 unstable {
1228 $($unstable_name:ident: $unstable_ty:ty),*
1229 $(,)?
1230 }
1231 }
1232 ) => {
1233 $(#[$($attributes)*])*
1234 $vis struct $name {
1235 $(
1236 $stable_name: $stable_ty,
1237 )*
1238 $(
1239 #[cfg(tokio_unstable)]
1240 #[cfg_attr(docsrs, doc(cfg(all(feature = "rt", tokio_unstable))))]
1241 $unstable_name: $unstable_ty,
1242 )*
1243 }
1244 };
1245}
1246
1247define_semi_stable! {
1248 /// Snapshot of per-worker metrics
1249 #[derive(Debug, Default)]
1250 struct Worker {
1251 stable {
1252 worker: usize,
1253 total_park_count: u64,
1254 total_busy_duration: Duration,
1255 }
1256 unstable {
1257 total_noop_count: u64,
1258 total_steal_count: u64,
1259 total_steal_operations: u64,
1260 total_local_schedule_count: u64,
1261 total_overflow_count: u64,
1262 total_polls_count: u64,
1263 poll_time_histogram: Vec<u64>,
1264 }
1265 }
1266}
1267
1268define_semi_stable! {
1269 /// Iterator returned by [`RuntimeMonitor::intervals`].
1270 ///
1271 /// See that method's documentation for more details.
1272 #[derive(Debug)]
1273 pub struct RuntimeIntervals {
1274 stable {
1275 runtime: runtime::RuntimeMetrics,
1276 started_at: Instant,
1277 workers: Vec<Worker>,
1278 }
1279 unstable {
1280 // Number of tasks scheduled from *outside* of the runtime
1281 num_remote_schedules: u64,
1282 budget_forced_yield_count: u64,
1283 io_driver_ready_count: u64,
1284 // Cached bucket ranges, static config that doesn't change after runtime creation.
1285 bucket_ranges: Vec<Range<Duration>>,
1286 }
1287 }
1288}
1289
1290impl RuntimeIntervals {
1291 fn probe(&mut self) -> RuntimeMetrics {
1292 let now = Instant::now();
1293
1294 let mut metrics = RuntimeMetrics {
1295 workers_count: self.runtime.num_workers(),
1296 live_tasks_count: self.runtime.num_alive_tasks(),
1297 elapsed: now.saturating_duration_since(self.started_at),
1298 global_queue_depth: self.runtime.global_queue_depth(),
1299 min_park_count: u64::MAX,
1300 min_busy_duration: Duration::from_secs(1000000000),
1301 ..Default::default()
1302 };
1303
1304 #[cfg(tokio_unstable)]
1305 {
1306 let num_remote_schedules = self.runtime.remote_schedule_count();
1307 let budget_forced_yields = self.runtime.budget_forced_yield_count();
1308 let io_driver_ready_events = self.runtime.io_driver_ready_count();
1309
1310 metrics.num_remote_schedules = num_remote_schedules.saturating_sub(self.num_remote_schedules);
1311 metrics.min_noop_count = u64::MAX;
1312 metrics.min_steal_count = u64::MAX;
1313 metrics.min_local_schedule_count = u64::MAX;
1314 metrics.min_overflow_count = u64::MAX;
1315 metrics.min_polls_count = u64::MAX;
1316 metrics.min_local_queue_depth = usize::MAX;
1317 metrics.mean_poll_duration_worker_min = Duration::MAX;
1318 metrics.poll_time_histogram = PollTimeHistogram::new(
1319 self.bucket_ranges
1320 .iter()
1321 .map(|range| HistogramBucket::new(range.start, range.end, 0))
1322 .collect(),
1323 );
1324 metrics.budget_forced_yield_count =
1325 budget_forced_yields.saturating_sub(self.budget_forced_yield_count);
1326 metrics.io_driver_ready_count = io_driver_ready_events.saturating_sub(self.io_driver_ready_count);
1327
1328 self.num_remote_schedules = num_remote_schedules;
1329 self.budget_forced_yield_count = budget_forced_yields;
1330 self.io_driver_ready_count = io_driver_ready_events;
1331 }
1332 self.started_at = now;
1333
1334 for worker in &mut self.workers {
1335 worker.probe(&self.runtime, &mut metrics);
1336 }
1337
1338 #[cfg(tokio_unstable)]
1339 {
1340 if metrics.total_polls_count == 0 {
1341 debug_assert_eq!(metrics.mean_poll_duration, Duration::default());
1342
1343 metrics.mean_poll_duration_worker_max = Duration::default();
1344 metrics.mean_poll_duration_worker_min = Duration::default();
1345 }
1346 }
1347
1348 metrics
1349 }
1350}
1351
1352impl Iterator for RuntimeIntervals {
1353 type Item = RuntimeMetrics;
1354
1355 fn next(&mut self) -> Option<RuntimeMetrics> {
1356 Some(self.probe())
1357 }
1358}
1359
1360impl RuntimeMonitor {
1361 /// Creates a new [`RuntimeMonitor`].
1362 pub fn new(runtime: &runtime::Handle) -> RuntimeMonitor {
1363 let runtime = runtime.metrics();
1364
1365 RuntimeMonitor { runtime }
1366 }
1367
1368 /// Produces an unending iterator of [`RuntimeMetrics`].
1369 ///
1370 /// Each sampling interval is defined by the time elapsed between advancements of the iterator
1371 /// produced by [`RuntimeMonitor::intervals`]. The item type of this iterator is [`RuntimeMetrics`],
1372 /// which is a bundle of runtime metrics that describe *only* changes occurring within that sampling
1373 /// interval.
1374 ///
1375 /// # Example
1376 ///
1377 /// ```
1378 /// use std::time::Duration;
1379 ///
1380 /// #[tokio::main]
1381 /// async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1382 /// let handle = tokio::runtime::Handle::current();
1383 /// // construct the runtime metrics monitor
1384 /// let runtime_monitor = tokio_metrics::RuntimeMonitor::new(&handle);
1385 ///
1386 /// // print runtime metrics every 500ms
1387 /// {
1388 /// tokio::spawn(async move {
1389 /// for interval in runtime_monitor.intervals() {
1390 /// // pretty-print the metric interval
1391 /// println!("{:?}", interval);
1392 /// // wait 500ms
1393 /// tokio::time::sleep(Duration::from_millis(500)).await;
1394 /// }
1395 /// });
1396 /// }
1397 ///
1398 /// // await some tasks
1399 /// tokio::join![
1400 /// do_work(),
1401 /// do_work(),
1402 /// do_work(),
1403 /// ];
1404 ///
1405 /// Ok(())
1406 /// }
1407 ///
1408 /// async fn do_work() {
1409 /// for _ in 0..25 {
1410 /// tokio::task::yield_now().await;
1411 /// tokio::time::sleep(Duration::from_millis(100)).await;
1412 /// }
1413 /// }
1414 /// ```
1415 pub fn intervals(&self) -> RuntimeIntervals {
1416 let started_at = Instant::now();
1417
1418 let workers = (0..self.runtime.num_workers())
1419 .map(|worker| Worker::new(worker, &self.runtime))
1420 .collect();
1421
1422 RuntimeIntervals {
1423 runtime: self.runtime.clone(),
1424 started_at,
1425 workers,
1426
1427 #[cfg(tokio_unstable)]
1428 num_remote_schedules: self.runtime.remote_schedule_count(),
1429 #[cfg(tokio_unstable)]
1430 budget_forced_yield_count: self.runtime.budget_forced_yield_count(),
1431 #[cfg(tokio_unstable)]
1432 io_driver_ready_count: self.runtime.io_driver_ready_count(),
1433 #[cfg(tokio_unstable)]
1434 bucket_ranges: (0..self.runtime.poll_time_histogram_num_buckets())
1435 .map(|i| self.runtime.poll_time_histogram_bucket_range(i))
1436 .collect(),
1437 }
1438 }
1439}
1440
1441impl Worker {
1442 fn new(worker: usize, rt: &runtime::RuntimeMetrics) -> Worker {
1443 #[allow(unused_mut, clippy::needless_update)]
1444 let mut wrk = Worker {
1445 worker,
1446 total_park_count: rt.worker_park_count(worker),
1447 total_busy_duration: rt.worker_total_busy_duration(worker),
1448 ..Default::default()
1449 };
1450
1451 #[cfg(tokio_unstable)]
1452 {
1453 let poll_time_histogram = if rt.poll_time_histogram_enabled() {
1454 vec![0; rt.poll_time_histogram_num_buckets()]
1455 } else {
1456 vec![]
1457 };
1458 wrk.total_noop_count = rt.worker_noop_count(worker);
1459 wrk.total_steal_count = rt.worker_steal_count(worker);
1460 wrk.total_steal_operations = rt.worker_steal_operations(worker);
1461 wrk.total_local_schedule_count = rt.worker_local_schedule_count(worker);
1462 wrk.total_overflow_count = rt.worker_overflow_count(worker);
1463 wrk.total_polls_count = rt.worker_poll_count(worker);
1464 wrk.poll_time_histogram = poll_time_histogram;
1465 };
1466 wrk
1467 }
1468
1469 fn probe(&mut self, rt: &runtime::RuntimeMetrics, metrics: &mut RuntimeMetrics) {
1470 macro_rules! metric {
1471 ( $sum:ident, $max:ident, $min:ident, $probe:ident ) => {{
1472 let val = rt.$probe(self.worker);
1473 let delta = val - self.$sum;
1474 self.$sum = val;
1475
1476 metrics.$sum += delta;
1477
1478 if delta > metrics.$max {
1479 metrics.$max = delta;
1480 }
1481
1482 if delta < metrics.$min {
1483 metrics.$min = delta;
1484 }
1485 }};
1486 }
1487
1488 metric!(
1489 total_park_count,
1490 max_park_count,
1491 min_park_count,
1492 worker_park_count
1493 );
1494 metric!(
1495 total_busy_duration,
1496 max_busy_duration,
1497 min_busy_duration,
1498 worker_total_busy_duration
1499 );
1500
1501 #[cfg(tokio_unstable)]
1502 {
1503 let mut worker_polls_count = self.total_polls_count;
1504 let total_polls_count = metrics.total_polls_count;
1505
1506 metric!(
1507 total_noop_count,
1508 max_noop_count,
1509 min_noop_count,
1510 worker_noop_count
1511 );
1512 metric!(
1513 total_steal_count,
1514 max_steal_count,
1515 min_steal_count,
1516 worker_steal_count
1517 );
1518 metric!(
1519 total_steal_operations,
1520 max_steal_operations,
1521 min_steal_operations,
1522 worker_steal_operations
1523 );
1524 metric!(
1525 total_local_schedule_count,
1526 max_local_schedule_count,
1527 min_local_schedule_count,
1528 worker_local_schedule_count
1529 );
1530 metric!(
1531 total_overflow_count,
1532 max_overflow_count,
1533 min_overflow_count,
1534 worker_overflow_count
1535 );
1536 metric!(
1537 total_polls_count,
1538 max_polls_count,
1539 min_polls_count,
1540 worker_poll_count
1541 );
1542
1543 // Get the number of polls since last probe
1544 worker_polls_count = self.total_polls_count.saturating_sub(worker_polls_count);
1545
1546 // Update the mean task poll duration if there were polls
1547 if worker_polls_count > 0 {
1548 let val = rt.worker_mean_poll_time(self.worker);
1549
1550 if val > metrics.mean_poll_duration_worker_max {
1551 metrics.mean_poll_duration_worker_max = val;
1552 }
1553
1554 if val < metrics.mean_poll_duration_worker_min {
1555 metrics.mean_poll_duration_worker_min = val;
1556 }
1557
1558 // First, scale the current value down
1559 let ratio = total_polls_count as f64 / metrics.total_polls_count as f64;
1560 let mut mean = metrics.mean_poll_duration.as_nanos() as f64 * ratio;
1561
1562 // Add the scaled current worker's mean poll duration
1563 let ratio = worker_polls_count as f64 / metrics.total_polls_count as f64;
1564 mean += val.as_nanos() as f64 * ratio;
1565
1566 metrics.mean_poll_duration = Duration::from_nanos(mean as u64);
1567 }
1568
1569 // Update the histogram counts if there were polls since last count
1570 if worker_polls_count > 0 {
1571 for (bucket, entry) in metrics.poll_time_histogram.buckets_mut().iter_mut().enumerate() {
1572 let new = rt.poll_time_histogram_bucket_count(self.worker, bucket);
1573 let delta = new.saturating_sub(self.poll_time_histogram[bucket]);
1574 self.poll_time_histogram[bucket] = new;
1575
1576 entry.add_count(delta);
1577 }
1578 }
1579
1580 // Local scheduled tasks is an absolute value
1581 let local_scheduled_tasks = rt.worker_local_queue_depth(self.worker);
1582 metrics.total_local_queue_depth = metrics.total_local_queue_depth.saturating_add(local_scheduled_tasks);
1583
1584 if local_scheduled_tasks > metrics.max_local_queue_depth {
1585 metrics.max_local_queue_depth = local_scheduled_tasks;
1586 }
1587
1588 if local_scheduled_tasks < metrics.min_local_queue_depth {
1589 metrics.min_local_queue_depth = local_scheduled_tasks;
1590 }
1591
1592 // Blocking queue depth is an absolute value too
1593 metrics.blocking_queue_depth = rt.blocking_queue_depth();
1594
1595 metrics.blocking_threads_count = rt.num_blocking_threads();
1596 metrics.idle_blocking_threads_count = rt.num_idle_blocking_threads();
1597 }
1598 }
1599}
1600
1601derived_metrics!(
1602 [RuntimeMetrics] {
1603 stable {
1604 /// Returns the ratio of the [`RuntimeMetrics::total_busy_duration`] to the [`RuntimeMetrics::elapsed`].
1605 pub fn busy_ratio(&self) -> f64 {
1606 self.total_busy_duration.as_nanos() as f64 / self.elapsed.as_nanos() as f64
1607 }
1608 }
1609 unstable {
1610 /// Returns the ratio of the [`RuntimeMetrics::total_polls_count`] to the [`RuntimeMetrics::total_noop_count`].
1611 pub fn mean_polls_per_park(&self) -> f64 {
1612 let total_park_count = self.total_park_count.saturating_sub(self.total_noop_count);
1613 if total_park_count == 0 {
1614 0.0
1615 } else {
1616 self.total_polls_count as f64 / total_park_count as f64
1617 }
1618 }
1619 }
1620 }
1621);
1622
1623#[cfg(all(test, tokio_unstable, feature = "metrique-integration"))]
1624mod metrique_integration_tests {
1625 use super::*;
1626 use metrique::test_util::test_metric;
1627
1628 /// Compile-time regression: if a field is added whose type doesn't
1629 /// implement `CloseValue`, this will fail to compile.
1630 #[test]
1631 fn metrique_integration_produces_expected_fields() {
1632 let metrics = RuntimeMetrics {
1633 workers_count: 4,
1634 total_park_count: 100,
1635 poll_time_histogram: PollTimeHistogram::new(vec![
1636 HistogramBucket::new(Duration::from_micros(0), Duration::from_micros(100), 10),
1637 HistogramBucket::new(Duration::from_micros(100), Duration::from_micros(200), 0),
1638 HistogramBucket::new(Duration::from_micros(200), Duration::from_micros(500), 3),
1639 ]),
1640 ..Default::default()
1641 };
1642
1643 let entry = test_metric(metrics);
1644
1645 // Stable fields
1646 assert_eq!(entry.metrics["workers_count"], 4);
1647 assert_eq!(entry.metrics["total_park_count"], 100);
1648 assert_eq!(entry.metrics["elapsed"].as_f64(), 0.0);
1649 assert_eq!(entry.metrics["total_busy_duration"].as_f64(), 0.0);
1650 assert_eq!(entry.metrics["global_queue_depth"].as_u64(), 0);
1651
1652 // Unstable fields
1653 assert_eq!(entry.metrics["mean_poll_duration"].as_f64(), 0.0);
1654 assert_eq!(entry.metrics["total_steal_count"].as_u64(), 0);
1655 assert_eq!(entry.metrics["total_polls_count"].as_u64(), 0);
1656
1657 // 2 non-zero buckets (count 10 and 3) should produce 2 observations
1658 let hist = &entry.metrics["poll_time_histogram"];
1659 assert_eq!(hist.distribution.len(), 2, "expected 2 non-zero buckets");
1660
1661 // midpoint of 0..100µs = 50µs, count = 10
1662 match hist.distribution[0] {
1663 metrique::writer::Observation::Repeated { total, occurrences } => {
1664 assert_eq!(occurrences, 10);
1665 assert!((total - 500.0).abs() < 0.01, "expected 50 * 10 = 500, got {total}");
1666 }
1667 other => panic!("expected Repeated, got {other:?}"),
1668 }
1669
1670 // midpoint of 200..500µs = 350µs, count = 3
1671 match hist.distribution[1] {
1672 metrique::writer::Observation::Repeated { total, occurrences } => {
1673 assert_eq!(occurrences, 3);
1674 assert!((total - 1050.0).abs() < 0.01, "expected 350 * 3 = 1050, got {total}");
1675 }
1676 other => panic!("expected Repeated, got {other:?}"),
1677 }
1678 }
1679
1680 /// Collect `RuntimeMetrics` from a live Tokio runtime and verify the pipeline produces valid output.
1681 #[cfg(feature = "rt")]
1682 #[test]
1683 fn metrique_end_to_end() {
1684 let rt = tokio::runtime::Builder::new_current_thread()
1685 .enable_all()
1686 .enable_metrics_poll_time_histogram()
1687 .build()
1688 .unwrap();
1689
1690 rt.block_on(async {
1691 let handle = tokio::runtime::Handle::current();
1692 let monitor = RuntimeMonitor::new(&handle);
1693 let mut intervals = monitor.intervals();
1694
1695 let _ = intervals.next().unwrap();
1696
1697 // Spawn tasks to create some work for the runtime to poll.
1698 let mut metrics_with_polls = None;
1699 for _ in 0..4 {
1700 for _ in 0..25 {
1701 tokio::spawn(async {
1702 tokio::task::yield_now().await;
1703 })
1704 .await
1705 .unwrap();
1706 }
1707 // Slow poll (>900µs) to land in the last histogram bucket.
1708 tokio::spawn(async {
1709 std::thread::sleep(Duration::from_millis(1));
1710 })
1711 .await
1712 .unwrap();
1713
1714 let metrics = intervals.next().unwrap();
1715 let total_polls: u64 = metrics.poll_time_histogram.buckets().iter().map(|b| b.count()).sum();
1716 if total_polls > 0 {
1717 metrics_with_polls = Some(metrics);
1718 break;
1719 }
1720 }
1721 let metrics = metrics_with_polls.expect("expected polls to be recorded within 4 sampled intervals");
1722
1723 let expected_workers_count = metrics.workers_count;
1724 let expected_non_zero_buckets = metrics
1725 .poll_time_histogram
1726 .buckets()
1727 .iter()
1728 .filter(|b| b.count() > 0)
1729 .count();
1730
1731 let expected_total_polls: u64 = metrics.poll_time_histogram.buckets().iter().map(|b| b.count()).sum();
1732 assert!(expected_workers_count > 0);
1733 assert!(expected_total_polls > 0);
1734
1735 let last_bucket = metrics.poll_time_histogram.buckets().last().unwrap();
1736
1737 // Sanity check: Tokio's last histogram bucket ends at Duration::from_nanos(u64::MAX)
1738 assert_eq!(last_bucket.range_end(), Duration::from_nanos(u64::MAX));
1739 assert!(last_bucket.count() > 0, "expected slow poll to land in last bucket");
1740 let last_bucket_start_us = last_bucket.range_start().as_micros() as f64;
1741 let last_bucket_count = last_bucket.count();
1742
1743 let entry = test_metric(metrics);
1744
1745 assert_eq!(entry.metrics["workers_count"], expected_workers_count as u64);
1746 assert!(entry.metrics["elapsed"].as_f64() >= 0.0);
1747 assert!(entry.metrics["total_busy_duration"].as_f64() >= 0.0);
1748
1749 let hist = &entry.metrics["poll_time_histogram"];
1750 assert_eq!(hist.distribution.len(), expected_non_zero_buckets);
1751 let observed_total_occurrences: u64 = hist
1752 .distribution
1753 .iter()
1754 .map(|obs| match obs {
1755 metrique::writer::Observation::Repeated { occurrences, .. } => *occurrences,
1756 other => panic!("expected Repeated, got {other:?}"),
1757 })
1758 .sum();
1759 assert_eq!(observed_total_occurrences, expected_total_polls);
1760
1761 // The last observation corresponds to the last histogram bucket.
1762 // Verify it uses range_start as the representative value instead of a midpoint,
1763 // since the last bucket range_end is Duration::from_nanos(u64::MAX).
1764 let last_obs = hist.distribution.last().unwrap();
1765 match last_obs {
1766 metrique::writer::Observation::Repeated { total, occurrences } => {
1767 assert_eq!(*occurrences, last_bucket_count);
1768 let expected_total = last_bucket_start_us * last_bucket_count as f64;
1769 assert!(
1770 (total - expected_total).abs() < 0.01,
1771 "last bucket should use range_start ({last_bucket_start_us}µs) as representative value, \
1772 expected total={expected_total}, got {total}"
1773 );
1774 }
1775 other => panic!("expected Repeated, got {other:?}"),
1776 }
1777 });
1778 }
1779}