use core::{ future::Future, pin::Pin, task::{ Context, Poll, }, time::Duration, }; use std::time::Instant; /// The execution report of the future, generated by [`FutureTracker`]. #[derive(Debug)] pub struct ExecutionTime { /// The time spent for real action of the future. pub busy: Duration, /// The idle time of the future. pub idle: Duration, /// The output of the future. pub output: Output, } /// A guard representing a span which has been entered and is currently /// executing. /// /// When the guard is dropped, the span will be exited. /// /// This is returned by the [`Span::enter`] function. #[derive(Debug)] #[must_use = "once a span has been entered, it should be exited"] struct Entered<'a> { span: &'a mut Span, busy_instant: Instant, } impl<'a> Entered<'a> { fn new(span: &'a mut Span) -> Self { Self { span, busy_instant: Instant::now(), } } } impl<'a> Drop for Entered<'a> { #[inline(always)] fn drop(&mut self) { self.span.busy = self.span.busy.saturating_add(self.busy_instant.elapsed()); self.span.do_exit() } } /// A handle representing a span, with the capability to enter the span if it /// exists. #[derive(Default, Debug, Clone)] struct Span { /// The cumulative busy(active) time of the future across all `poll` calls. busy: Duration, /// The cumulative idle time of the future across all `poll` calls. idle: Duration, /// An [`Instant`] to track the idle time. /// /// If this is `None`, then the span has either closed or was never enabled. idle_instant: Option, } impl Span { /// Enters this span, returning a guard that will exit the span when dropped. #[inline(always)] pub fn enter(&mut self) -> Entered<'_> { self.do_enter(); Entered::new(self) } #[inline(always)] fn do_enter(&mut self) { let idle_instant = core::mem::take(&mut self.idle_instant); if let Some(idle_instant) = idle_instant { self.idle = self.idle.saturating_add(idle_instant.elapsed()); } } #[inline(always)] fn do_exit(&mut self) { self.idle_instant = Some(Instant::now()); } } pin_project_lite::pin_project! { /// A [`Future`] that has been tracked with a [`Span`]. /// It tracks the execution time of the future, it's active and idle phases. #[derive(Debug, Clone)] #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct FutureTracker { #[pin] inner: T, span: Span, } } impl FutureTracker { /// Creates a [`FutureTracker`] wrapper around the `inner` future. pub fn new(inner: T) -> Self { Self { inner, span: Default::default(), } } } impl Future for FutureTracker { type Output = ExecutionTime; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); let enter = this.span.enter(); let output = this.inner.poll(cx); match output { Poll::Ready(output) => { drop(enter); Poll::Ready(ExecutionTime { busy: this.span.busy, idle: this.span.idle, output, }) } Poll::Pending => Poll::Pending, } } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn empty_future() { let future = async { tokio::task::yield_now().await }; let wrapper_future = FutureTracker::new(future); let result = wrapper_future.await; assert_eq!(result.idle.as_secs(), 0) } #[tokio::test] async fn idle_time_correct() { let future = async { tokio::time::sleep(Duration::from_secs(1)).await }; let wrapper_future = FutureTracker::new(future); let result = wrapper_future.await; assert_eq!(result.idle.as_secs(), 1) } #[tokio::test] async fn busy_time_correct() { let future = async { std::thread::sleep(Duration::from_secs(1)) }; let wrapper_future = FutureTracker::new(future); let result = wrapper_future.await; assert_eq!(result.idle.as_secs(), 0); assert_eq!(result.busy.as_secs(), 1); } #[tokio::test] async fn hybrid_time_correct() { let future = async { tokio::time::sleep(Duration::from_secs(2)).await; std::thread::sleep(Duration::from_secs(1)); }; let wrapper_future = FutureTracker::new(future); let result = wrapper_future.await; assert_eq!(result.idle.as_secs(), 2); assert_eq!(result.busy.as_secs(), 1); } #[tokio::test] async fn hybrid_time_correct_complex_case() { let future = async { tokio::time::sleep(Duration::from_secs(1)).await; std::thread::sleep(Duration::from_secs(1)); tokio::time::sleep(Duration::from_secs(2)).await; std::thread::sleep(Duration::from_secs(2)); tokio::time::sleep(Duration::from_secs(3)).await; }; let wrapper_future = FutureTracker::new(future); let result = wrapper_future.await; assert_eq!(result.idle.as_secs(), 6); assert_eq!(result.busy.as_secs(), 3); } }