use std::net::IpAddr;
use std::time::Duration;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct DurationPair {
rel: Duration,
total: Duration,
}
impl DurationPair {
fn new(duration_step: Duration, absolute_duration_so_far: Duration) -> Self {
Self {
rel: duration_step,
total: absolute_duration_so_far + duration_step,
}
}
#[must_use]
pub const fn relative(&self) -> Duration {
self.rel
}
#[must_use]
pub const fn total(&self) -> Duration {
self.total
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TtfbOutcome {
user_input: String,
ip_addr: IpAddr,
port: u16,
dns_duration_rel: Option<Duration>,
tcp_connect_duration_rel: Duration,
tls_handshake_duration_rel: Option<Duration>,
http_get_send_duration_rel: Duration,
http_ttfb_duration_rel: Duration,
}
impl TtfbOutcome {
#[allow(clippy::too_many_arguments)]
pub(crate) const fn new(
user_input: String,
ip_addr: IpAddr,
port: u16,
dns_duration_rel: Option<Duration>,
tcp_connect_duration_rel: Duration,
tls_handshake_duration_rel: Option<Duration>,
http_get_send_duration_rel: Duration,
http_ttfb_duration_rel: Duration,
) -> Self {
Self {
user_input,
ip_addr,
port,
dns_duration_rel,
tcp_connect_duration_rel,
tls_handshake_duration_rel,
http_get_send_duration_rel,
http_ttfb_duration_rel,
}
}
#[must_use]
pub fn user_input(&self) -> &str {
&self.user_input
}
#[must_use]
pub const fn ip_addr(&self) -> IpAddr {
self.ip_addr
}
#[must_use]
pub const fn port(&self) -> u16 {
self.port
}
#[must_use]
pub fn dns_lookup_duration(&self) -> Option<DurationPair> {
self.dns_duration_rel
.map(|d| DurationPair::new(d, Duration::default()))
}
#[must_use]
pub fn tcp_connect_duration(&self) -> DurationPair {
let abs_dur_so_far = self.dns_lookup_duration().unwrap_or_default().total();
DurationPair::new(self.tcp_connect_duration_rel, abs_dur_so_far)
}
#[must_use]
pub fn tls_handshake_duration(&self) -> Option<DurationPair> {
self.tls_handshake_duration_rel.map(|dur| {
let abs_dur_so_far = self.tcp_connect_duration().total();
DurationPair::new(dur, abs_dur_so_far)
})
}
#[must_use]
pub fn http_get_send_duration(&self) -> DurationPair {
let abs_dur_so_far = self.tls_handshake_duration().unwrap_or_default().total();
DurationPair::new(self.http_get_send_duration_rel, abs_dur_so_far)
}
#[must_use]
pub fn ttfb_duration(&self) -> DurationPair {
let abs_dur_so_far = self.http_get_send_duration().total();
DurationPair::new(self.http_ttfb_duration_rel, abs_dur_so_far)
}
}
#[cfg(test)]
mod tests {
use crate::outcome::TtfbOutcome;
use std::net::{IpAddr, Ipv4Addr};
use std::time::Duration;
#[test]
fn outcome_durations_are_sane() {
let outcome = TtfbOutcome::new(
"https://phip1611.de".to_string(),
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
443,
Some(Duration::from_millis(1)),
Duration::from_millis(2),
Some(Duration::from_millis(3)),
Duration::from_millis(4),
Duration::from_millis(5),
);
assert_eq!(
outcome.dns_lookup_duration().unwrap().total().as_millis(),
1,
"DNS is the very first operation"
);
assert_eq!(
outcome.tcp_connect_duration().total().as_millis(),
1 + 2,
"DNS + TCP connect"
);
println!("{:#?}", outcome);
assert_eq!(
outcome
.tls_handshake_duration()
.unwrap()
.total()
.as_millis(),
1 + 2 + 3,
"DNS + TCP connect + TLS handshake"
);
assert_eq!(
outcome.http_get_send_duration().total().as_millis(),
1 + 2 + 3 + 4,
"DNS + TCP connect + TLS handshake + HTTP GET send"
);
assert_eq!(
outcome.ttfb_duration().total().as_millis(),
1 + 2 + 3 + 4 + 5,
"Total TTFB: DNS + TCP connect + TLS handshake + HTTP GET send + relative TTFB"
);
}
}