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 busy_duration_total: u64,
14
15 processing_scheduled_tasks_started_at: Option<Instant>,
17
18 park_count: u64,
20
21 park_unpark_count: u64,
23
24 #[cfg(tokio_unstable)]
25 noop_count: u64,
27
28 #[cfg(tokio_unstable)]
29 steal_count: u64,
31
32 #[cfg(tokio_unstable)]
33 steal_operations: u64,
35
36 #[cfg(tokio_unstable)]
37 poll_count: u64,
39
40 #[cfg(tokio_unstable)]
41 poll_count_on_last_park: u64,
44
45 #[cfg(tokio_unstable)]
46 local_schedule_count: u64,
48
49 #[cfg(tokio_unstable)]
50 overflow_count: u64,
53
54 #[cfg(tokio_unstable)]
55 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 poll_counts: HistogramBatch,
66
67 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 #[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 pub(crate) fn about_to_park(&mut self) {
186 self.park_count += 1;
187 self.park_unpark_count += 1;
188 }
189 },
190 unstable: {
191 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 pub(crate) fn unparked(&mut self) {
208 self.park_unpark_count += 1;
209 }
210
211 pub(crate) fn start_processing_scheduled_tasks(&mut self) {
213 self.processing_scheduled_tasks_started_at = now();
214 }
215
216 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 pub(crate) fn start_poll(
230 &mut self,
231 _schedule_latency_context: Option<ScheduleLatencyContext>,
232 ) -> Option<Duration> {
233 None
234 }
235 },
236 unstable: {
237 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 #[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 #[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 pub(crate) fn end_poll(&mut self) {}
324 },
325 unstable: {
326 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
389fn 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}