#![allow(clippy::expect_used, clippy::unwrap_used, clippy::type_complexity)]
use std::path::Path;
use std::time::Instant;
use eryx::Sandbox;
use eryx::Session;
use eryx::session::InProcessSession;
fn load_numpy_extensions(
numpy_dir: &Path,
) -> Result<Vec<(String, Vec<u8>)>, Box<dyn std::error::Error>> {
let mut extensions = Vec::new();
for entry in walkdir::WalkDir::new(numpy_dir) {
let entry = entry?;
let path = entry.path();
if let Some(ext) = path.extension()
&& ext == "so"
{
let numpy_parent = numpy_dir.parent().ok_or("no parent")?;
let relative_path = path.strip_prefix(numpy_parent)?;
let dlopen_path = format!("/site-packages/{}", relative_path.to_string_lossy());
let bytes = std::fs::read(path)?;
extensions.push((dlopen_path, bytes));
}
}
Ok(extensions)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let numpy_dir = Path::new("/tmp/numpy");
if !numpy_dir.exists() {
eprintln!("numpy not found at /tmp/numpy");
return Ok(());
}
let site_packages = numpy_dir.parent().ok_or("no parent")?;
let cache_dir = Path::new("/tmp/eryx-session-bench-cache");
let _ = std::fs::remove_dir_all(cache_dir); std::fs::create_dir_all(cache_dir)?;
println!("=== Session Per-Execution Benchmark ===\n");
let extensions = load_numpy_extensions(numpy_dir)?;
println!("Loaded {} native extensions\n", extensions.len());
println!("Creating sandbox (cold - linking + compiling + caching)...");
let start = Instant::now();
let mut builder = Sandbox::embedded();
for (name, bytes) in &extensions {
builder = builder.with_native_extension(name.clone(), bytes.clone());
}
let sandbox = builder
.with_site_packages(site_packages)
.with_cache_dir(cache_dir)?
.build()?;
println!(" Cold sandbox creation: {:?}\n", start.elapsed());
let start = Instant::now();
let mut session = InProcessSession::new(&sandbox).await?;
println!("Session creation: {:?}\n", start.elapsed());
println!("Warming up (importing numpy in session)...");
let start = Instant::now();
session.execute("import numpy as np").await?;
println!(" numpy import: {:?}", start.elapsed());
session.execute("x = 1").await?;
println!("\n--- Simple execution (x = 1) ---");
let mut times = vec![];
for _ in 0..10 {
let start = Instant::now();
session.execute("x = 1").await?;
times.push(start.elapsed());
}
let avg = times.iter().map(|t| t.as_micros()).sum::<u128>() / times.len() as u128;
println!(" Average: {}µs ({:.2}ms)", avg, avg as f64 / 1000.0);
println!("\n--- Numpy operation (np.sum([1,2,3])) ---");
let mut times = vec![];
for _ in 0..10 {
let start = Instant::now();
session.execute("result = np.sum([1,2,3])").await?;
times.push(start.elapsed());
}
let avg = times.iter().map(|t| t.as_micros()).sum::<u128>() / times.len() as u128;
println!(" Average: {}µs ({:.2}ms)", avg, avg as f64 / 1000.0);
println!("\n--- Print operation ---");
let mut times = vec![];
for _ in 0..10 {
let start = Instant::now();
session.execute("print('hello')").await?;
times.push(start.elapsed());
}
let avg = times.iter().map(|t| t.as_micros()).sum::<u128>() / times.len() as u128;
println!(" Average: {}µs ({:.2}ms)", avg, avg as f64 / 1000.0);
println!("\n=== Warm Sandbox Creation (cache hit) ===\n");
let mut times = vec![];
for i in 0..5 {
let start = Instant::now();
let mut builder = Sandbox::embedded();
for (name, bytes) in &extensions {
builder = builder.with_native_extension(name.clone(), bytes.clone());
}
let _sandbox = builder
.with_site_packages(site_packages)
.with_cache_dir(cache_dir)?
.build()?;
let elapsed = start.elapsed();
if i == 0 {
println!(" First (verifying cache): {:?}", elapsed);
}
times.push(elapsed);
}
let avg = times.iter().skip(1).map(|t| t.as_millis()).sum::<u128>() / (times.len() - 1) as u128;
println!(" Average (excluding first): {}ms", avg);
println!("\n=== Summary ===");
println!(" Session per-execution: sub-millisecond");
println!(" Warm sandbox creation: ~{}ms (mmap cache hit)", avg);
let _ = std::fs::remove_dir_all(cache_dir);
Ok(())
}