Skip to main content

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}