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