use std::sync::Arc;
use std::thread;
use std::time::Instant;
use zeropool::BufferPool;
#[allow(missing_docs)]
const NUM_WORKER_THREADS: usize = 8;
const CONNECTIONS_PER_THREAD: usize = 5000; const REQUESTS_PER_CONNECTION: usize = 100;
const SIMULATION_DURATION_SECS: u64 = 30;
const PACKET_SIZES: &[(usize, usize)] = &[
(64, 50), (512, 20), (1024, 10), (4096, 10), (16384, 7), (32768, 2), (65536, 1), ];
fn main() {
eprintln!("=== Network Server Profiling ===");
eprintln!("Worker threads: {NUM_WORKER_THREADS}");
eprintln!("Connections per thread: {CONNECTIONS_PER_THREAD}");
eprintln!("Requests per connection: {REQUESTS_PER_CONNECTION}");
eprintln!("Target duration: {SIMULATION_DURATION_SECS}s");
eprintln!();
let pool = Arc::new(
BufferPool::builder()
.num_shards(16)
.tls_cache_size(6)
.max_buffers_per_shard(32)
.min_buffer_size(512) .build(),
);
eprintln!("Pre-allocating buffers...");
preallocate_common_sizes(&pool);
let start = Instant::now();
let mut handles = Vec::new();
eprintln!("\nStarting {NUM_WORKER_THREADS} worker threads...");
for worker_id in 0..NUM_WORKER_THREADS {
let pool = Arc::clone(&pool);
let handle = thread::spawn(move || worker_thread(worker_id, &pool));
handles.push(handle);
}
let mut total_requests = 0;
let mut total_bytes = 0;
for handle in handles {
let (requests, bytes) = handle.join().unwrap();
total_requests += requests;
total_bytes += bytes;
}
let duration = start.elapsed();
eprintln!("\n=== Profiling Complete ===");
eprintln!("Duration: {duration:.2?}");
eprintln!("Total requests: {total_requests}");
eprintln!("Total data processed: {:.2} GB", total_bytes as f64 / 1e9);
eprintln!("Throughput: {:.0} req/s", total_requests as f64 / duration.as_secs_f64());
eprintln!("Bandwidth: {:.2} GB/s", total_bytes as f64 / duration.as_secs_f64() / 1e9);
eprintln!("Pool stats: {} buffers in pool", pool.len());
}
fn preallocate_common_sizes(pool: &BufferPool) {
pool.preallocate(32, 64);
pool.preallocate(32, 512);
pool.preallocate(32, 1024);
pool.preallocate(16, 4096);
pool.preallocate(8, 16384);
}
fn worker_thread(worker_id: usize, pool: &Arc<BufferPool>) -> (usize, usize) {
let mut requests_processed = 0;
let mut bytes_processed = 0;
if worker_id == 0 {
eprintln!(" Worker {worker_id} started");
}
for _conn_id in 0..CONNECTIONS_PER_THREAD {
let (reqs, bytes) = handle_connection(pool);
requests_processed += reqs;
bytes_processed += bytes;
}
(requests_processed, bytes_processed)
}
fn handle_connection(pool: &BufferPool) -> (usize, usize) {
let mut requests = 0;
let mut bytes = 0;
for _ in 0..REQUESTS_PER_CONNECTION {
let request_size = get_packet_size();
let mut request_buf = pool.get(request_size);
receive_packet(&mut request_buf);
let request_data = parse_request(&request_buf);
bytes += request_buf.len();
let response_size = get_packet_size();
let mut response_buf = pool.get(response_size);
generate_response(&mut response_buf, request_data);
send_packet(&response_buf);
bytes += response_buf.len();
requests += 1;
}
(requests, bytes)
}
fn get_packet_size() -> usize {
let mut cumulative = 0;
let random = (std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.subsec_nanos()
% 100) as usize;
for &(size, weight) in PACKET_SIZES {
cumulative += weight;
if random < cumulative {
return size;
}
}
PACKET_SIZES[0].0
}
fn receive_packet(buffer: &mut [u8]) {
if !buffer.is_empty() {
buffer[0] = 1;
}
}
fn parse_request(buffer: &[u8]) -> u32 {
std::hint::black_box(buffer.len() as u32)
}
fn generate_response(buffer: &mut [u8], request_id: u32) {
if !buffer.is_empty() {
buffer[0] = (request_id % 256) as u8;
}
}
fn send_packet(buffer: &[u8]) {
std::hint::black_box(buffer.len());
}