mirror of
https://github.com/lexmount/moli.git
synced 2026-09-29 00:01:38 +00:00
140 lines
4.4 KiB
Rust
140 lines
4.4 KiB
Rust
//! Fixed HTTP/1.1 keepalive load alongside the WebSocket owner probe.
|
|
use anyhow::{Context, Result, ensure};
|
|
use curl::easy::{Easy2, Handler, WriteError};
|
|
use moli_curl::{
|
|
CurlDnsResolution, CurlHttpSender, CurlMultiCompletion, CurlMultiJob, CurlMultiRuntime,
|
|
CurlMultiRuntimeConfig,
|
|
};
|
|
use std::{
|
|
io::{Read, Write},
|
|
net::TcpListener,
|
|
thread,
|
|
time::{Duration, Instant},
|
|
};
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct Body(Vec<u8>);
|
|
impl Handler for Body {
|
|
fn write(&mut self, data: &[u8]) -> Result<usize, WriteError> {
|
|
self.0.extend_from_slice(data);
|
|
Ok(data.len())
|
|
}
|
|
}
|
|
|
|
pub struct Workload {
|
|
pub runtime: CurlMultiRuntime<Body, ()>,
|
|
completed: crossbeam_channel::Receiver<CurlMultiCompletion<Body, ()>>,
|
|
url: String,
|
|
peer: thread::JoinHandle<Result<()>>,
|
|
}
|
|
|
|
impl Workload {
|
|
pub fn new() -> Result<Self> {
|
|
let listener = TcpListener::bind("127.0.0.1:0")?;
|
|
let url = format!("http://{}/probe", listener.local_addr()?);
|
|
let peer = thread::spawn(move || {
|
|
let (mut stream, _) = listener.accept()?;
|
|
stream.set_read_timeout(Some(Duration::from_secs(30)))?;
|
|
let mut header = Vec::new();
|
|
loop {
|
|
let mut byte = [0];
|
|
if stream.read(&mut byte)? == 0 {
|
|
break;
|
|
}
|
|
header.push(byte[0]);
|
|
if header.ends_with(b"\r\n\r\n") {
|
|
stream.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok")?;
|
|
header.clear();
|
|
}
|
|
}
|
|
Ok(())
|
|
});
|
|
let (runtime, completed) = CurlMultiRuntime::new(CurlMultiRuntimeConfig {
|
|
thread_name: "moli-curl-http".to_owned(),
|
|
..CurlMultiRuntimeConfig::default()
|
|
})?;
|
|
let workload = Self {
|
|
runtime,
|
|
completed,
|
|
url,
|
|
peer,
|
|
};
|
|
for _ in 0..16 {
|
|
request(
|
|
&workload.runtime.http_sender(),
|
|
&workload.completed,
|
|
&workload.url,
|
|
)?;
|
|
}
|
|
Ok(workload)
|
|
}
|
|
|
|
pub fn start(&self, end: Instant) -> thread::JoinHandle<Result<Vec<f64>>> {
|
|
let sender = self.runtime.http_sender();
|
|
let url = self.url.clone();
|
|
let completed = self.completed.clone();
|
|
thread::spawn(move || {
|
|
let mut latency = Vec::new();
|
|
while Instant::now() < end {
|
|
let started = Instant::now();
|
|
request(&sender, &completed, &url)?;
|
|
latency.push(started.elapsed().as_secs_f64() * 1000.0);
|
|
// A fixed 100 requests/s offered load; this is a benchmark,
|
|
// not synchronization for a correctness test.
|
|
thread::sleep(Duration::from_millis(10).saturating_sub(started.elapsed()));
|
|
}
|
|
Ok(latency)
|
|
})
|
|
}
|
|
|
|
pub fn finish(self) -> Result<()> {
|
|
drop(self.runtime);
|
|
self.peer
|
|
.join()
|
|
.map_err(|_| anyhow::anyhow!("HTTP peer panicked"))?
|
|
}
|
|
}
|
|
|
|
fn request(
|
|
sender: &CurlHttpSender<Body, ()>,
|
|
completed: &crossbeam_channel::Receiver<CurlMultiCompletion<Body, ()>>,
|
|
url: &str,
|
|
) -> Result<()> {
|
|
let mut easy = Easy2::new(Body::default());
|
|
easy.url(url)?;
|
|
easy.proxy("")?;
|
|
sender
|
|
.submit(CurlMultiJob {
|
|
easy,
|
|
context: (),
|
|
origin: None,
|
|
deadline: Some(Instant::now() + Duration::from_secs(10)),
|
|
dns_resolution: CurlDnsResolution::no_shared_resolution(),
|
|
priority: 1,
|
|
label: "owner probe HTTP".into(),
|
|
})
|
|
.map_err(|error| error.error)?;
|
|
let completion = completed.recv_timeout(Duration::from_secs(10))?;
|
|
completion.result?;
|
|
ensure!(
|
|
completion.easy.context("HTTP easy missing")?.get_ref().0 == b"ok",
|
|
"HTTP body mismatch"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn report(task: thread::JoinHandle<Result<Vec<f64>>>) -> Result<()> {
|
|
let mut samples = task
|
|
.join()
|
|
.map_err(|_| anyhow::anyhow!("HTTP producer panicked"))??;
|
|
samples.sort_by(f64::total_cmp);
|
|
ensure!(!samples.is_empty(), "no HTTP samples");
|
|
println!(
|
|
"http_version=1.1 http_requests={} http_p50_ms={:.3} http_p95_ms={:.3}",
|
|
samples.len(),
|
|
samples[(samples.len() - 1) / 2],
|
|
samples[(samples.len() - 1) * 95 / 100]
|
|
);
|
|
Ok(())
|
|
}
|