Skip to main content

alux_http_conformance/
measure.rs

1//! Measures what opening, closing and ending one bound address cost.
2//!
3//! The scenario states what a server must do; this states how long it takes to do it. What a round
4//! needs but does not measure, such as opening a server or putting a connection on it, happens off
5//! the clock.
6
7use crate::lifecycle::{LifecycleError, answered, free_address, hold};
8use alux_ext::ext;
9use alux_http::{HttpServerAlg, HttpServerSetup};
10use core::time::Duration;
11use std::time::Instant;
12use tokio::task::LocalSet;
13
14/// What is on the connection when a close begins, and how far the round waits for it.
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub struct Closing {
17    asking_for: &'static str,
18    ending: bool,
19}
20
21impl Closing {
22    /// States a round holding `asking_for`, which ends the server after closing it when `ending`.
23    pub const fn new(asking_for: &'static str, ending: bool) -> Self {
24        Self { asking_for, ending }
25    }
26
27    /// Returns the path the held request asks for.
28    pub const fn asking_for(self) -> &'static str {
29        self.asking_for
30    }
31
32    /// Returns whether the round ends the server after closing it.
33    pub const fn ending(self) -> bool {
34        self.ending
35    }
36}
37
38/// What is on the connection while one address is handed from a server to the next.
39#[derive(Clone, Copy, Debug, Eq, PartialEq)]
40pub enum Handover {
41    /// Nothing.
42    NoRequest,
43    /// A request asked and answered, timed as part of the round.
44    RequestOk,
45    /// A request the server is still producing an answer for, held off the clock.
46    RequestHeld(&'static str),
47}
48
49/// Measures the lifecycle of a concrete HTTP server.
50#[ext(name = MeasureLifecycleExt)]
51pub impl<This> This
52where
53    This: HttpServerAlg,
54    This::Error: Into<LifecycleError>,
55{
56    /// Closes `rounds` servers, each holding one request, and answers how long the closing took.
57    ///
58    /// Opening a server and holding a request on it is setup, and so is dropping what a round left
59    /// behind. Only `close`, and `end` where [`Closing::ending`] states one, is timed.
60    ///
61    /// A round drops its server rather than ending it, since `close` has already taken the address
62    /// back and whatever is still draining has nothing left to answer to. Ending it instead would
63    /// cost every round a drain it is not measuring.
64    async fn time_closing<Compile>(mut self, mut compile: Compile, closing: Closing, rounds: u64) -> Duration
65    where
66        Compile: FnMut() -> Self::Program,
67    {
68        LocalSet::new()
69            .run_until(async move {
70                let bind = free_address().expect("an address nothing holds");
71                let mut timed = Duration::ZERO;
72                for _ in 0..rounds {
73                    let Ok(mut open) = self.open(HttpServerSetup::new(bind, compile())).await else {
74                        panic!("the server opens");
75                    };
76                    let held = hold(bind, closing.asking_for()).await.expect("a request the server is serving");
77
78                    let round = Instant::now();
79                    assert!(self.close(&mut open).await.is_ok(), "the open server closes");
80                    if closing.ending() {
81                        assert!(self.end(&mut open).await.is_ok(), "the closed server ends");
82                    }
83                    timed += round.elapsed();
84
85                    drop(held);
86                    drop(open);
87                }
88
89                timed
90            })
91            .await
92    }
93
94    /// Hands one address from a server to the next `rounds` times, and answers how long it took.
95    ///
96    /// A handover is `close` then `open` on the same address: what a caller replacing a server
97    /// needs, and nothing more. [`Handover::RequestHeld`] holds its connection off the clock.
98    async fn time_handover<Compile>(mut self, mut compile: Compile, load: Handover, rounds: u64) -> Duration
99    where
100        Compile: FnMut() -> Self::Program,
101    {
102        LocalSet::new()
103            .run_until(async move {
104                let bind = free_address().expect("an address nothing holds");
105                let Ok(mut open) = self.open(HttpServerSetup::new(bind, compile())).await else {
106                    panic!("the first server opens");
107                };
108
109                let mut holding = Vec::new();
110                let mut timed = Duration::ZERO;
111                for _ in 0..rounds {
112                    if let Handover::RequestHeld(asking_for) = load {
113                        holding.push(hold(bind, asking_for).await.expect("a request the server is serving"));
114                    }
115
116                    let round = Instant::now();
117                    if load == Handover::RequestOk {
118                        answered(bind).await.expect("the open server answers");
119                    }
120                    assert!(self.close(&mut open).await.is_ok(), "the open server closes");
121                    let Ok(next) = self.open(HttpServerSetup::new(bind, compile())).await else {
122                        panic!("the next server opens");
123                    };
124                    open = next;
125                    timed += round.elapsed();
126                }
127
128                assert!(self.end(&mut open).await.is_ok(), "the last server ends");
129
130                timed
131            })
132            .await
133    }
134}