isahc/metrics.rs
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 170
//! Request and response metrics tracking.
use crossbeam_utils::atomic::AtomicCell;
use std::{fmt, sync::Arc, time::Duration};
/// An object that holds status updates and progress statistics on a particular
/// request. A [`Metrics`] can be shared between threads, which allows an agent
/// thread to post updates to the object while consumers can read from the
/// object simultaneously.
///
/// Reading stats is not always guaranteed to be up-to-date.
#[derive(Clone)]
pub struct Metrics {
pub(crate) inner: Arc<Inner>,
}
#[derive(Default)]
pub(crate) struct Inner {
pub(crate) upload_progress: AtomicCell<f64>,
pub(crate) upload_total: AtomicCell<f64>,
pub(crate) download_progress: AtomicCell<f64>,
pub(crate) download_total: AtomicCell<f64>,
pub(crate) upload_speed: AtomicCell<f64>,
pub(crate) download_speed: AtomicCell<f64>,
// An overview of the six time values (taken from the curl documentation):
//
// curl_easy_perform()
// |
// |--NAMELOOKUP
// |--|--CONNECT
// |--|--|--APPCONNECT
// |--|--|--|--PRETRANSFER
// |--|--|--|--|--STARTTRANSFER
// |--|--|--|--|--|--TOTAL
// |--|--|--|--|--|--REDIRECT
//
// The numbers we expose in the API are a little more "high-level" than the
// ones written here.
pub(crate) namelookup_time: AtomicCell<f64>,
pub(crate) connect_time: AtomicCell<f64>,
pub(crate) appconnect_time: AtomicCell<f64>,
pub(crate) pretransfer_time: AtomicCell<f64>,
pub(crate) starttransfer_time: AtomicCell<f64>,
pub(crate) total_time: AtomicCell<f64>,
pub(crate) redirect_time: AtomicCell<f64>,
}
impl Metrics {
pub(crate) fn new() -> Self {
Self {
inner: Arc::default(),
}
}
/// Number of bytes uploaded / estimated total.
pub fn upload_progress(&self) -> (u64, u64) {
(
self.inner.upload_progress.load() as u64,
self.inner.upload_total.load() as u64,
)
}
/// Average upload speed so far in bytes/second.
pub fn upload_speed(&self) -> f64 {
self.inner.upload_speed.load()
}
/// Number of bytes downloaded / estimated total.
pub fn download_progress(&self) -> (u64, u64) {
(
self.inner.download_progress.load() as u64,
self.inner.download_total.load() as u64,
)
}
/// Average download speed so far in bytes/second.
pub fn download_speed(&self) -> f64 {
self.inner.download_speed.load()
}
/// Get the total time from the start of the request until DNS name
/// resolving was completed.
///
/// When a redirect is followed, the time from each request is added
/// together.
pub fn name_lookup_time(&self) -> Duration {
Duration::from_secs_f64(self.inner.namelookup_time.load())
}
/// Get the amount of time taken to establish a connection to the server
/// (not including TLS connection time).
///
/// When a redirect is followed, the time from each request is added
/// together.
pub fn connect_time(&self) -> Duration {
Duration::from_secs_f64(
(self.inner.connect_time.load() - self.inner.namelookup_time.load()).max(0f64),
)
}
/// Get the amount of time spent on TLS handshakes.
///
/// When a redirect is followed, the time from each request is added
/// together.
pub fn secure_connect_time(&self) -> Duration {
let app_connect_time = self.inner.appconnect_time.load();
if app_connect_time > 0f64 {
Duration::from_secs_f64(app_connect_time - self.inner.connect_time.load())
} else {
Duration::new(0, 0)
}
}
/// Get the time it took from the start of the request until the first
/// byte is either sent or received.
///
/// When a redirect is followed, the time from each request is added
/// together.
pub fn transfer_start_time(&self) -> Duration {
Duration::from_secs_f64(self.inner.starttransfer_time.load())
}
/// Get the amount of time spent performing the actual request transfer. The
/// "transfer" includes both sending the request and receiving the response.
///
/// When a redirect is followed, the time from each request is added
/// together.
pub fn transfer_time(&self) -> Duration {
Duration::from_secs_f64(
(self.inner.total_time.load() - self.inner.starttransfer_time.load()).max(0f64),
)
}
/// Get the total time for the entire request. This will continuously
/// increase until the entire response body is consumed and completed.
///
/// When a redirect is followed, the time from each request is added
/// together.
pub fn total_time(&self) -> Duration {
Duration::from_secs_f64(self.inner.total_time.load())
}
/// If automatic redirect following is enabled, gets the total time taken
/// for all redirection steps including name lookup, connect, pretransfer
/// and transfer before final transaction was started.
pub fn redirect_time(&self) -> Duration {
Duration::from_secs_f64(self.inner.redirect_time.load())
}
}
impl fmt::Debug for Metrics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Metrics")
.field("upload_progress", &self.upload_progress())
.field("upload_speed", &self.upload_speed())
.field("download_progress", &self.download_progress())
.field("download_speed", &self.download_speed())
.field("name_lookup_time", &self.name_lookup_time())
.field("connect_time", &self.connect_time())
.field("secure_connect_time", &self.secure_connect_time())
.field("transfer_start_time", &self.transfer_start_time())
.field("transfer_time", &self.transfer_time())
.field("total_time", &self.total_time())
.field("redirect_time", &self.redirect_time())
.finish()
}
}