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}