use culverin::{AttackBuilder, Target, Header, calculate_metrics};
use std::time::Duration;
use anyhow::Result;
use url::Url;
#[tokio::main]
async fn main() -> Result<()> {
let targets = vec![
Target {
method: "GET".to_string(),
url: Url::parse("https://example.com/api/users")?,
headers: vec![
Header {
name: "Accept".to_string(),
value: "application/json".to_string(),
},
],
body: None,
},
Target {
method: "POST".to_string(),
url: Url::parse("https://example.com/api/users")?,
headers: vec![
Header {
name: "Content-Type".to_string(),
value: "application/json".to_string(),
},
Header {
name: "Accept".to_string(),
value: "application/json".to_string(),
},
],
body: Some(r#"{"name": "John Doe", "email": "john@example.com"}"#.as_bytes().to_vec()),
},
Target {
method: "PUT".to_string(),
url: Url::parse("https://example.com/api/users/123")?,
headers: vec![
Header {
name: "Content-Type".to_string(),
value: "application/json".to_string(),
},
],
body: Some(r#"{"name": "Jane Doe", "email": "jane@example.com"}"#.as_bytes().to_vec()),
},
];
let results = AttackBuilder::new()
.rate(20.0) .duration(Duration::from_secs(10)) .timeout(Duration::from_secs(5)) .workers(8) .max_workers(16) .keepalive(true) .http2(true) .insecure(false) .redirects(5) .add_header("User-Agent", "culverin-advanced-example")
.targets(targets) .run()
.await?;
if let Some(metrics) = calculate_metrics(&results) {
println!("=== Attack Results ===");
println!("Total requests: {}", metrics.requests);
println!("Successful requests: {} ({:.2}%)",
metrics.success,
metrics.success_rate * 100.0);
println!("Duration: {:.2}s", metrics.duration.as_secs_f64());
println!("Requests/second: {:.2}", metrics.rate);
println!("\n=== Latency Statistics ===");
println!("Min: {:.2}ms", metrics.min.as_secs_f64() * 1000.0);
println!("Mean: {:.2}ms", metrics.mean.as_secs_f64() * 1000.0);
println!("Max: {:.2}ms", metrics.max.as_secs_f64() * 1000.0);
println!("50th percentile: {:.2}ms", metrics.p50.as_secs_f64() * 1000.0);
println!("90th percentile: {:.2}ms", metrics.p90.as_secs_f64() * 1000.0);
println!("95th percentile: {:.2}ms", metrics.p95.as_secs_f64() * 1000.0);
println!("99th percentile: {:.2}ms", metrics.p99.as_secs_f64() * 1000.0);
println!("\n=== Data Transfer ===");
println!("Total data received: {} bytes", metrics.bytes_in);
println!("Total data sent: {} bytes", metrics.bytes_out);
println!("\n=== Status Code Distribution ===");
let status_codes = results.iter()
.fold(std::collections::HashMap::new(), |mut acc, r| {
*acc.entry(r.status_code).or_insert(0) += 1;
acc
});
for (code, count) in status_codes.iter() {
println!("{}: {} requests", code, count);
}
let errors = results.iter()
.filter_map(|r| r.error.as_ref())
.fold(std::collections::HashMap::new(), |mut acc, e| {
*acc.entry(e.as_str()).or_insert(0) += 1;
acc
});
if !errors.is_empty() {
println!("\n=== Error Distribution ===");
for (error, count) in errors.iter() {
println!("{}: {} occurrences", error, count);
}
}
} else {
println!("No results collected");
}
Ok(())
}