Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,37 @@
|
||||
use std::path::Path;
|
||||
|
||||
pub const DEPENDENCIES: &str =
|
||||
"futures-util = { version = \"=0.3.34\", default-features = false, features = [\"std\"] }\n";
|
||||
|
||||
pub fn run(core: &Path) -> bool {
|
||||
if !std::env::args().any(|argument| argument == "--transport") {
|
||||
return false;
|
||||
}
|
||||
assert!(
|
||||
!std::env::args().any(|argument| argument == "--memory"),
|
||||
"transport uses asynchronous samples; --memory is unsupported"
|
||||
);
|
||||
let reference = [
|
||||
include_str!("reference.rs"),
|
||||
include_str!("session.rs"),
|
||||
include_str!("samples.rs"),
|
||||
]
|
||||
.concat()
|
||||
.replace(
|
||||
"TRANSPORT_TLS_DIRECTORY",
|
||||
&format!("{:?}", core.join("test/dever-tests/fixtures/tls")),
|
||||
);
|
||||
crate::compare(
|
||||
core,
|
||||
"transport-performance",
|
||||
"transport",
|
||||
&reference,
|
||||
&[
|
||||
("GENERATED_TRANSPORT_PLAIN", "main.servePlain"),
|
||||
("GENERATED_TRANSPORT_TLS", "main.serveSecure"),
|
||||
("GENERATED_TRANSPORT_EXCHANGE", "main.exchange"),
|
||||
],
|
||||
&[],
|
||||
);
|
||||
true
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
use dever_runtime::{
|
||||
http, net,
|
||||
task::{Runtime, Scope, Timed},
|
||||
tls,
|
||||
};
|
||||
use std::{collections::BTreeSet, hint::black_box, sync::Arc, time::Instant};
|
||||
|
||||
async fn rust_transport_route(
|
||||
request: http::Request,
|
||||
_: Arc<Scope<'static>>,
|
||||
) -> Result<http::Response, String> {
|
||||
if request.method != "POST" || request.target != "/transport" {
|
||||
return Ok(http::Response {
|
||||
status: 404,
|
||||
headers: vec![],
|
||||
body: dever_runtime::Bytes::new(),
|
||||
});
|
||||
}
|
||||
let mut body = Vec::with_capacity(6 + request.body.len());
|
||||
body.extend_from_slice(b"reply:");
|
||||
body.extend_from_slice(&request.body);
|
||||
Ok(http::Response {
|
||||
status: 201,
|
||||
headers: vec![],
|
||||
body: body.into(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn rust_transport_serve(
|
||||
listener: &net::Listener,
|
||||
limits: &http::Limits,
|
||||
scope: &Scope<'_>,
|
||||
secure: Option<&tls::ServerTls>,
|
||||
) -> Result<(), String> {
|
||||
let result = if let Some(secure) = secure {
|
||||
http::serve_tls::<_, _, false>(listener, limits, scope, secure, rust_transport_route).await
|
||||
} else {
|
||||
http::serve::<_, _, false>(listener, limits, scope, rust_transport_route).await
|
||||
};
|
||||
result.map_err(|error| error.message().to_owned())
|
||||
}
|
||||
|
||||
async fn rust_transport_exchange(
|
||||
client: &http::HttpClient,
|
||||
payload: &dever_runtime::Bytes,
|
||||
) -> Result<(i64, dever_runtime::Bytes), String> {
|
||||
let response = client
|
||||
.request(http::Request {
|
||||
method: "POST".into(),
|
||||
target: "/transport".into(),
|
||||
headers: vec![],
|
||||
body: payload.clone(),
|
||||
})
|
||||
.await
|
||||
.map_err(|error| error.into_owned())?;
|
||||
Ok((response.status, response.body))
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
fn transport_samples(
|
||||
runtime: &Runtime,
|
||||
certificates: &Arc<TransportCertificates>,
|
||||
case: TransportCase,
|
||||
smoke: bool,
|
||||
) {
|
||||
let name = case.name();
|
||||
if let Ok(selected) = std::env::var("DEVER_BENCH_CASES")
|
||||
&& !selected.split(',').any(|item| item == name)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let batches = if case.cold || smoke { 2 } else { 16 };
|
||||
if !smoke {
|
||||
for generated in [true, false] {
|
||||
black_box(transport_measure(
|
||||
runtime,
|
||||
certificates,
|
||||
case,
|
||||
generated,
|
||||
2,
|
||||
false,
|
||||
));
|
||||
}
|
||||
}
|
||||
eprintln!(
|
||||
"{name}: concurrency={}, connections={}, requests_per_sample={}",
|
||||
case.concurrency,
|
||||
case.connections(),
|
||||
batches * case.concurrency,
|
||||
);
|
||||
for sample in 0..if smoke { 1 } else { 15 } {
|
||||
let mut order = [("dever", true), ("rust_runtime", false), ("control", true)];
|
||||
order.rotate_left(sample % 3);
|
||||
let mut expected = None;
|
||||
for (implementation, generated) in order {
|
||||
let (elapsed, checksum) =
|
||||
transport_measure(runtime, certificates, case, generated, batches, !smoke);
|
||||
if let Some(expected) = expected {
|
||||
assert_eq!(checksum, expected);
|
||||
}
|
||||
expected = Some(checksum);
|
||||
if !smoke {
|
||||
println!("{name},{sample},{implementation},{elapsed},{checksum}");
|
||||
}
|
||||
}
|
||||
}
|
||||
if smoke {
|
||||
println!("correctness smoke passed: {name}; no performance result collected");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let smoke = std::env::args().any(|argument| argument == "--smoke");
|
||||
let fixture = std::path::Path::new(TRANSPORT_TLS_DIRECTORY);
|
||||
let certificates = Arc::new(TransportCertificates {
|
||||
root: std::fs::read(fixture.join("root.pem")).unwrap(),
|
||||
server: tls::server(
|
||||
&std::fs::read(fixture.join("server.pem")).unwrap(),
|
||||
&std::fs::read(fixture.join("server-key.pem")).unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
});
|
||||
let threads = std::thread::available_parallelism().map_or(1, |count| count.get().min(4));
|
||||
let runtime = Runtime::new(dever_runtime::task::Limits {
|
||||
threads,
|
||||
workers: 1,
|
||||
tasks: 256,
|
||||
})
|
||||
.unwrap();
|
||||
eprintln!(
|
||||
"transport: shared Dever runtime/Hyper/TLS; generation-layer comparison only; \
|
||||
Linux dev Pod loopback; {threads} async workers (maximum 4), one blocking worker; \
|
||||
HTTP/1 uses c connections; persistent HTTP/2 uses one connection and c streams; \
|
||||
cold uses c fresh clients and fresh TLS session caches; \
|
||||
cold timing includes connect/DNS (TLS localhost), full handshake and first response; \
|
||||
server readiness is primed outside timing for both modes; \
|
||||
persistent timing excludes the priming batch; payloads are fixed buffered bytes; \
|
||||
elapsed_ns sums request batches, including request/handler/body collection; \
|
||||
certificate/pool/server setup, exact-byte/socket checks and teardown are excluded"
|
||||
);
|
||||
if !smoke {
|
||||
println!("case,sample,implementation,elapsed_ns,checksum");
|
||||
}
|
||||
for http2 in [false, true] {
|
||||
for secure in [false, true] {
|
||||
for concurrency in [1, 8, 32] {
|
||||
for (cold, bytes) in [(true, 64), (false, 64), (false, 65536)] {
|
||||
transport_samples(
|
||||
&runtime,
|
||||
&certificates,
|
||||
TransportCase {
|
||||
http2,
|
||||
secure,
|
||||
cold,
|
||||
concurrency,
|
||||
bytes,
|
||||
},
|
||||
smoke,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
#[derive(Clone, Copy)]
|
||||
struct TransportCase {
|
||||
http2: bool,
|
||||
secure: bool,
|
||||
cold: bool,
|
||||
concurrency: usize,
|
||||
bytes: usize,
|
||||
}
|
||||
|
||||
impl TransportCase {
|
||||
fn connections(self) -> usize {
|
||||
if self.cold || !self.http2 {
|
||||
self.concurrency
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
fn limits(self, connections: usize, streams: usize) -> http::Limits {
|
||||
http::Limits {
|
||||
connections: connections as i64,
|
||||
http2: self.http2.then_some(http::Http2Limits {
|
||||
streams: streams as i64,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn name(self) -> String {
|
||||
format!(
|
||||
"http{}_{}_{}_c{}_b{}",
|
||||
if self.http2 { 2 } else { 1 },
|
||||
if self.secure { "tls" } else { "plain" },
|
||||
if self.cold {
|
||||
"cold_first_response"
|
||||
} else {
|
||||
"persistent"
|
||||
},
|
||||
self.concurrency,
|
||||
self.bytes,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct TransportCertificates {
|
||||
root: Vec<u8>,
|
||||
server: tls::ServerTls,
|
||||
}
|
||||
|
||||
fn transport_clients(
|
||||
case: TransportCase,
|
||||
port: i64,
|
||||
certificates: &TransportCertificates,
|
||||
scope: &Scope<'_>,
|
||||
) -> Vec<http::HttpClient> {
|
||||
let origin = if case.secure {
|
||||
format!("https://localhost:{port}")
|
||||
} else {
|
||||
format!("http://127.0.0.1:{port}")
|
||||
};
|
||||
let count = if case.cold { case.concurrency } else { 1 };
|
||||
let limits = if case.cold {
|
||||
case.limits(1, 1)
|
||||
} else {
|
||||
case.limits(case.connections(), case.concurrency)
|
||||
};
|
||||
(0..count)
|
||||
.map(|_| {
|
||||
let fresh_tls = tls::client(&certificates.root).unwrap();
|
||||
http::client(
|
||||
&origin,
|
||||
&fresh_tls,
|
||||
&limits,
|
||||
&http::PoolLimits::default(),
|
||||
scope,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn transport_batch(
|
||||
generated: bool,
|
||||
clients: &[http::HttpClient],
|
||||
payloads: &[dever_runtime::Bytes],
|
||||
) -> Vec<(i64, dever_runtime::Bytes)> {
|
||||
futures_util::future::join_all(payloads.iter().enumerate().map(|(index, payload)| {
|
||||
let client = &clients[index % clients.len()];
|
||||
async move {
|
||||
if generated {
|
||||
GENERATED_TRANSPORT_EXCHANGE(black_box(client), black_box(payload))
|
||||
.await
|
||||
.map_err(|error| error.to_string())
|
||||
} else {
|
||||
rust_transport_exchange(black_box(client), black_box(payload)).await
|
||||
}
|
||||
}
|
||||
}))
|
||||
.await
|
||||
.into_iter()
|
||||
.map(Result::unwrap)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn transport_check(
|
||||
replies: &[(i64, dever_runtime::Bytes)],
|
||||
payloads: &[dever_runtime::Bytes],
|
||||
) -> u64 {
|
||||
assert_eq!(replies.len(), payloads.len());
|
||||
let mut checksum = 0u64;
|
||||
for ((status, body), payload) in replies.iter().zip(payloads) {
|
||||
assert_eq!(*status, 201);
|
||||
assert_eq!(body.len(), 6 + payload.len());
|
||||
assert_eq!(&body[..6], b"reply:");
|
||||
assert_eq!(&body[6..], payload.as_ref());
|
||||
checksum += *status as u64 + body.iter().map(|byte| u64::from(*byte)).sum::<u64>();
|
||||
}
|
||||
black_box(checksum)
|
||||
}
|
||||
|
||||
fn transport_connections(port: i64) -> BTreeSet<(String, String)> {
|
||||
let endpoint = format!("0100007F:{port:04X}");
|
||||
std::fs::read_to_string("/proc/self/net/tcp")
|
||||
.expect("transport checks require the Linux dev Pod TCP table")
|
||||
.lines()
|
||||
.skip(1)
|
||||
.filter_map(|line| {
|
||||
let fields = line.split_whitespace().collect::<Vec<_>>();
|
||||
assert!(fields.len() >= 10, "malformed Linux TCP table");
|
||||
(fields[1] == endpoint && fields[3] == "01")
|
||||
.then(|| (fields[2].to_owned(), fields[9].to_owned()))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn transport_close(clients: Vec<http::HttpClient>) {
|
||||
for client in clients {
|
||||
client.close().await.unwrap();
|
||||
assert!(rust_transport_exchange(&client, &dever_runtime::Bytes::from_static(b"closed")).await.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
async fn transport_session(
|
||||
scope: &Scope<'_>,
|
||||
certificates: &TransportCertificates,
|
||||
case: TransportCase,
|
||||
generated: bool,
|
||||
batches: usize,
|
||||
timed: bool,
|
||||
) -> (u128, u64) {
|
||||
let listener = net::listen("127.0.0.1", 0, scope).await.unwrap();
|
||||
let port = listener.port().unwrap();
|
||||
let incoming = listener.clone();
|
||||
let server_tls = certificates.server.clone();
|
||||
let server = scope
|
||||
.spawn(move |child| async move {
|
||||
let connections = case.connections() as i64;
|
||||
let streams = case.concurrency as i64;
|
||||
if generated && case.secure {
|
||||
GENERATED_TRANSPORT_TLS(
|
||||
&incoming,
|
||||
case.http2,
|
||||
connections,
|
||||
streams,
|
||||
&server_tls,
|
||||
&child,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| error.to_string())
|
||||
} else if generated {
|
||||
GENERATED_TRANSPORT_PLAIN(&incoming, case.http2, connections, streams, &child)
|
||||
.await
|
||||
.map_err(|error| error.to_string())
|
||||
} else {
|
||||
rust_transport_serve(
|
||||
&incoming,
|
||||
&case.limits(case.connections(), case.concurrency),
|
||||
&child,
|
||||
case.secure.then_some(&server_tls),
|
||||
)
|
||||
.await
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
let payloads = (0..case.concurrency)
|
||||
.map(|slot| {
|
||||
(0..case.bytes)
|
||||
.map(|index| ((index + slot) % 251) as u8)
|
||||
.collect::<Vec<_>>()
|
||||
.into()
|
||||
})
|
||||
.collect::<Vec<dever_runtime::Bytes>>();
|
||||
let mut clients = transport_clients(case, port, certificates, scope);
|
||||
let replies = transport_batch(generated, &clients, &payloads).await;
|
||||
transport_check(&replies, &payloads);
|
||||
let connections = transport_connections(port);
|
||||
assert_eq!(connections.len(), case.connections());
|
||||
let persistent = if case.cold {
|
||||
transport_close(clients).await;
|
||||
clients = transport_clients(case, port, certificates, scope);
|
||||
BTreeSet::new()
|
||||
} else {
|
||||
connections
|
||||
};
|
||||
let mut elapsed = 0;
|
||||
let mut checksum = 0;
|
||||
for batch in 0..batches {
|
||||
if case.cold && batch != 0 {
|
||||
clients = transport_clients(case, port, certificates, scope);
|
||||
}
|
||||
let start = timed.then(Instant::now);
|
||||
let replies = transport_batch(generated, &clients, &payloads).await;
|
||||
elapsed += start.map_or(0, |start| start.elapsed().as_nanos());
|
||||
checksum += transport_check(&replies, &payloads);
|
||||
let connections = transport_connections(port);
|
||||
assert_eq!(connections.len(), case.connections());
|
||||
if case.cold {
|
||||
transport_close(std::mem::take(&mut clients)).await;
|
||||
} else {
|
||||
assert_eq!(connections, persistent, "persistent transport reconnected");
|
||||
}
|
||||
}
|
||||
transport_close(clients).await;
|
||||
listener.close().unwrap();
|
||||
match server.timeout(5000).await.unwrap() {
|
||||
Timed::Ready(result) => result.unwrap(),
|
||||
Timed::Expired(_) => panic!("transport server did not drain"),
|
||||
}
|
||||
assert!(transport_connections(port).is_empty());
|
||||
(elapsed, checksum)
|
||||
}
|
||||
|
||||
fn transport_measure(
|
||||
runtime: &Runtime,
|
||||
certificates: &Arc<TransportCertificates>,
|
||||
case: TransportCase,
|
||||
generated: bool,
|
||||
batches: usize,
|
||||
timed: bool,
|
||||
) -> (u128, u64) {
|
||||
let certificates = certificates.clone();
|
||||
runtime
|
||||
.run(async move |scope| {
|
||||
let session = scope
|
||||
.spawn(move |child| async move {
|
||||
transport_session(&child, &certificates, case, generated, batches, timed).await
|
||||
})
|
||||
.unwrap();
|
||||
match session.timeout(30000).await.unwrap() {
|
||||
Timed::Ready(result) => result,
|
||||
Timed::Expired(_) => panic!("transport sample exceeded 30 seconds after cleanup"),
|
||||
}
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
Reference in new issue
Block a user