//! 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, } #[derive(Default)] pub(crate) struct Inner { pub(crate) upload_progress: AtomicCell, pub(crate) upload_total: AtomicCell, pub(crate) download_progress: AtomicCell, pub(crate) download_total: AtomicCell, pub(crate) upload_speed: AtomicCell, pub(crate) download_speed: AtomicCell, // 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, pub(crate) connect_time: AtomicCell, pub(crate) appconnect_time: AtomicCell, pub(crate) pretransfer_time: AtomicCell, pub(crate) starttransfer_time: AtomicCell, pub(crate) total_time: AtomicCell, pub(crate) redirect_time: AtomicCell, } 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() } }