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(all(tokio_unstable, 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(all(tokio_unstable, 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(all(tokio_unstable, 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(all(tokio_unstable, 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(
230                &mut self,
231                _schedule_latency_context: Option<ScheduleLatencyContext>,
232            ) -> Option<Duration> {
233                None
234            }
235        },
236        unstable: {
237            /// Start polling an individual task
238            ///
239            /// # Arguments
240            ///
241            /// `schedule_latency_context` is used to calculate task schedule latency.
242            pub(crate) fn start_poll(
243                &mut self,
244                schedule_latency_context: Option<ScheduleLatencyContext>,
245            ) -> Option<Duration> {
246                self.poll_count += 1;
247                let poll_started_at = self.poll_timer.as_mut().map(|poll_timer| {
248                    let now = Instant::now();
249                    poll_timer.poll_started_at = now;
250                    now
251                });
252
253                #[cfg(all(tokio_unstable, feature = "schedule-latency"))]
254                {
255                    self.record_schedule_latency_at(schedule_latency_context, poll_started_at)
256                }
257
258                #[cfg(not(all(tokio_unstable, feature = "schedule-latency")))]
259                {
260                    let _ = (poll_started_at, schedule_latency_context);
261                    None
262                }
263            }
264        }
265    }
266
267    cfg_metrics_variant! {
268        stable: {
269            /// Record the schedule latency of an additional task polled as part
270            /// of the current poll operation.
271            #[cfg(feature = "rt-multi-thread")]
272            pub(crate) fn record_schedule_latency(
273                &mut self,
274                _schedule_latency_context: Option<ScheduleLatencyContext>,
275            ) -> Option<Duration> {
276                None
277            }
278        },
279        unstable: {
280            /// Record the schedule latency of an additional task polled as part
281            /// of the current poll operation.
282            #[cfg(feature = "rt-multi-thread")]
283            pub(crate) fn record_schedule_latency(
284                &mut self,
285                schedule_latency_context: Option<ScheduleLatencyContext>,
286            ) -> Option<Duration> {
287                #[cfg(all(tokio_unstable, feature = "schedule-latency"))]
288                {
289                    self.record_schedule_latency_at(schedule_latency_context, None)
290                }
291
292                #[cfg(not(all(tokio_unstable, feature = "schedule-latency")))]
293                {
294                    let _ = schedule_latency_context;
295                    None
296                }
297            }
298        }
299    }
300
301    #[cfg(all(tokio_unstable, feature = "schedule-latency"))]
302    fn record_schedule_latency_at(
303        &mut self,
304        schedule_latency_context: Option<ScheduleLatencyContext>,
305        poll_started_at: Option<Instant>,
306    ) -> Option<Duration> {
307        let task_schedule_latency = schedule_latency_context.and_then(|schedule_latency_context| {
308            poll_started_at
309                .or_else(now)
310                .map(|now| Duration::from_nanos(schedule_latency_context.elapsed_nanos(now)))
311        });
312        if let (Some(task_schedule_latency), Some(schedule_latencies)) =
313            (task_schedule_latency, &mut self.schedule_latencies)
314        {
315            schedule_latencies.measure(duration_as_u64(task_schedule_latency), 1);
316        }
317        task_schedule_latency
318    }
319
320    cfg_metrics_variant! {
321        stable: {
322            /// Stop polling an individual task
323            pub(crate) fn end_poll(&mut self) {}
324        },
325        unstable: {
326            /// Stop polling an individual task
327            pub(crate) fn end_poll(&mut self) {
328                if let Some(poll_timer) = &mut self.poll_timer {
329                    let elapsed = duration_as_u64(poll_timer.poll_started_at.elapsed());
330                    poll_timer.poll_counts.measure(elapsed, 1);
331                }
332            }
333        }
334    }
335
336    cfg_metrics_variant! {
337        stable: {
338            pub(crate) fn inc_local_schedule_count(&mut self) {}
339        },
340        unstable: {
341            pub(crate) fn inc_local_schedule_count(&mut self) {
342                self.local_schedule_count += 1;
343            }
344        }
345    }
346}
347
348cfg_rt_multi_thread! {
349    impl MetricsBatch {
350        cfg_metrics_variant! {
351            stable: {
352                pub(crate) fn incr_steal_count(&mut self, _by: u16) {}
353            },
354            unstable: {
355                pub(crate) fn incr_steal_count(&mut self, by: u16) {
356                    self.steal_count += by as u64;
357                }
358            }
359        }
360
361        cfg_metrics_variant! {
362            stable: {
363                pub(crate) fn incr_steal_operations(&mut self) {}
364            },
365            unstable: {
366                pub(crate) fn incr_steal_operations(&mut self) {
367                    self.steal_operations += 1;
368                }
369            }
370        }
371
372        cfg_metrics_variant! {
373            stable: {
374                pub(crate) fn incr_overflow_count(&mut self) {}
375            },
376            unstable: {
377                pub(crate) fn incr_overflow_count(&mut self) {
378                    self.overflow_count += 1;
379                }
380            }
381        }
382    }
383}
384
385pub(crate) fn duration_as_u64(dur: Duration) -> u64 {
386    u64::try_from(dur.as_nanos()).unwrap_or(u64::MAX)
387}
388
389/// Gate unsupported time metrics for `wasm32-unknown-unknown`
390/// <https://github.com/tokio-rs/tokio/issues/7319>
391fn now() -> Option<Instant> {
392    if cfg!(all(
393        target_arch = "wasm32",
394        target_os = "unknown",
395        target_vendor = "unknown"
396    )) {
397        None
398    } else {
399        Some(Instant::now())
400    }
401}