async_instrumenter/lib.rs
1pub use log;
2
3use std::{
4 future::Future,
5 pin::Pin,
6 task::{Context, Poll},
7 time::{Duration, Instant},
8};
9
10use pin_project::pin_project;
11
12// The result of a finished [`InstrumentFuture`]
13#[derive(Debug)]
14pub struct InstrumentFutureResult<R> {
15 pub result: R,
16 pub elapsed: Duration,
17}
18
19/// Wraps a future and determines exactly how long it took to execute
20///
21/// ```rust
22/// let my_fut: impl Future<Output = ()> = foobar();
23/// let res = InstrumentFuture::new(my_fut).await;
24///
25/// // get the result of `my_fut`
26/// res.result;
27///
28/// // print the elapsed time of `my_fut`
29/// println!("my_fut took {:?}", res.elapsed);
30/// ```
31#[derive(Debug)]
32#[pin_project]
33pub struct InstrumentFuture<F: Future> {
34 #[pin]
35 future: F,
36 timer: Option<Instant>,
37}
38
39impl<F: Future> InstrumentFuture<F> {
40 pub fn new(future: F) -> Self {
41 Self {
42 future,
43 timer: None,
44 }
45 }
46}
47
48impl<F: Future> Future for InstrumentFuture<F> {
49 type Output = InstrumentFutureResult<<F as Future>::Output>;
50
51 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
52 let this = self.project();
53
54 if this.timer.is_none() {
55 *this.timer = Some(Instant::now());
56 }
57
58 match this.future.poll(cx) {
59 Poll::Ready(r) => Poll::Ready(InstrumentFutureResult {
60 result: r,
61 // SAFETY: `timer` is always `Some(T)` since we set timer to Some up above when it's None
62 elapsed: unsafe { this.timer.unwrap_unchecked() }.elapsed(),
63 }),
64
65 Poll::Pending => Poll::Pending,
66 }
67 }
68}
69
70/// Debug log how long a future took to execute
71///
72/// When `debug_assertions` are enabled, does a log::debug!() with the file!(), line!(), and elapsed time.
73/// Without `debug_assertions`, simply is a no-op which executes future normally.
74///
75/// The argument to the function must be a unexecuted future. It will return a future you must `await` on.
76/// This allows you to use the created instrumenting future later if desired since it doesn't `await` immediately.
77///
78/// There is also an optional one with a custom log message. `elapsed` is provided as a keyword arg to the literal,
79/// so you must use it somewhere in there.
80///
81/// If you need custom behavior, you can make a custom instrumenting future using [`InstrumentFuture`]
82///
83/// Examples:
84///
85/// ```rust
86/// let my_fut: impl Future<Output = ()> = foobar();
87/// dbg_instrument!(my_fut).await;
88///
89/// let f = 0;
90/// dbg_instrument!("custom_log_message {f}: {elapsed:?}").await;
91/// ```
92#[macro_export]
93macro_rules! dbg_instrument {
94 ($fut:expr) => {
95 async {
96 if cfg!(debug_assertions) {
97 $crate::instrument!($fut).await
98 } else {
99 $fut.await
100 }
101 }
102 };
103
104 ($log:literal, $fut:expr) => {{
105 async {
106 if cfg!(debug_assertions) {
107 $crate::instrument!($log, $fut).await
108 } else {
109 $fut.await
110 }
111 }
112 }};
113}
114
115/// Debug log how long a future took to execute
116///
117/// As opposed to [`dbg_instrument!`], this always logs regardless whether `debug_assertions` are enabled or not
118///
119/// The argument to the function must be a unexecuted future. It will return a future you must `await` on.
120/// This allows you to use the created instrumenting future later if desired since it doesn't `await` immediately.
121///
122/// There is also an optional one with a custom log message. `elapsed` is provided as a keyword arg to the literal,
123/// so you must use it somewhere in there.
124///
125/// If you need custom behavior, you can make a custom instrumenting future using [`InstrumentFuture`]
126///
127/// Examples:
128///
129/// ```rust
130/// let my_fut: impl Future<Output = ()> = foobar();
131/// instrument!(my_fut).await;
132///
133/// let f = 0;
134/// instrument!("custom_log_message {f}: {elapsed:?}").await;
135/// ```
136#[macro_export]
137macro_rules! instrument {
138 ($fut:expr) => {
139 async { $crate::_instrument!($fut) }
140 };
141
142 ($log:literal, $fut:expr) => {{
143 async { $crate::_instrument!($log, $fut) }
144 }};
145}
146
147#[doc(hidden)]
148#[macro_export]
149macro_rules! _instrument {
150 ($fut:expr) => {{
151 let timed = $crate::InstrumentFuture::new($fut).await;
152
153 let _file = file!();
154 let _line = line!();
155 let _elapsed = timed.elapsed;
156
157 $crate::log::debug!("{_file}:{_line} completed in {_elapsed:?}");
158
159 timed.result
160 }};
161
162 ($log:literal, $fut:expr) => {{
163 let timed = $crate::InstrumentFuture::new($fut).await;
164
165 $crate::log::debug!($log, elapsed = timed.elapsed);
166
167 timed.result
168 }};
169}