@@ -256,7 +256,7 @@ pub struct RuntimeMetrics {
256256
257257 /// The number of tasks worker threads stole from another worker thread.
258258 ///
259- /// The worker steal count starts increases by the amount of stolen tasks each time the worker
259+ /// The worker steal count increases by the amount of stolen tasks each time the worker
260260 /// has processed its scheduled queue and successfully steals more pending tasks from another
261261 /// worker.
262262 ///
@@ -344,6 +344,95 @@ pub struct RuntimeMetrics {
344344 /// - [`RuntimeMetrics::max_steal_count`]
345345 pub min_steal_count : u64 ,
346346
347+ /// The number of times worker threads stole tasks from another worker thread.
348+ ///
349+ /// The worker steal operations increases by one each time the worker has processed its
350+ /// scheduled queue and successfully steals more pending tasks from another worker.
351+ ///
352+ /// This metric only applies to the **multi-threaded** runtime and will always return `0` when
353+ /// using the current thread runtime.
354+ ///
355+ /// ##### Definition
356+ /// This metric is derived from the sum of [`tokio::runtime::RuntimeMetrics::worker_steal_operations`]
357+ /// for all worker threads.
358+ ///
359+ /// ##### See also
360+ /// - [`RuntimeMetrics::min_steal_operations`]
361+ /// - [`RuntimeMetrics::max_steal_operations`]
362+ ///
363+ /// ##### Examples
364+ /// In the below example, a blocking channel is used to backup one worker thread:
365+ /// ```
366+ /// #[tokio::main(flavor = "multi_thread", worker_threads = 2)]
367+ /// async fn main() {
368+ /// let handle = tokio::runtime::Handle::current();
369+ /// let monitor = tokio_metrics::RuntimeMonitor::new(&handle);
370+ /// let mut intervals = monitor.intervals();
371+ /// let mut next_interval = || intervals.next().unwrap();
372+ ///
373+ /// let interval = next_interval(); // end of first sampling interval
374+ /// assert_eq!(interval.total_steal_operations, 0);
375+ /// assert_eq!(interval.min_steal_operations, 0);
376+ /// assert_eq!(interval.max_steal_operations, 0);
377+ ///
378+ /// // induce a steal
379+ /// async {
380+ /// let (tx, rx) = std::sync::mpsc::channel();
381+ /// // Move to the runtime.
382+ /// tokio::spawn(async move {
383+ /// // Spawn the task that sends to the channel
384+ /// tokio::spawn(async move {
385+ /// tx.send(()).unwrap();
386+ /// });
387+ /// // Spawn a task that bumps the previous task out of the "next
388+ /// // scheduled" slot.
389+ /// tokio::spawn(async {});
390+ /// // Blocking receive on the channe.
391+ /// rx.recv().unwrap();
392+ /// flush_metrics().await;
393+ /// }).await.unwrap();
394+ /// flush_metrics().await;
395+ /// }.await;
396+ ///
397+ /// let interval = { flush_metrics().await; next_interval() }; // end of interval 2
398+ /// assert_eq!(interval.total_steal_operations, 1);
399+ /// assert_eq!(interval.min_steal_operations, 0);
400+ /// assert_eq!(interval.max_steal_operations, 1);
401+ ///
402+ /// let interval = { flush_metrics().await; next_interval() }; // end of interval 3
403+ /// assert_eq!(interval.total_steal_operations, 0);
404+ /// assert_eq!(interval.min_steal_operations, 0);
405+ /// assert_eq!(interval.max_steal_operations, 0);
406+ /// }
407+ ///
408+ /// async fn flush_metrics() {
409+ /// let _ = tokio::time::sleep(std::time::Duration::ZERO).await;
410+ /// }
411+ /// ```
412+ pub total_steal_operations : u64 ,
413+
414+ /// The maximum number of times any worker thread stole tasks from another worker thread.
415+ ///
416+ /// ##### Definition
417+ /// This metric is derived from the maximum of [`tokio::runtime::RuntimeMetrics::worker_steal_operations`]
418+ /// across all worker threads.
419+ ///
420+ /// ##### See also
421+ /// - [`RuntimeMetrics::total_steal_operations`]
422+ /// - [`RuntimeMetrics::min_steal_operations`]
423+ pub max_steal_operations : u64 ,
424+
425+ /// The minimum number of times any worker thread stole tasks from another worker thread.
426+ ///
427+ /// ##### Definition
428+ /// This metric is derived from the minimum of [`tokio::runtime::RuntimeMetrics::worker_steal_operations`]
429+ /// across all worker threads.
430+ ///
431+ /// ##### See also
432+ /// - [`RuntimeMetrics::total_steal_operations`]
433+ /// - [`RuntimeMetrics::max_steal_operations`]
434+ pub min_steal_operations : u64 ,
435+
347436 /// The number of tasks scheduled from **outside** of the runtime.
348437 ///
349438 /// The remote schedule count increases by one each time a task is woken from **outside** of
@@ -959,6 +1048,7 @@ struct Worker {
9591048 total_park_count : u64 ,
9601049 total_noop_count : u64 ,
9611050 total_steal_count : u64 ,
1051+ total_steal_operations : u64 ,
9621052 total_local_schedule_count : u64 ,
9631053 total_overflow_count : u64 ,
9641054 total_polls_count : u64 ,
@@ -1106,6 +1196,7 @@ impl Worker {
11061196 total_park_count : rt. worker_park_count ( worker) ,
11071197 total_noop_count : rt. worker_noop_count ( worker) ,
11081198 total_steal_count : rt. worker_steal_count ( worker) ,
1199+ total_steal_operations : rt. worker_steal_operations ( worker) ,
11091200 total_local_schedule_count : rt. worker_local_schedule_count ( worker) ,
11101201 total_overflow_count : rt. worker_overflow_count ( worker) ,
11111202 total_polls_count : rt. worker_poll_count ( worker) ,
@@ -1150,6 +1241,12 @@ impl Worker {
11501241 min_steal_count,
11511242 worker_steal_count
11521243 ) ;
1244+ metric ! (
1245+ total_steal_operations,
1246+ max_steal_operations,
1247+ min_steal_operations,
1248+ worker_steal_operations
1249+ ) ;
11531250 metric ! (
11541251 total_local_schedule_count,
11551252 max_local_schedule_count,
0 commit comments