Skip to main content

tokio/runtime/metrics/
batch.rs

1use crate::runtime::metrics::WorkerMetrics;
2
3cfg_unstable_metrics! {
4    use crate::runtime::metrics::HistogramBatch;
5}
6
7use crate::runtime::metrics::ScheduleLatencyContext;
8use std::sync::atomic::Ordering::Relaxed;
9use std::time::{Duration, Instant};
10
11pub(crate) struct MetricsBatch {
12    /// The total busy duration in nanoseconds.
13    busy_duration_total: u64,
14
15    /// Instant at which work last resumed (continued after park).
16    processing_scheduled_tasks_started_at: Option<Instant>,
17
18    /// Number of times the worker parked.
19    park_count: u64,
20
21    /// Number of times the worker parked and unparked.
22    park_unpark_count: u64,
23
24    #[cfg(tokio_unstable)]
25    /// Number of times the worker woke w/o doing work.
26    noop_count: u64,
27
28    #[cfg(tokio_unstable)]
29    /// Number of tasks stolen.
30    steal_count: u64,
31
32    #[cfg(tokio_unstable)]
33    /// Number of times tasks where stolen.
34    steal_operations: u64,
35
36    #[cfg(tokio_unstable)]
37    /// Number of tasks that were polled by the worker.
38    poll_count: u64,
39
40    #[cfg(tokio_unstable)]
41    /// Number of tasks polled when the worker entered park. This is used to
42    /// track the noop count.
43    poll_count_on_last_park: u64,
44
45    #[cfg(tokio_unstable)]
46    /// Number of tasks that were scheduled locally on this worker.
47    local_schedule_count: u64,
48
49    #[cfg(tokio_unstable)]
50    /// Number of tasks moved to the global queue to make space in the local
51    /// queue
52    overflow_count: u64,
53
54    #[cfg(tokio_unstable)]
55    /// If `Some`, tracks poll times in nanoseconds
56    poll_timer: Option<PollTimer>,
57
58    #[cfg(feature = "schedule-latency")]
59    schedule_latencies: Option<HistogramBatch>,
60}
61
62cfg_unstable_metrics! {
63    struct PollTimer {
64        /// Histogram of poll counts within each band.
65        poll_counts: HistogramBatch,
66
67        /// Instant when the most recent task started polling.
68        poll_started_at: Instant,
69    }
70}
71
72impl MetricsBatch {
73    pub(crate) fn new(worker_metrics: &WorkerMetrics) -> MetricsBatch {
74        let maybe_now = now();
75        Self::new_unstable(worker_metrics, maybe_now)
76    }
77
78    cfg_metrics_variant! {
79        stable: {
80            #[inline(always)]
81            fn new_unstable(_worker_metrics: &WorkerMetrics, maybe_now: Option<Instant>) -> MetricsBatch {
82                MetricsBatch {
83                    busy_duration_total: 0,
84                    processing_scheduled_tasks_started_at: maybe_now,
85                    park_count: 0,
86                    park_unpark_count: 0,
87                }
88            }
89        },
90        unstable: {
91            #[inline(always)]
92            fn new_unstable(worker_metrics: &WorkerMetrics, maybe_now: Option<Instant>) -> MetricsBatch {
93                let poll_timer = maybe_now.and_then(|now| {
94                    worker_metrics
95                        .poll_count_histogram
96                        .as_ref()
97                        .map(|worker_poll_counts| PollTimer {
98                            poll_counts: HistogramBatch::from_histogram(worker_poll_counts),
99                            poll_started_at: now,
100                        })
101                });
102                // Schedule latencies cannot be tracked if `Instant::now()` is unavailable
103                #[cfg(feature = "schedule-latency")]
104                let schedule_latencies = maybe_now.and_then(|_| {
105                    worker_metrics
106                        .schedule_latency_histogram
107                        .as_ref()
108                        .map(HistogramBatch::from_histogram)
109                });
110                MetricsBatch {
111                    park_count: 0,
112                    park_unpark_count: 0,
113                    noop_count: 0,
114                    steal_count: 0,
115                    steal_operations: 0,
116                    poll_count: 0,
117                    poll_count_on_last_park: 0,
118                    local_schedule_count: 0,
119                    overflow_count: 0,
120                    busy_duration_total: 0,
121                    processing_scheduled_tasks_started_at: maybe_now,
122                    poll_timer,
123                    #[cfg(feature = "schedule-latency")]
124                    schedule_latencies,
125                }
126            }
127        }
128    }
129
130    pub(crate) fn submit(&mut self, worker: &WorkerMetrics, mean_poll_time: u64) {
131        worker
132            .busy_duration_total
133            .store(self.busy_duration_total, Relaxed);
134
135        self.submit_unstable(worker, mean_poll_time);
136    }
137
138    cfg_metrics_variant! {
139        stable: {
140            #[inline(always)]
141            fn submit_unstable(&mut self, worker: &WorkerMetrics, _mean_poll_time: u64) {
142                worker.park_count.store(self.park_count, Relaxed);
143                worker
144                    .park_unpark_count
145                    .store(self.park_unpark_count, Relaxed);
146            }
147        },
148        unstable: {
149            #[inline(always)]
150            fn submit_unstable(&mut self, worker: &WorkerMetrics, mean_poll_time: u64) {
151                worker.mean_poll_time.store(mean_poll_time, Relaxed);
152                worker.park_count.store(self.park_count, Relaxed);
153                worker
154                    .park_unpark_count
155                    .store(self.park_unpark_count, Relaxed);
156                worker.noop_count.store(self.noop_count, Relaxed);
157                worker.steal_count.store(self.steal_count, Relaxed);
158                worker
159                    .steal_operations
160                    .store(self.steal_operations, Relaxed);
161                worker.poll_count.store(self.poll_count, Relaxed);
162
163                worker
164                    .local_schedule_count
165                    .store(self.local_schedule_count, Relaxed);
166                worker.overflow_count.store(self.overflow_count, Relaxed);
167
168                if let Some(poll_timer) = &self.poll_timer {
169                    let dst = worker.poll_count_histogram.as_ref().unwrap();
170                    poll_timer.poll_counts.submit(dst);
171                }
172
173                #[cfg(feature = "schedule-latency")]
174                if let Some(schedule_latencies) = &self.schedule_latencies {
175                    let dst = worker.schedule_latency_histogram.as_ref().unwrap();
176                    schedule_latencies.submit(dst);
177                }
178            }
179        }
180    }
181
182    cfg_metrics_variant! {
183        stable: {
184            /// The worker is about to park.
185            pub(crate) fn about_to_park(&mut self) {
186                self.park_count += 1;
187                self.park_unpark_count += 1;
188            }
189        },
190        unstable: {
191            /// The worker is about to park.
192            pub(crate) fn about_to_park(&mut self) {
193                {
194                    self.park_count += 1;
195                    self.park_unpark_count += 1;
196
197                    if self.poll_count_on_last_park == self.poll_count {
198                        self.noop_count += 1;
199                    } else {
200                        self.poll_count_on_last_park = self.poll_count;
201                    }
202                }
203            }
204        }
205    }
206    /// The worker was unparked.
207    pub(crate) fn unparked(&mut self) {
208        self.park_unpark_count += 1;
209    }
210
211    /// Start processing a batch of tasks
212    pub(crate) fn start_processing_scheduled_tasks(&mut self) {
213        self.processing_scheduled_tasks_started_at = now();
214    }
215
216    /// Stop processing a batch of tasks
217    pub(crate) fn end_processing_scheduled_tasks(&mut self) {
218        if let Some(processing_scheduled_tasks_started_at) =
219            self.processing_scheduled_tasks_started_at
220        {
221            let busy_duration = processing_scheduled_tasks_started_at.elapsed();
222            self.busy_duration_total += duration_as_u64(busy_duration);
223        }
224    }
225
226    cfg_metrics_variant! {
227        stable: {
228            /// Start polling an individual task
229            pub(crate) fn start_poll(&mut self, _task_scheduled_at: Option<ScheduleLatencyContext>) {}
230        },
231        unstable: {
232            /// Start polling an individual task
233            ///
234            /// # Arguments
235            ///
236            /// `task_scheduled_at` is used to calculate task schedule latency.
237            /// A `ScheduleLatencyContext` can be obtained by calling `prepare` on a task's
238            /// `ScheduleLatencyInstant`.
239            pub(crate) fn start_poll(&mut self, _task_scheduled_at: Option<ScheduleLatencyContext>) {
240                self.poll_count += 1;
241                if let Some(poll_timer) = &mut self.poll_timer {
242                    poll_timer.poll_started_at = Instant::now();
243                }
244                #[cfg(feature = "schedule-latency")]
245                if let Some(task_scheduled_at) = _task_scheduled_at {
246                    if let Some(schedule_latencies) = &mut self.schedule_latencies {
247                        if let Some(now) = self.poll_timer.as_ref().map(|p| p.poll_started_at).or_else(now) {
248                            let elapsed = task_scheduled_at.elapsed_nanos(now);
249                            schedule_latencies.measure(elapsed, 1);
250                        }
251                    }
252                }
253            }
254        }
255    }
256
257    cfg_metrics_variant! {
258        stable: {
259            /// Stop polling an individual task
260            pub(crate) fn end_poll(&mut self) {}
261        },
262        unstable: {
263            /// Stop polling an individual task
264            pub(crate) fn end_poll(&mut self) {
265                if let Some(poll_timer) = &mut self.poll_timer {
266                    let elapsed = duration_as_u64(poll_timer.poll_started_at.elapsed());
267                    poll_timer.poll_counts.measure(elapsed, 1);
268                }
269            }
270        }
271    }
272
273    cfg_metrics_variant! {
274        stable: {
275            pub(crate) fn inc_local_schedule_count(&mut self) {}
276        },
277        unstable: {
278            pub(crate) fn inc_local_schedule_count(&mut self) {
279                self.local_schedule_count += 1;
280            }
281        }
282    }
283}
284
285cfg_rt_multi_thread! {
286    impl MetricsBatch {
287        cfg_metrics_variant! {
288            stable: {
289                pub(crate) fn incr_steal_count(&mut self, _by: u16) {}
290            },
291            unstable: {
292                pub(crate) fn incr_steal_count(&mut self, by: u16) {
293                    self.steal_count += by as u64;
294                }
295            }
296        }
297
298        cfg_metrics_variant! {
299            stable: {
300                pub(crate) fn incr_steal_operations(&mut self) {}
301            },
302            unstable: {
303                pub(crate) fn incr_steal_operations(&mut self) {
304                    self.steal_operations += 1;
305                }
306            }
307        }
308
309        cfg_metrics_variant! {
310            stable: {
311                pub(crate) fn incr_overflow_count(&mut self) {}
312            },
313            unstable: {
314                pub(crate) fn incr_overflow_count(&mut self) {
315                    self.overflow_count += 1;
316                }
317            }
318        }
319    }
320}
321
322pub(crate) fn duration_as_u64(dur: Duration) -> u64 {
323    u64::try_from(dur.as_nanos()).unwrap_or(u64::MAX)
324}
325
326/// Gate unsupported time metrics for `wasm32-unknown-unknown`
327/// <https://github.com/tokio-rs/tokio/issues/7319>
328fn now() -> Option<Instant> {
329    if cfg!(all(
330        target_arch = "wasm32",
331        target_os = "unknown",
332        target_vendor = "unknown"
333    )) {
334        None
335    } else {
336        Some(Instant::now())
337    }
338}