Skip to main content

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}