rusmes_loadtest/lib.rs
1//! # rusmes-loadtest
2//!
3//! Multi-protocol load testing tool for the **RusMES** Rust Mail Enterprise Server.
4//!
5//! This crate provides the `rusmes-loadtest` binary, capable of generating
6//! realistic, high-throughput email traffic across SMTP, IMAP, JMAP, and POP3
7//! protocols simultaneously, while collecting detailed latency and throughput metrics.
8//!
9//! ## Features
10//!
11//! - **Multi-protocol** — SMTP, IMAP, JMAP, POP3, and configurable mixed-protocol scenarios
12//! - **Configurable scenarios** — SMTP throughput, concurrent connections, mixed protocol,
13//! and sustained-load test patterns
14//! - **Flexible workload patterns** — steady, spike, ramp-up, stress, and wave patterns
15//! - **HDR histogram latency** — precise percentile reporting (p50, p95, p99, p99.9)
16//! via [`hdrhistogram`]
17//! - **Report generation** — JSON, HTML, and CSV output; Prometheus metrics export
18//! - **Realistic message generation** — random sizes, template-based, and real-world content types
19//!
20//! ## Example
21//!
22//! ```text
23//! # 60-second SMTP throughput test at 200 msg/s with 20 workers
24//! rusmes-loadtest --host mail.example.com --port 25 \
25//! --protocol smtp --scenario smtp-throughput \
26//! --duration 60 --concurrency 20 --rate 200 \
27//! --output-json /tmp/results.json --output-html /tmp/results.html
28//! ```
29//!
30//! ## Architecture
31//!
32//! The main entry point is [`LoadTester`], which takes a [`LoadTestConfig`] and
33//! orchestrates concurrent workers via Tokio tasks. Each worker calls into a
34//! protocol-specific client ([`protocols::SmtpClient`], [`protocols::ImapClient`], …)
35//! and records results into a shared [`LoadTestMetrics`] protected by an
36//! `Arc<RwLock<…>>`.
37
38use anyhow::Result;
39use std::sync::Arc;
40use tokio::sync::RwLock;
41
42pub mod config;
43pub mod generators;
44pub mod metrics;
45pub mod protocols;
46pub mod reporter;
47pub mod scenarios;
48pub mod workload;
49
50pub use config::LoadTestConfig;
51pub use metrics::{LatencyStats, LoadTestMetrics};
52pub use scenarios::{LoadTestScenario, ScenarioRunner};
53
54/// Main load test coordinator
55pub struct LoadTester {
56 config: LoadTestConfig,
57 metrics: Arc<RwLock<LoadTestMetrics>>,
58}
59
60impl LoadTester {
61 /// Create a new load tester with the given configuration
62 pub fn new(config: LoadTestConfig) -> Self {
63 Self {
64 config,
65 metrics: Arc::new(RwLock::new(LoadTestMetrics::new())),
66 }
67 }
68
69 /// Run the load test
70 pub async fn run(&self) -> Result<LoadTestMetrics> {
71 tracing::info!("Starting load test with config: {:?}", self.config);
72
73 let scenario = self.config.scenario.create_runner(&self.config);
74 scenario.run(self.metrics.clone()).await?;
75
76 let final_metrics = self.metrics.read().await.clone();
77 Ok(final_metrics)
78 }
79
80 /// Get current metrics
81 pub async fn get_metrics(&self) -> LoadTestMetrics {
82 self.metrics.read().await.clone()
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn test_loadtester_creation() {
92 let config = LoadTestConfig::default();
93 let tester = LoadTester::new(config);
94 assert!(Arc::strong_count(&tester.metrics) >= 1);
95 }
96
97 #[tokio::test]
98 async fn test_loadtester_metrics() {
99 let config = LoadTestConfig::default();
100 let tester = LoadTester::new(config);
101 let metrics = tester.get_metrics().await;
102 assert_eq!(metrics.total_requests, 0);
103 }
104}