future_timed/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. The busy time of a future is the sum of all the time consumed during calls to
5//! [`Future::poll`] on that future. On the other hand, The idle time of a future is the sum of all
6//! the time between calls to [`Future::poll`]. The time before the first poll is not included.
7//!
8//! # Usage
9//!
10//! Add `future-timed` to your Cargo.toml `dependencies`:
11//!
12//! ```toml
13//! future-timed = "0.1"
14//! ```
15//!
16//! Use the [`TimedFutureExt`] extension trait to add an instrumentation closure
17//! on a future:
18//!
19//! ```
20//! # use future_timed::{TimedFutureExt, Timing};
21//! # use std::time::Duration;
22//! # async fn some_async_fn() -> u64 {
23//! # tokio::time::sleep(Duration::from_micros(10)).await;
24//! # 42
25//! # }
26//! # fn do_something_with_output(_: u64) {}
27//! # #[tokio::main]
28//! # async fn main() {
29//! let output = some_async_fn()
30//! .timed(|Timing { idle, busy }| {
31//! assert!(!idle.is_zero());
32//! assert!(!busy.is_zero());
33//! })
34//! .await;
35//!
36//! do_something_with_output(output);
37//! # }
38//! ```
39//!
40//! # Comparison with similar crates
41//!
42//! This work is based almost entirely on the [future-timing] crate but sports a different API.
43//! While future-timing requires destructuring the future's output into the timing data and the
44//! futures output itself, fuiture-timed allows to report inline and compose the output with
45//! subsequent future combinators, for example from the [futures] crate:
46//!
47//! ```
48//! # use future_timed::{TimedFutureExt, Timing};
49//! # use futures::future::FutureExt;
50//! # use std::time::Duration;
51//! # #[tokio::main]
52//! # async fn main() {
53//! let output = async {
54//! tokio::time::sleep(Duration::from_micros(10)).await;
55//! 21
56//! }
57//! .timed(|Timing { busy, .. }| {
58//! println!("busy for {busy:?}");
59//! })
60//! .map(|n| 2 * n)
61//! .await;
62//!
63//! assert_eq!(output, 42);
64//! # }
65//! ```
66//!
67//! Note that in that case you measure the combined time for all wrapped futures.
68//!
69//! # License
70//!
71//! This project is licensed under the [MIT license].
72//!
73//! [MIT license]: https://github.com/matze/future-timed/blob/main/LICENSE
74//! [future-timing]: https://docs.rs/future-timing/latest/future_timing/
75//! [futures]: https://docs.rs/futures/latest/futures/index.html
76
77use std::future::Future;
78use std::pin::Pin;
79use std::task::{Context, Poll};
80use std::time::{Duration, Instant};
81
82use pin_project_lite::pin_project;
83
84/// Instrument a future to record its timing.
85///
86/// The busy and idle time for the future will be passed as an argument to the provided closure.
87/// See the documentation for [`Timing`] for more details. In general, it is more straightforward
88/// to use the [`TimedFutureExt`] extension trait to attach instrument a future directly.
89///
90/// # Examples
91///
92/// ```
93/// use future_timed::{timed, Timing};
94/// # async fn some_async_fn() -> u64 {
95/// # tokio::time::sleep(std::time::Duration::from_micros(10)).await;
96/// # 42
97/// # }
98/// # fn do_something_with_output(_: u64) {}
99/// # #[tokio::main]
100/// # async fn main() {
101/// let output = timed(some_async_fn(), |Timing { idle, busy }| {
102/// assert!(!idle.is_zero());
103/// assert!(!busy.is_zero());
104/// })
105/// .await;
106///
107/// do_something_with_output(output);
108/// # }
109pub fn timed<Fut, F>(fut: Fut, f: F) -> Timed<Fut, F>
110where
111 Fut: Future,
112 F: FnOnce(Timing),
113{
114 Timed::new(fut, f)
115}
116
117pin_project! {
118 /// Future for the [`timed`] function and [`timed`](TimedFutureExt::timed) method.
119 pub struct Timed<Fut, F> where Fut: Future, F: FnOnce(Timing) {
120 last_poll_end: Option<Instant>,
121 timing: Timing,
122 op: Option<F>,
123 #[pin]
124 inner: Fut,
125 }
126}
127
128impl<Fut, F> Timed<Fut, F>
129where
130 Fut: Future,
131 F: FnOnce(Timing),
132{
133 fn new(inner: Fut, op: F) -> Self {
134 let timing = Timing {
135 idle: Duration::ZERO,
136 busy: Duration::ZERO,
137 };
138
139 Self {
140 last_poll_end: None,
141 timing,
142 op: Some(op),
143 inner,
144 }
145 }
146}
147
148impl<Fut, F> Future for Timed<Fut, F>
149where
150 Fut: Future,
151 F: FnOnce(Timing),
152{
153 type Output = Fut::Output;
154
155 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
156 let start = Instant::now();
157 let mut this = self.project();
158 let result = this.inner.as_mut().poll(cx);
159 let end = Instant::now();
160
161 if let Some(last_poll_end) = this.last_poll_end.take() {
162 this.timing.idle += start - last_poll_end;
163 }
164
165 this.timing.busy += end - start;
166 *this.last_poll_end = Some(end);
167
168 match result {
169 Poll::Pending => Poll::Pending,
170 Poll::Ready(output) => {
171 if let Some(op) = this.op.take() {
172 op(*this.timing);
173 }
174 Poll::Ready(output)
175 }
176 }
177 }
178}
179
180/// An extension trait for `Future`s that adds the [`timed`] method.
181pub trait TimedFutureExt: Future {
182 /// Instrument a future to record its timing
183 ///
184 /// The busy and idle time for the future will be passed as an argument to the provided
185 /// closure. See the documentation for [`Timing`] for more details.
186 ///
187 /// # Examples
188 ///
189 /// ```
190 /// use future_timed::{TimedFutureExt, Timing};
191 /// # use std::time::Duration;
192 /// # #[tokio::main]
193 /// # async fn main() {
194 ///
195 /// let output = async {
196 /// // Block the executor
197 /// std::thread::sleep(Duration::from_micros(200));
198 /// tokio::time::sleep(Duration::from_micros(10)).await;
199 /// 42
200 /// }.timed(|Timing { idle, busy }| {
201 /// assert!(idle > Duration::from_micros(10));
202 /// assert!(busy > Duration::from_micros(200));
203 /// })
204 /// . await;
205 ///
206 /// assert_eq!(output, 42);
207 /// # }
208 fn timed<F>(self, f: F) -> Timed<Self, F>
209 where
210 Self: Sized,
211 F: FnOnce(Timing),
212 {
213 Timed::new(self, f)
214 }
215}
216
217impl<T: Future> TimedFutureExt for T {}
218
219/// Timing information for an instrumented future.
220#[derive(Clone, Copy, Debug, Hash, PartialEq)]
221pub struct Timing {
222 /// The idle time of a future is the sum of all the time between calls to [`Future::poll`]. The
223 /// time before the first poll is not included.
224 pub idle: Duration,
225 /// The busy time of a future is the sum of all the time consumed during calls to [`Future::poll`]
226 /// on that future.
227 pub busy: Duration,
228}