future_timing/
lib.rs

1//! Future timing instrumentation.
2//!
3//! Provides instrumentation to record the time taken by a future. This includes the busy time and
4//! the idle time.
5//!
6//! ## Busy time
7//!
8//! The busy time of a future is the sum of all the time consumed during calls to [`Future::poll`]
9//! on that future.
10//!
11//! ## Idle time
12//!
13//! The idle time of a future is the sum of all the time between calls to [`Future::poll`]. The
14//! time before the first poll is not included.
15//!
16//! # Usage
17//!
18//! First, add this to your Cargo.toml `dependencies`:
19//!
20//! ```toml
21//! future-timing = "0.1"
22//! ```
23//!
24//! Record the timing of a future in the following manner.
25//!
26//! ```
27//! # async fn some_async_fn() -> u64 {
28//! #   tokio::time::sleep(std::time::Duration::from_micros(10)).await;
29//! #   42
30//! # }
31//! # fn do_something_with_output(_: u64) {}
32//! # #[tokio::main]
33//! # async fn main() {
34//!     let output = future_timing::timed(some_async_fn()).await;
35//!     let (timing, future_output) = output.into_parts();
36//!
37//!     do_something_with_output(future_output);
38//!
39//!     assert!(!timing.idle().is_zero());
40//!     assert!(!timing.busy().is_zero());
41//! # }
42//! ```
43//!
44//! # Comparison with similar crates
45//!
46//! This is a single purpose crate, created because I couldn't find any other crate that included
47//! the functionality I needed, which is to say, record future timing and make it available to the
48//! code that awaited the future upon that future resolving.
49//!
50//! If you want to record and analyze the timing of many different futures (and you're using the
51//! [Tokio runtime], then you can use Tokio's [`RuntimeMetrics`] for an aggregated view or [Tokio
52//! Console] to see the timings of each task individually.
53//!
54//! If you don't actualy want to record the timing of a future, but instead want a future which
55//! resolves after a specific period of time, then you're in the wrong place. Have a look at the
56//! [`async-timer`] crate instead.
57//!
58//! # Supported Rust Versions
59//!
60//! `future-timing` is built against the latest stable release. The minimum supported version is
61//! 1.70. The current version of `future-timing` is not guaranteed to build on Rust versions earlier
62//! than the minimum supported version.
63//!
64//! # License
65//!
66//! This project is licensed under the [MIT license].
67//!
68//! ## Contribution
69//!
70//! Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion
71//! in `future-timing` by you, shall be licensed as MIT, without any additional terms or conditions.
72//!
73//! [`async-timer`]: https://docs.rs/async-timer/latest/async_timer/
74//! [`RuntimeMetrics`]: https://docs.rs/tokio/latest/tokio/runtime/struct.RuntimeMetrics.html
75//! [Tokio Console]: https://docs.rs/tokio-console/latest/tokio_console/
76//! [Tokio runtime]: https://docs.rs/tokio/latest/tokio/
77//! [MIT license]: https://github.com/hds/future-timing/blob/main/LICENSE
78use std::{
79    future::Future,
80    pin::Pin,
81    task::{Context, Poll},
82    time::{Duration, Instant},
83};
84
85use pin_project_lite::pin_project;
86
87/// Instrument a future to record its timing
88///
89/// The busy and idle time for the future will be recorded separately in the result together with
90/// the output of the wrapped future. See the documentation for [`Timing`] for more details.
91///
92/// # Examples
93///
94/// ```
95/// # async fn some_async_fn() -> u64 {
96/// #   tokio::time::sleep(std::time::Duration::from_micros(10)).await;
97/// #   42
98/// # }
99/// # fn do_something_with_output(_: u64) {}
100/// # #[tokio::main]
101/// # async fn main() {
102///     let output = future_timing::timed(some_async_fn()).await;
103///     let (timing, future_output) = output.into_parts();
104///
105///     do_something_with_output(future_output);
106///
107///     assert!(!timing.idle().is_zero());
108///     assert!(!timing.busy().is_zero());
109/// # }
110pub fn timed<F>(fut: F) -> Timed<F>
111where
112    F: Future,
113{
114    Timed::new(fut)
115}
116
117pin_project! {
118    /// Instrumentation to record the timing of a wrapped future.
119    ///
120    /// The `Timed` wraps any future and records the inner future's busy and idle time. The
121    /// timing is returned together with the inner future's output once it resolves ready.
122    ///
123    /// # Examples
124    ///
125    /// To wrap a future, use the [`timed`] function.
126    ///
127    /// ```
128    /// # async fn some_async_fn() -> u64 {
129    /// #   tokio::time::sleep(std::time::Duration::from_micros(10)).await;
130    /// #   42
131    /// # }
132    /// # fn do_something_with_output(_: u64) {}
133    ///
134    /// # #[tokio::main]
135    /// # async fn main() {
136    ///     let output = future_timing::timed(some_async_fn()).await;
137    ///     let (timing, future_output) = output.into_parts();
138    ///
139    ///     do_something_with_output(future_output);
140    ///
141    ///     assert!(!timing.idle().is_zero());
142    ///     assert!(!timing.busy().is_zero());
143    /// # }
144    /// ```
145    pub struct Timed<F>
146    where
147        F: Future,
148    {
149        last_poll_end: Option<Instant>,
150        idle: Duration,
151        busy: Duration,
152
153        #[pin]
154        inner: F,
155    }
156}
157
158impl<F> Timed<F>
159where
160    F: Future,
161{
162    fn new(inner: F) -> Self {
163        Self {
164            last_poll_end: None,
165            idle: Duration::ZERO,
166            busy: Duration::ZERO,
167
168            inner,
169        }
170    }
171}
172
173impl<F> Future for Timed<F>
174where
175    F: Future,
176{
177    type Output = TimingOutput<F::Output>;
178
179    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<TimingOutput<F::Output>> {
180        let start = Instant::now();
181        let mut this = self.project();
182        let result = this.inner.as_mut().poll(cx);
183        let end = Instant::now();
184
185        if let Some(last_poll_end) = this.last_poll_end.take() {
186            *this.idle += start - last_poll_end;
187        }
188        *this.busy += end - start;
189        *this.last_poll_end = Some(end);
190
191        match result {
192            Poll::Pending => Poll::Pending,
193            Poll::Ready(output) => Poll::Ready(TimingOutput {
194                timing: Timing {
195                    idle: *this.idle,
196                    busy: *this.busy,
197                },
198                inner: output,
199            }),
200        }
201    }
202}
203
204/// A wrapper around timing information for an instrumented future and that future's output.
205///
206/// See the documentation on [`Timing`] for further details.
207#[derive(Clone, Copy, Debug, Hash, PartialEq)]
208pub struct TimingOutput<T> {
209    timing: Timing,
210    inner: T,
211}
212
213impl<T> TimingOutput<T> {
214    /// Returns the timing of the future that was instrumented.
215    ///
216    /// # Examples
217    ///
218    /// ```
219    /// # async fn some_async_fn() {}
220    /// # #[tokio::main]
221    /// # async fn main() {
222    ///     let output = future_timing::timed(some_async_fn()).await;
223    ///     let timing: future_timing::Timing = output.timing();
224    /// #   _ = timing
225    /// # }
226    /// ```
227    #[must_use]
228    pub fn timing(&self) -> Timing {
229        self.timing
230    }
231
232    /// Returns the timing of the future and its output.
233    ///
234    /// # Examples
235    ///
236    /// ```
237    /// # async fn some_async_fn() {}
238    /// # #[tokio::main]
239    /// # async fn main() {
240    ///     let output = future_timing::timed(some_async_fn()).await;
241    ///     let (timing, future_output) = output.into_parts();
242    /// #   _ = timing;
243    /// #   _ = future_output;
244    /// # }
245    /// ```
246    #[must_use]
247    pub fn into_parts(self) -> (Timing, T) {
248        (self.timing, self.inner)
249    }
250
251    /// Returns the future's output.
252    ///
253    /// # Examples
254    ///
255    /// ```
256    /// # async fn some_async_fn() {}
257    /// # #[tokio::main]
258    /// # async fn main() {
259    ///     let output = future_timing::timed(some_async_fn()).await;
260    ///     let future_output = output.into_inner();
261    /// #   _ = future_output;
262    /// # }
263    /// ```
264    #[must_use]
265    pub fn into_inner(self) -> T {
266        self.inner
267    }
268}
269
270/// The timing information for an instrumented future.
271///
272/// The busy time and the idle time of the instrumented future is available.
273///
274/// ## Busy time
275///
276/// The busy time of a future is the sum of all the time consumed during calls to [`Future::poll`]
277/// on that future.
278///
279/// The busy time will always be non-zero.
280///
281/// ## Idle time
282///
283/// The idle time of a future is the sum of all the time between calls to [`Future::poll`]. The
284/// time before the first poll is not included.
285///
286/// The idle time may be zero if the inner future returns [`Poll::Ready`] on the first poll (and so
287/// never returns [`Poll::Pending`]).
288#[derive(Clone, Copy, Debug, Hash, PartialEq)]
289pub struct Timing {
290    idle: Duration,
291    busy: Duration,
292}
293
294impl Timing {
295    /// The sum of all poll durations.
296    ///
297    /// This is the total time the future was polled across all polls.
298    #[must_use]
299    pub fn busy(&self) -> Duration {
300        self.busy
301    }
302
303    /// The sum of all durations between polls.
304    ///
305    /// This is the total time between each poll of the timed future. The time before the first
306    /// poll and after the final poll is not included.
307    #[must_use]
308    pub fn idle(&self) -> Duration {
309        self.idle
310    }
311}