mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-09-03 09:35:41 +00:00
refactor(core): separate portable core from native runtime (#2451)
Create easytier-core as the portable owner of configuration, connectivity, tunnels, peer and routing state, gateways, management, the data plane, and instance lifecycle. Keep operating-system integration, native protocol engines, process startup, and presentation in easytier behind explicit Host capability adapters. Create easytier-proto to own schemas, generated RPC types, descriptors, and feature-scoped protocol slices. Remove runtime protobuf reflection from core while preserving unknown route-peer fields across forwarding. Normalize instance construction through CoreInstance, CoreHostAdapters, CoreProcessRuntime, and InstanceManager. Make the runtime config store the only authoritative mutable configuration after startup. Move the portable TCP/UDP data plane into core and extract a generic OperationBroker for completion, cancellation, disposal, and capacity accounting. Expose the session-based FFI v2 completion API and keep the WASI guest ABI, wire schemas, and adapters with core. Migrate CLI, GUI, web, FFI, Android JNI, OHOS, uptime, and mobile consumers to the shared manager and core state. Add explicit user/web config ownership and revision-aware web reconciliation. Preserve configuration, wire, and management behavior while fixing regressions discovered by the full platform and integration matrix: - inherit advertised relay capabilities in foreign networks; - refresh OSPF peer state immediately after runtime config changes; - restore CLI GlobalCtx event output without forcing GUI logging; - retain legacy encryption names and standalone RPC tunnel metadata; - restore ICMP host composition and fragmented UDP handling; - use portable 64-bit atomics on 32-bit MIPS targets; and - retain discarded operations until late cancellation completes. Validate the refactor across 45 GitHub checks, including Linux, macOS, Windows, FreeBSD, web, GUI, Android, OHOS, feature profiles, and three-node and subnet-proxy integration tests. BREAKING CHANGE: internal Rust module paths are not preserved. Legacy native data-plane APIs are replaced by the session-based FFI v2 API. The dedicated Android data-plane wrapper is removed.
This commit is contained in:
+3
-126
@@ -2,10 +2,9 @@
|
||||
|
||||
Criterion benchmarks for EasyTier hot paths.
|
||||
|
||||
| Bench | What it measures |
|
||||
| --------------------------- | -------------------------------------------------------------------------------- |
|
||||
| `tx_throughput` | End-to-end TX injection path through `peer_manager::send_msg_by_ip` |
|
||||
| `packet_bytes_extraction` | `ZCPacket::payload_bytes` / `tunnel_payload_bytes` extraction (advance hot path) |
|
||||
| Bench | What it measures |
|
||||
| ------------------------- | ------------------------------------------------------------------------------- |
|
||||
| `packet_bytes_extraction` | `ZCPacket::payload_bytes` / `tunnel_payload_bytes` extraction (advance hot path) |
|
||||
|
||||
## Packet Bytes Extraction
|
||||
|
||||
@@ -38,125 +37,3 @@ cargo bench --bench packet_bytes_extraction -- --quiet
|
||||
| `PACKET_BYTES_MEASUREMENT_SECS` | `10` | Criterion `measurement_time` |
|
||||
| `PACKET_BYTES_WARMUP_SECS` | `3` | Criterion `warm_up_time` |
|
||||
| `PACKET_BYTES_SAMPLE_SIZE` | `10` | Criterion `sample_size` (min 10) |
|
||||
|
||||
---
|
||||
|
||||
## TX Throughput Benchmark
|
||||
|
||||
Criterion benchmark for EasyTier's TX injection path (`peer_manager::send_msg_by_ip`).
|
||||
|
||||
## What it measures
|
||||
|
||||
The benchmark sets up two EasyTier instances (`hot-a` / `hot-b`) and drives
|
||||
packets from `hot-a` to `hot-b` via `peer_manager.send_msg_by_ip`. This is the
|
||||
same entry point `easytier-core` uses for daily forwarded traffic, so the
|
||||
numbers reflect the real TX hot path: NIC pipeline → route lookup →
|
||||
compress/encrypt → peer connection → tunnel send.
|
||||
|
||||
Two variants are reported per tunnel kind:
|
||||
|
||||
| Bench | What it measures |
|
||||
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `tx_throughput/<tunnel>` | Serial baseline. One send in flight at a time. Reports per-packet CPU cost (TX injection latency). |
|
||||
| `tx_throughput/<tunnel>-saturate` | Spawns `TX_THROUGHPUT_INFLIGHT` tokio tasks that independently pump `send_msg_by_ip`. Reports the aggregate throughput ceiling the peer manager + tunnel can sustain across worker threads. |
|
||||
|
||||
> **Out of scope (by design):** TUN read/write (`no_tun = true`), compression
|
||||
> (default `None`), reverse/RX-side measurement, multi-peer fanout. Add
|
||||
> separate benchmarks if you need those.
|
||||
|
||||
## Quick start
|
||||
|
||||
### ring tunnel (no root, fastest)
|
||||
|
||||
```bash
|
||||
cargo bench --bench tx_throughput
|
||||
```
|
||||
|
||||
Smoke run (faster iteration):
|
||||
|
||||
```bash
|
||||
TX_THROUGHPUT_MEASUREMENT_SECS=2 \
|
||||
TX_THROUGHPUT_WARMUP_SECS=1 \
|
||||
TX_THROUGHPUT_SAMPLE_SIZE=10 \
|
||||
cargo bench --bench tx_throughput -- --quiet
|
||||
```
|
||||
|
||||
### tcp / udp tunnels (requires Docker + root)
|
||||
|
||||
The benchmark creates a Docker network and registers each container's netns
|
||||
under `/var/run/netns`, which requires root. Run the whole command under
|
||||
`sudo`:
|
||||
|
||||
```bash
|
||||
sudo TX_THROUGHPUT_TUNNEL=tcp \
|
||||
TX_THROUGHPUT_MEASUREMENT_SECS=5 \
|
||||
TX_THROUGHPUT_WARMUP_SECS=2 \
|
||||
TX_THROUGHPUT_INFLIGHT=64 \
|
||||
cargo bench --bench tx_throughput -- --quiet
|
||||
|
||||
sudo TX_THROUGHPUT_TUNNEL=udp cargo bench --bench tx_throughput -- --quiet
|
||||
```
|
||||
|
||||
> If `sudo` cannot find `cargo`, use `sudo -E` or the absolute path
|
||||
> (`$(which cargo)`).
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Variable | Default | Notes |
|
||||
| -------------------------------- | --------------------- | -------------------------------------- |
|
||||
| `TX_THROUGHPUT_TUNNEL` | `ring` | `ring` / `tcp` / `udp` |
|
||||
| `TX_THROUGHPUT_PKT_SIZE` | `1400` | IP total length in bytes |
|
||||
| `TX_THROUGHPUT_WORKER_THREADS` | `4` | tokio worker threads |
|
||||
| `TX_THROUGHPUT_INFLIGHT` | `64` | saturate-mode concurrency (task count) |
|
||||
| `TX_THROUGHPUT_TUNNEL_PORT` | `35521` | tcp/udp listen port |
|
||||
| `TX_THROUGHPUT_MEASUREMENT_SECS` | `10` | Criterion `measurement_time` |
|
||||
| `TX_THROUGHPUT_WARMUP_SECS` | `3` | Criterion `warm_up_time` |
|
||||
| `TX_THROUGHPUT_SAMPLE_SIZE` | `10` | Criterion `sample_size` (min 10) |
|
||||
| `TX_THROUGHPUT_DOCKER_IMAGE` | `busybox:latest` | tcp/udp only |
|
||||
| `TX_THROUGHPUT_DOCKER_NET` | `easytier-bench-<id>` | auto-generated unique name |
|
||||
| `TX_THROUGHPUT_DOCKER_SUBNET` | `172.31.250.0/24` | |
|
||||
| `TX_THROUGHPUT_DOCKER_IP_A` | `172.31.250.2` | |
|
||||
| `TX_THROUGHPUT_DOCKER_IP_B` | `172.31.250.3` | |
|
||||
|
||||
## Parameter sweeps
|
||||
|
||||
```bash
|
||||
# Packet size
|
||||
for sz in 64 256 1400 9000; do
|
||||
TX_THROUGHPUT_PKT_SIZE=$sz cargo bench --bench tx_throughput -- --quick
|
||||
done
|
||||
|
||||
# Inflight depth (self-check: depth=1 should match serial baseline)
|
||||
for d in 1 4 16 64 256; do
|
||||
TX_THROUGHPUT_INFLIGHT=$d cargo bench --bench tx_throughput -- --quick
|
||||
done
|
||||
|
||||
# Worker threads
|
||||
for w in 1 2 4 8; do
|
||||
TX_THROUGHPUT_WORKER_THREADS=$w cargo bench --bench tx_throughput -- --quick
|
||||
done
|
||||
```
|
||||
|
||||
## Interpreting results
|
||||
|
||||
- **`<tunnel>`** reports per-packet latency. Lower is better. Throughput
|
||||
column here is "what one in-flight sender sustains".
|
||||
- **`<tunnel>-saturate`** reports aggregate throughput across
|
||||
`TX_THROUGHPUT_INFLIGHT` concurrent senders. If this matches the serial
|
||||
baseline, the TX path is bottlenecked on an internal serialization point
|
||||
(lock, single-threaded queue, etc.) rather than CPU or link bandwidth.
|
||||
|
||||
### Known finding (ring, single peer)
|
||||
|
||||
On the ring tunnel with a single destination peer, saturate does **not** beat
|
||||
serial (observed ~277 MiB/s saturate vs ~288 MiB/s serial on a 4-worker
|
||||
runtime). This points to a serialization point inside the peer-connection TX
|
||||
path. Tunnels with real I/O await points (tcp/udp via Docker) are expected to
|
||||
show a saturate > serial gap; verify with the sudo commands above.
|
||||
|
||||
## Output artifacts
|
||||
|
||||
Criterion writes HTML reports + SVG plots under
|
||||
`easytier/target/criterion/`. Open `tx_throughput/<tunnel>/report/index.html`
|
||||
or `.../<tunnel>-saturate/report/index.html` in a browser to inspect
|
||||
distributions and regressions across runs.
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::time::Duration;
|
||||
|
||||
use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main};
|
||||
|
||||
use easytier::tunnel::packet_def::ZCPacket;
|
||||
use easytier_core::packet::ZCPacket;
|
||||
|
||||
const PAYLOAD_SIZES: &[usize] = &[1280, 4096];
|
||||
|
||||
|
||||
@@ -1,472 +0,0 @@
|
||||
use std::{
|
||||
net::IpAddr,
|
||||
path::PathBuf,
|
||||
process::{Command, Stdio},
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use criterion::{Criterion, Throughput, criterion_group, criterion_main};
|
||||
|
||||
use easytier::{
|
||||
common::config::{ConfigLoader, TomlConfigLoader},
|
||||
instance::instance::Instance,
|
||||
tunnel::{
|
||||
packet_def::ZCPacket, ring::RingTunnelConnector, tcp::TcpTunnelConnector,
|
||||
udp::UdpTunnelConnector,
|
||||
},
|
||||
};
|
||||
|
||||
const VIRTUAL_IP_A: &str = "10.144.144.1";
|
||||
const VIRTUAL_IP_B: &str = "10.144.144.2";
|
||||
const DEFAULT_DOCKER_SUBNET: &str = "172.31.250.0/24";
|
||||
const DEFAULT_DOCKER_IP_A: &str = "172.31.250.2";
|
||||
const DEFAULT_DOCKER_IP_B: &str = "172.31.250.3";
|
||||
const DEFAULT_TUNNEL_PORT: u16 = 35521;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum TunnelKind {
|
||||
Ring,
|
||||
Tcp,
|
||||
Udp,
|
||||
}
|
||||
|
||||
impl TunnelKind {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
TunnelKind::Ring => "ring",
|
||||
TunnelKind::Tcp => "tcp",
|
||||
TunnelKind::Udp => "udp",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for TunnelKind {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
match value {
|
||||
"ring" => Ok(TunnelKind::Ring),
|
||||
"tcp" => Ok(TunnelKind::Tcp),
|
||||
"udp" => Ok(TunnelKind::Udp),
|
||||
other => Err(format!(
|
||||
"unsupported TX_THROUGHPUT_TUNNEL={other:?}; expected ring, tcp, or udp"
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BenchTopology {
|
||||
_docker: Option<DockerNetns>,
|
||||
inst_a: Instance,
|
||||
_inst_b: Instance,
|
||||
dst: IpAddr,
|
||||
packet: ZCPacket,
|
||||
}
|
||||
|
||||
struct DockerNetns {
|
||||
network: String,
|
||||
container_a: String,
|
||||
container_b: String,
|
||||
netns_a: String,
|
||||
netns_b: String,
|
||||
ip_a: String,
|
||||
netns_a_path: PathBuf,
|
||||
netns_b_path: PathBuf,
|
||||
}
|
||||
|
||||
impl DockerNetns {
|
||||
fn create() -> Self {
|
||||
let id = unique_id();
|
||||
let image = env_string("TX_THROUGHPUT_DOCKER_IMAGE", "busybox:latest");
|
||||
let network = env_string("TX_THROUGHPUT_DOCKER_NET", &format!("easytier-bench-{id}"));
|
||||
let subnet = env_string("TX_THROUGHPUT_DOCKER_SUBNET", DEFAULT_DOCKER_SUBNET);
|
||||
let ip_a = env_string("TX_THROUGHPUT_DOCKER_IP_A", DEFAULT_DOCKER_IP_A);
|
||||
let ip_b = env_string("TX_THROUGHPUT_DOCKER_IP_B", DEFAULT_DOCKER_IP_B);
|
||||
let container_a = format!("easytier-bench-a-{id}");
|
||||
let container_b = format!("easytier-bench-b-{id}");
|
||||
let netns_a = format!("easytier-bench-a-{id}");
|
||||
let netns_b = format!("easytier-bench-b-{id}");
|
||||
|
||||
docker(&[
|
||||
"network", "create", "--driver", "bridge", "--subnet", &subnet, &network,
|
||||
]);
|
||||
|
||||
let mut docker_netns = Self {
|
||||
network,
|
||||
container_a,
|
||||
container_b,
|
||||
netns_a,
|
||||
netns_b,
|
||||
ip_a: ip_a.clone(),
|
||||
netns_a_path: PathBuf::new(),
|
||||
netns_b_path: PathBuf::new(),
|
||||
};
|
||||
|
||||
docker_netns.start_container(&docker_netns.container_a, &ip_a, &image);
|
||||
docker_netns.start_container(&docker_netns.container_b, &ip_b, &image);
|
||||
|
||||
let pid_a = docker(&["inspect", "-f", "{{.State.Pid}}", &docker_netns.container_a]);
|
||||
let pid_b = docker(&["inspect", "-f", "{{.State.Pid}}", &docker_netns.container_b]);
|
||||
|
||||
docker_netns.netns_a_path = register_netns(&docker_netns.netns_a, &pid_a);
|
||||
docker_netns.netns_b_path = register_netns(&docker_netns.netns_b, &pid_b);
|
||||
docker_netns
|
||||
}
|
||||
|
||||
fn start_container(&self, name: &str, ip: &str, image: &str) {
|
||||
docker(&[
|
||||
"run",
|
||||
"-d",
|
||||
"--name",
|
||||
name,
|
||||
"--network",
|
||||
&self.network,
|
||||
"--ip",
|
||||
ip,
|
||||
image,
|
||||
"sleep",
|
||||
"3600",
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DockerNetns {
|
||||
fn drop(&mut self) {
|
||||
let _ = std::fs::remove_file(&self.netns_a_path);
|
||||
let _ = std::fs::remove_file(&self.netns_b_path);
|
||||
docker_ignore(&["rm", "-f", &self.container_a, &self.container_b]);
|
||||
docker_ignore(&["network", "rm", &self.network]);
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_tx_throughput(c: &mut Criterion) {
|
||||
let tunnel = env_string("TX_THROUGHPUT_TUNNEL", "ring")
|
||||
.parse::<TunnelKind>()
|
||||
.unwrap_or_else(|err| panic!("{err}"));
|
||||
let packet_size = env_parse("TX_THROUGHPUT_PKT_SIZE", 1400usize);
|
||||
const MIN_PKT_SIZE: usize = 28; // IPv4 (20) + UDP (8) header
|
||||
assert!(
|
||||
packet_size >= MIN_PKT_SIZE,
|
||||
"TX_THROUGHPUT_PKT_SIZE={packet_size} is smaller than the minimum {MIN_PKT_SIZE} (IPv4+UDP headers)"
|
||||
);
|
||||
let worker_threads = env_parse("TX_THROUGHPUT_WORKER_THREADS", 4usize);
|
||||
let inflight_depth = env_parse("TX_THROUGHPUT_INFLIGHT", 64usize).max(1);
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(worker_threads)
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("create tokio runtime");
|
||||
|
||||
let topology = runtime.block_on(setup_topology(tunnel, packet_size));
|
||||
let peer_manager = topology.inst_a.get_peer_manager();
|
||||
let packet = topology.packet.clone();
|
||||
let dst = topology.dst;
|
||||
|
||||
eprintln!(
|
||||
"tx_throughput: tunnel={} inflight={} workers={} pkt_size={}",
|
||||
tunnel.as_str(),
|
||||
inflight_depth.max(1),
|
||||
worker_threads,
|
||||
packet_size
|
||||
);
|
||||
|
||||
let mut group = c.benchmark_group("tx_throughput");
|
||||
group.throughput(Throughput::Bytes(packet_size as u64));
|
||||
|
||||
// Serial baseline: one packet in flight at a time.
|
||||
// Measures per-packet CPU cost (TX injection latency).
|
||||
group.bench_function(tunnel.as_str(), |b| {
|
||||
b.iter_custom(|iterations| {
|
||||
let pm = peer_manager.clone();
|
||||
let pkt = packet.clone();
|
||||
runtime.block_on(async move {
|
||||
let start = Instant::now();
|
||||
for _ in 0..iterations {
|
||||
pm.send_msg_by_ip(pkt.clone(), dst, false)
|
||||
.await
|
||||
.expect("send packet by EasyTier IP");
|
||||
}
|
||||
start.elapsed()
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Saturate: spawn TX_THROUGHPUT_INFLIGHT worker tasks, each independently
|
||||
// pumping send_msg_by_ip. Work is distributed across tokio worker threads,
|
||||
// exposing the peer manager + tunnel's true aggregate throughput ceiling.
|
||||
// With TX_THROUGHPUT_INFLIGHT=1 it degrades to the serial baseline.
|
||||
group.bench_function(format!("{}-saturate", tunnel.as_str()), |b| {
|
||||
b.iter_custom(|iterations| {
|
||||
let pm = peer_manager.clone();
|
||||
let pkt = packet.clone();
|
||||
let concurrency = inflight_depth.min(iterations as usize).max(1);
|
||||
runtime.block_on(async move {
|
||||
let counter = Arc::new(AtomicU64::new(iterations));
|
||||
let start = Instant::now();
|
||||
let mut handles = Vec::with_capacity(concurrency);
|
||||
for _ in 0..concurrency {
|
||||
let pm = pm.clone();
|
||||
let pkt = pkt.clone();
|
||||
let counter = counter.clone();
|
||||
handles.push(tokio::spawn(async move {
|
||||
loop {
|
||||
if counter
|
||||
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |cur| {
|
||||
if cur > 0 { Some(cur - 1) } else { None }
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
pm.send_msg_by_ip(pkt.clone(), dst, false)
|
||||
.await
|
||||
.expect("send packet by EasyTier IP");
|
||||
}
|
||||
}));
|
||||
}
|
||||
for h in handles {
|
||||
h.await.expect("saturate worker task panicked");
|
||||
}
|
||||
start.elapsed()
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
|
||||
runtime.block_on(async move {
|
||||
drop(topology);
|
||||
});
|
||||
}
|
||||
|
||||
async fn setup_topology(tunnel: TunnelKind, packet_size: usize) -> BenchTopology {
|
||||
let tunnel_port = env_parse("TX_THROUGHPUT_TUNNEL_PORT", DEFAULT_TUNNEL_PORT);
|
||||
let docker = match tunnel {
|
||||
TunnelKind::Ring => None,
|
||||
TunnelKind::Tcp | TunnelKind::Udp => Some(DockerNetns::create()),
|
||||
};
|
||||
|
||||
let (netns_a, netns_b) = match &docker {
|
||||
Some(docker) => (Some(docker.netns_a.clone()), Some(docker.netns_b.clone())),
|
||||
None => (None, None),
|
||||
};
|
||||
let listeners_a = match tunnel {
|
||||
TunnelKind::Ring => Vec::new(),
|
||||
TunnelKind::Tcp | TunnelKind::Udp => vec![
|
||||
format!("{}://0.0.0.0:{}", tunnel.as_str(), tunnel_port)
|
||||
.parse()
|
||||
.unwrap(),
|
||||
],
|
||||
};
|
||||
|
||||
let mut inst_a = Instance::new(no_tun_config("hot-a", VIRTUAL_IP_A, netns_a, listeners_a));
|
||||
let mut inst_b = Instance::new(no_tun_config("hot-b", VIRTUAL_IP_B, netns_b, Vec::new()));
|
||||
|
||||
inst_a.run().await.expect("inst_a run");
|
||||
inst_b.run().await.expect("inst_b run");
|
||||
|
||||
match tunnel {
|
||||
TunnelKind::Ring => inst_b
|
||||
.get_conn_manager()
|
||||
.add_connector(RingTunnelConnector::new(
|
||||
format!("ring://{}", inst_a.id()).parse().unwrap(),
|
||||
)),
|
||||
TunnelKind::Tcp => inst_b
|
||||
.get_conn_manager()
|
||||
.add_connector(TcpTunnelConnector::new(
|
||||
format!(
|
||||
"tcp://{}:{}",
|
||||
docker.as_ref().expect("tcp benchmark needs Docker").ip_a,
|
||||
tunnel_port
|
||||
)
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)),
|
||||
TunnelKind::Udp => inst_b
|
||||
.get_conn_manager()
|
||||
.add_connector(UdpTunnelConnector::new(
|
||||
format!(
|
||||
"udp://{}:{}",
|
||||
docker.as_ref().expect("udp benchmark needs Docker").ip_a,
|
||||
tunnel_port
|
||||
)
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)),
|
||||
}
|
||||
|
||||
wait_for_routes(&inst_a, &inst_b).await;
|
||||
|
||||
BenchTopology {
|
||||
_docker: docker,
|
||||
inst_a,
|
||||
_inst_b: inst_b,
|
||||
dst: VIRTUAL_IP_B.parse().unwrap(),
|
||||
packet: make_data_packet(VIRTUAL_IP_A, VIRTUAL_IP_B, packet_size),
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_routes(inst_a: &Instance, inst_b: &Instance) {
|
||||
tokio::time::timeout(Duration::from_secs(15), async {
|
||||
loop {
|
||||
let routes_a = inst_a.get_peer_manager().list_routes().await;
|
||||
let routes_b = inst_b.get_peer_manager().list_routes().await;
|
||||
if !routes_a.is_empty() && !routes_b.is_empty() {
|
||||
return;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("EasyTier routes did not converge within 15s");
|
||||
}
|
||||
|
||||
fn make_data_packet(src: &str, dst: &str, total_size: usize) -> ZCPacket {
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
let hdr_len = 28;
|
||||
let payload_len = total_size.saturating_sub(hdr_len);
|
||||
let ip_total_len = (hdr_len + payload_len) as u16;
|
||||
let mut buf = BytesMut::with_capacity(total_size);
|
||||
|
||||
buf.extend_from_slice(&[
|
||||
0x45,
|
||||
0x00,
|
||||
(ip_total_len >> 8) as u8,
|
||||
(ip_total_len & 0xff) as u8,
|
||||
0x00,
|
||||
0x00,
|
||||
0x40,
|
||||
0x00,
|
||||
0x40,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
]);
|
||||
let src: Ipv4Addr = src.parse().unwrap();
|
||||
buf.extend_from_slice(&src.octets());
|
||||
let dst: Ipv4Addr = dst.parse().unwrap();
|
||||
buf.extend_from_slice(&dst.octets());
|
||||
|
||||
let udp_len = (8 + payload_len) as u16;
|
||||
buf.extend_from_slice(&[
|
||||
0x30,
|
||||
0x39,
|
||||
0xd4,
|
||||
0x31,
|
||||
(udp_len >> 8) as u8,
|
||||
(udp_len & 0xff) as u8,
|
||||
0x00,
|
||||
0x00,
|
||||
]);
|
||||
|
||||
buf.resize(total_size, 0xaa);
|
||||
ZCPacket::new_with_payload(&buf)
|
||||
}
|
||||
|
||||
fn no_tun_config(
|
||||
name: &str,
|
||||
ipv4: &str,
|
||||
netns: Option<String>,
|
||||
listeners: Vec<url::Url>,
|
||||
) -> TomlConfigLoader {
|
||||
let config = TomlConfigLoader::default();
|
||||
config.set_inst_name(name.to_owned());
|
||||
config.set_netns(netns);
|
||||
config.set_ipv4(Some(ipv4.parse().unwrap()));
|
||||
config.set_listeners(listeners);
|
||||
let mut flags = config.get_flags();
|
||||
flags.no_tun = true;
|
||||
config.set_flags(flags);
|
||||
config
|
||||
}
|
||||
|
||||
fn register_netns(name: &str, pid: &str) -> PathBuf {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let dir = PathBuf::from("/var/run/netns");
|
||||
std::fs::create_dir_all(&dir).expect("create /var/run/netns");
|
||||
let path = dir.join(name);
|
||||
let _ = std::fs::remove_file(&path);
|
||||
std::os::unix::fs::symlink(format!("/proc/{pid}/ns/net"), &path)
|
||||
.expect("link Docker netns into /var/run/netns");
|
||||
path
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
let _ = (name, pid);
|
||||
panic!("Docker netns benchmark requires Linux");
|
||||
}
|
||||
}
|
||||
|
||||
fn docker(args: &[&str]) -> String {
|
||||
let output = Command::new("docker")
|
||||
.args(args)
|
||||
.output()
|
||||
.unwrap_or_else(|err| panic!("failed to run docker {args:?}: {err}"));
|
||||
if !output.status.success() {
|
||||
panic!(
|
||||
"docker {:?} failed with status {:?}: {}",
|
||||
args,
|
||||
output.status.code(),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
String::from_utf8_lossy(&output.stdout).trim().to_owned()
|
||||
}
|
||||
|
||||
fn docker_ignore(args: &[&str]) {
|
||||
let _ = Command::new("docker")
|
||||
.args(args)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status();
|
||||
}
|
||||
|
||||
fn env_string(name: &str, default: &str) -> String {
|
||||
std::env::var(name).unwrap_or_else(|_| default.to_owned())
|
||||
}
|
||||
|
||||
fn env_parse<T>(name: &str, default: T) -> T
|
||||
where
|
||||
T: FromStr,
|
||||
T::Err: std::fmt::Display,
|
||||
{
|
||||
match std::env::var(name) {
|
||||
Ok(value) => value
|
||||
.parse()
|
||||
.unwrap_or_else(|err| panic!("invalid {name}={value:?}: {err}")),
|
||||
Err(_) => default,
|
||||
}
|
||||
}
|
||||
|
||||
fn criterion_config() -> Criterion {
|
||||
let measurement_secs = env_parse("TX_THROUGHPUT_MEASUREMENT_SECS", 10u64);
|
||||
let warmup_secs = env_parse("TX_THROUGHPUT_WARMUP_SECS", 3u64);
|
||||
let sample_size = env_parse("TX_THROUGHPUT_SAMPLE_SIZE", 10usize).max(10);
|
||||
|
||||
Criterion::default()
|
||||
.measurement_time(Duration::from_secs(measurement_secs))
|
||||
.warm_up_time(Duration::from_secs(warmup_secs))
|
||||
.sample_size(sample_size)
|
||||
}
|
||||
|
||||
fn unique_id() -> String {
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("system clock before UNIX epoch")
|
||||
.as_nanos();
|
||||
format!("{}-{nanos}", std::process::id())
|
||||
}
|
||||
|
||||
criterion_group! {
|
||||
name = benches;
|
||||
config = criterion_config();
|
||||
targets = bench_tx_throughput
|
||||
}
|
||||
criterion_main!(benches);
|
||||
Reference in New Issue
Block a user