pub mod local;
pub use local::LocalPerformanceTester;
use reqwest::{Client, ClientBuilder};
use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant};
use tracing::{info, instrument, warn};
#[derive(Debug, Clone)]
pub struct OptimizedHttpConfig {
pub pool_max_idle_per_host: usize,
pub pool_idle_timeout: Duration,
pub connect_timeout: Duration,
pub request_timeout: Duration,
pub http2_prior_knowledge: bool,
pub http2_adaptive_window: bool,
pub gzip: bool,
pub brotli: bool,
pub user_agent: String,
}
impl Default for OptimizedHttpConfig {
fn default() -> Self {
Self {
pool_max_idle_per_host: 90,
pool_idle_timeout: Duration::from_secs(90),
connect_timeout: Duration::from_secs(10),
request_timeout: Duration::from_secs(30),
http2_prior_knowledge: true,
http2_adaptive_window: true,
gzip: true,
brotli: true,
user_agent: format!("open-lark/{}", env!("CARGO_PKG_VERSION")),
}
}
}
impl OptimizedHttpConfig {
pub fn production() -> Self {
Self {
pool_max_idle_per_host: 100,
pool_idle_timeout: Duration::from_secs(120),
connect_timeout: Duration::from_secs(5),
request_timeout: Duration::from_secs(30),
http2_prior_knowledge: true,
http2_adaptive_window: true,
gzip: true,
brotli: true,
user_agent: format!("open-lark/{}", env!("CARGO_PKG_VERSION")),
}
}
pub fn high_throughput() -> Self {
Self {
pool_max_idle_per_host: 200,
pool_idle_timeout: Duration::from_secs(180),
connect_timeout: Duration::from_secs(3),
request_timeout: Duration::from_secs(15),
http2_prior_knowledge: true,
http2_adaptive_window: true,
gzip: true,
brotli: true,
user_agent: format!("open-lark/{}", env!("CARGO_PKG_VERSION")),
}
}
pub fn low_latency() -> Self {
Self {
pool_max_idle_per_host: 50,
pool_idle_timeout: Duration::from_secs(60),
connect_timeout: Duration::from_secs(2),
request_timeout: Duration::from_secs(10),
http2_prior_knowledge: true,
http2_adaptive_window: true,
gzip: false, brotli: false,
user_agent: format!("open-lark/{}", env!("CARGO_PKG_VERSION")),
}
}
pub fn build_client(&self) -> Result<Client, reqwest::Error> {
let mut builder = ClientBuilder::new()
.pool_max_idle_per_host(self.pool_max_idle_per_host)
.pool_idle_timeout(self.pool_idle_timeout)
.connect_timeout(self.connect_timeout)
.timeout(self.request_timeout)
.user_agent(&self.user_agent);
if !self.gzip {
builder = builder.no_gzip();
}
if !self.brotli {
builder = builder.no_brotli();
}
builder.build()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceMetrics {
pub test_name: String,
pub total_requests: u64,
pub successful_requests: u64,
pub failed_requests: u64,
pub avg_response_time_ms: f64,
pub p95_response_time_ms: f64,
pub p99_response_time_ms: f64,
pub min_response_time_ms: f64,
pub max_response_time_ms: f64,
pub requests_per_second: f64,
pub duration_seconds: f64,
pub throughput_kbps: f64,
pub error_rate: f64,
}
impl PerformanceMetrics {
pub fn calculate(
test_name: String,
response_times_ms: Vec<f64>,
total_duration: Duration,
total_bytes: u64,
) -> Self {
let total_requests = response_times_ms.len() as u64;
let failed_requests = response_times_ms.iter().filter(|&&t| t < 0.0).count() as u64;
let successful_requests = total_requests - failed_requests;
let mut sorted_times: Vec<f64> = response_times_ms
.iter()
.filter(|&&t| t >= 0.0)
.copied()
.collect();
sorted_times.sort_by(|a, b| a.partial_cmp(b).unwrap());
let avg_response_time_ms = if sorted_times.is_empty() {
0.0
} else {
sorted_times.iter().sum::<f64>() / sorted_times.len() as f64
};
let p95_response_time_ms = if sorted_times.is_empty() {
0.0
} else {
let index = ((sorted_times.len() as f64) * 0.95) as usize;
sorted_times
.get(index.saturating_sub(1))
.copied()
.unwrap_or(0.0)
};
let p99_response_time_ms = if sorted_times.is_empty() {
0.0
} else {
let index = ((sorted_times.len() as f64) * 0.99) as usize;
sorted_times
.get(index.saturating_sub(1))
.copied()
.unwrap_or(0.0)
};
let min_response_time_ms = sorted_times.first().copied().unwrap_or(0.0);
let max_response_time_ms = sorted_times.last().copied().unwrap_or(0.0);
let duration_seconds = total_duration.as_secs_f64();
let requests_per_second = if duration_seconds > 0.0 {
total_requests as f64 / duration_seconds
} else {
0.0
};
let throughput_kbps = if duration_seconds > 0.0 {
(total_bytes as f64) / (duration_seconds * 1024.0)
} else {
0.0
};
let error_rate = if total_requests > 0 {
(failed_requests as f64 / total_requests as f64) * 100.0
} else {
0.0
};
Self {
test_name,
total_requests,
successful_requests,
failed_requests,
avg_response_time_ms,
p95_response_time_ms,
p99_response_time_ms,
min_response_time_ms,
max_response_time_ms,
requests_per_second,
duration_seconds,
throughput_kbps,
error_rate,
}
}
pub fn print_report(&self) {
info!("╔══════════════════════════════════════════════════════════╗");
info!("║ 性能测试报告 ║");
info!("╠══════════════════════════════════════════════════════════╣");
info!("║ 测试名称: {:<45} ║", self.test_name);
info!("║ 总请求数: {:<45} ║", self.total_requests);
info!("║ 成功请求: {:<45} ║", self.successful_requests);
info!("║ 失败请求: {:<45} ║", self.failed_requests);
info!("║ 错误率: {:<43.2}% ║", self.error_rate);
info!("╠══════════════════════════════════════════════════════════╣");
info!("║ 平均响应时间: {:<39.2}ms ║", self.avg_response_time_ms);
info!("║ 95th百分位: {:<39.2}ms ║", self.p95_response_time_ms);
info!("║ 99th百分位: {:<39.2}ms ║", self.p99_response_time_ms);
info!("║ 最小响应时间: {:<39.2}ms ║", self.min_response_time_ms);
info!("║ 最大响应时间: {:<39.2}ms ║", self.max_response_time_ms);
info!("╠══════════════════════════════════════════════════════════╣");
info!("║ 吞吐量: {:<39.2}RPS ║", self.requests_per_second);
info!("║ 数据传输率: {:<37.2}KB/s ║", self.throughput_kbps);
info!("║ 测试时长: {:<39.2}s ║", self.duration_seconds);
info!("╚══════════════════════════════════════════════════════════╝");
}
}
#[derive(Debug, Clone)]
pub struct BenchmarkConfig {
pub concurrent_connections: usize,
pub requests_per_connection: usize,
pub warmup_requests: usize,
pub target_url: String,
pub headers: std::collections::HashMap<String, String>,
}
impl Default for BenchmarkConfig {
fn default() -> Self {
Self {
concurrent_connections: 10,
requests_per_connection: 100,
warmup_requests: 10,
target_url: "https://httpbin.org/json".to_string(),
headers: std::collections::HashMap::new(),
}
}
}
pub struct HttpBenchmark {
client: Client,
config: BenchmarkConfig,
}
impl HttpBenchmark {
pub fn new(client: Client, config: BenchmarkConfig) -> Self {
Self { client, config }
}
#[instrument(skip(self), fields(
concurrent_connections = self.config.concurrent_connections,
requests_per_connection = self.config.requests_per_connection,
target_url = %self.config.target_url
))]
pub async fn run_benchmark(
&self,
) -> Result<PerformanceMetrics, Box<dyn std::error::Error + Send + Sync>> {
info!("开始HTTP客户端基准测试...");
info!("目标URL: {}", self.config.target_url);
info!("并发连接数: {}", self.config.concurrent_connections);
info!("每连接请求数: {}", self.config.requests_per_connection);
if self.config.warmup_requests > 0 {
info!("预热阶段: {} 个请求", self.config.warmup_requests);
for _ in 0..self.config.warmup_requests {
let _ = self.make_request().await;
}
info!("预热完成");
}
let start_time = Instant::now();
let mut tasks = Vec::new();
let mut total_bytes = 0u64;
for connection_id in 0..self.config.concurrent_connections {
let client = self.client.clone();
let config = self.config.clone();
let task = tokio::spawn(async move {
let mut response_times = Vec::new();
let mut bytes_transferred = 0u64;
for request_id in 0..config.requests_per_connection {
let request_start = Instant::now();
match Self::make_single_request(&client, &config.target_url, &config.headers)
.await
{
Ok(bytes) => {
let elapsed = request_start.elapsed().as_secs_f64() * 1000.0;
response_times.push(elapsed);
bytes_transferred += bytes;
}
Err(e) => {
warn!("连接 {} 请求 {} 失败: {}", connection_id, request_id, e);
response_times.push(-1.0); }
}
}
(response_times, bytes_transferred)
});
tasks.push(task);
}
let mut all_response_times = Vec::new();
for task in tasks {
match task.await {
Ok((response_times, bytes)) => {
all_response_times.extend(response_times);
total_bytes += bytes;
}
Err(e) => {
warn!("任务执行失败: {}", e);
}
}
}
let total_duration = start_time.elapsed();
let metrics = PerformanceMetrics::calculate(
"HTTP客户端基准测试".to_string(),
all_response_times,
total_duration,
total_bytes,
);
info!("基准测试完成");
metrics.print_report();
Ok(metrics)
}
async fn make_request(&self) -> Result<u64, Box<dyn std::error::Error + Send + Sync>> {
Self::make_single_request(&self.client, &self.config.target_url, &self.config.headers).await
}
async fn make_single_request(
client: &Client,
url: &str,
headers: &std::collections::HashMap<String, String>,
) -> Result<u64, Box<dyn std::error::Error + Send + Sync>> {
let mut request = client.get(url);
for (key, value) in headers {
request = request.header(key, value);
}
let response = request.send().await?;
let bytes = response.content_length().unwrap_or(0);
let _body = response.bytes().await?;
Ok(bytes)
}
}
pub struct ClientComparison;
impl ClientComparison {
#[instrument]
pub async fn compare_configurations(
configs: Vec<(&str, OptimizedHttpConfig)>,
benchmark_config: BenchmarkConfig,
) -> Result<Vec<(String, PerformanceMetrics)>, Box<dyn std::error::Error + Send + Sync>> {
let mut results = Vec::new();
for (config_name, http_config) in configs {
info!("测试配置: {}", config_name);
let client = http_config.build_client()?;
let benchmark = HttpBenchmark::new(client, benchmark_config.clone());
match benchmark.run_benchmark().await {
Ok(metrics) => {
let mut named_metrics = metrics;
named_metrics.test_name = config_name.to_string();
results.push((config_name.to_string(), named_metrics));
}
Err(e) => {
warn!("配置 {} 测试失败: {}", config_name, e);
}
}
tokio::time::sleep(Duration::from_secs(2)).await;
}
Self::print_comparison_report(&results);
Ok(results)
}
fn print_comparison_report(results: &[(String, PerformanceMetrics)]) {
if results.is_empty() {
warn!("没有可比较的结果");
return;
}
info!("╔══════════════════════════════════════════════════════════════════════════╗");
info!("║ 配置性能比较报告 ║");
info!("╠══════════════════════════════════════════════════════════════════════════╣");
info!("║ 配置名称 │ RPS │ 平均延迟(ms) │ P95(ms) │ P99(ms) │ 错误率(%) ║");
info!("╠═════════════════╪═════════╪══════════════╪═════════╪═════════╪═══════════╣");
for (name, metrics) in results {
info!(
"║ {:<15} │ {:>7.1} │ {:>12.2} │ {:>7.2} │ {:>7.2} │ {:>9.2} ║",
name.chars().take(15).collect::<String>(),
metrics.requests_per_second,
metrics.avg_response_time_ms,
metrics.p95_response_time_ms,
metrics.p99_response_time_ms,
metrics.error_rate
);
}
info!("╚═════════════════╧═════════╧══════════════╧═════════╧═════════╧═══════════╝");
if let Some((best_name, best_metrics)) = results.iter().max_by(|a, b| {
a.1.requests_per_second
.partial_cmp(&b.1.requests_per_second)
.unwrap()
}) {
info!(
"🏆 最佳吞吐量配置: {} ({:.1} RPS)",
best_name, best_metrics.requests_per_second
);
}
if let Some((best_name, best_metrics)) = results.iter().min_by(|a, b| {
a.1.avg_response_time_ms
.partial_cmp(&b.1.avg_response_time_ms)
.unwrap()
}) {
info!(
"🚀 最低延迟配置: {} ({:.2}ms 平均延迟)",
best_name, best_metrics.avg_response_time_ms
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_optimized_http_config_default() {
let config = OptimizedHttpConfig::default();
assert_eq!(config.pool_max_idle_per_host, 90);
assert_eq!(config.connect_timeout, Duration::from_secs(10));
assert!(config.http2_prior_knowledge);
assert!(config.gzip);
}
#[test]
fn test_optimized_http_config_production() {
let config = OptimizedHttpConfig::production();
assert_eq!(config.pool_max_idle_per_host, 100);
assert_eq!(config.connect_timeout, Duration::from_secs(5));
assert_eq!(config.request_timeout, Duration::from_secs(30));
}
#[test]
fn test_optimized_http_config_high_throughput() {
let config = OptimizedHttpConfig::high_throughput();
assert_eq!(config.pool_max_idle_per_host, 200);
assert_eq!(config.connect_timeout, Duration::from_secs(3));
assert_eq!(config.request_timeout, Duration::from_secs(15));
}
#[test]
fn test_optimized_http_config_low_latency() {
let config = OptimizedHttpConfig::low_latency();
assert_eq!(config.pool_max_idle_per_host, 50);
assert_eq!(config.connect_timeout, Duration::from_secs(2));
assert!(!config.gzip); assert!(!config.brotli);
}
#[test]
fn test_performance_metrics_calculation() {
let response_times = vec![100.0, 200.0, 150.0, 300.0, 250.0];
let duration = Duration::from_secs(5);
let total_bytes = 1024 * 5;
let metrics = PerformanceMetrics::calculate(
"test".to_string(),
response_times,
duration,
total_bytes,
);
assert_eq!(metrics.total_requests, 5);
assert_eq!(metrics.successful_requests, 5);
assert_eq!(metrics.failed_requests, 0);
assert_eq!(metrics.avg_response_time_ms, 200.0);
assert_eq!(metrics.min_response_time_ms, 100.0);
assert_eq!(metrics.max_response_time_ms, 300.0);
assert_eq!(metrics.requests_per_second, 1.0);
assert_eq!(metrics.throughput_kbps, 1.0); assert_eq!(metrics.error_rate, 0.0);
}
#[test]
fn test_performance_metrics_with_failures() {
let response_times = vec![100.0, -1.0, 150.0, -1.0, 250.0]; let duration = Duration::from_secs(5);
let total_bytes = 1024 * 3;
let metrics = PerformanceMetrics::calculate(
"test_with_failures".to_string(),
response_times,
duration,
total_bytes,
);
assert_eq!(metrics.total_requests, 5);
assert_eq!(metrics.successful_requests, 3);
assert_eq!(metrics.failed_requests, 2);
assert_eq!(metrics.error_rate, 40.0); assert_eq!(metrics.avg_response_time_ms, (100.0 + 150.0 + 250.0) / 3.0);
}
#[test]
fn test_benchmark_config_default() {
let config = BenchmarkConfig::default();
assert_eq!(config.concurrent_connections, 10);
assert_eq!(config.requests_per_connection, 100);
assert_eq!(config.warmup_requests, 10);
assert_eq!(config.target_url, "https://httpbin.org/json");
}
#[tokio::test]
async fn test_http_config_build_client() {
let config = OptimizedHttpConfig::default();
let client = config.build_client();
assert!(client.is_ok(), "应该能够成功构建HTTP客户端");
}
#[test]
fn test_performance_metrics_empty_response_times() {
let response_times: Vec<f64> = vec![];
let duration = Duration::from_secs(1);
let total_bytes = 0;
let metrics = PerformanceMetrics::calculate(
"empty_test".to_string(),
response_times,
duration,
total_bytes,
);
assert_eq!(metrics.total_requests, 0);
assert_eq!(metrics.avg_response_time_ms, 0.0);
assert_eq!(metrics.requests_per_second, 0.0);
}
#[test]
fn test_performance_metrics_percentiles() {
let response_times = (1..=100).map(|i| i as f64).collect::<Vec<_>>();
let duration = Duration::from_secs(10);
let total_bytes = 10240;
let metrics = PerformanceMetrics::calculate(
"percentile_test".to_string(),
response_times,
duration,
total_bytes,
);
assert_eq!(metrics.total_requests, 100);
assert_eq!(metrics.min_response_time_ms, 1.0);
assert_eq!(metrics.max_response_time_ms, 100.0);
assert!((metrics.p95_response_time_ms - 95.0).abs() < 2.0);
assert!((metrics.p99_response_time_ms - 99.0).abs() < 2.0);
}
#[test]
fn test_optimized_http_config_clone() {
let config = OptimizedHttpConfig::production();
let cloned_config = config.clone();
assert_eq!(
config.pool_max_idle_per_host,
cloned_config.pool_max_idle_per_host
);
assert_eq!(config.connect_timeout, cloned_config.connect_timeout);
assert_eq!(config.user_agent, cloned_config.user_agent);
}
#[test]
fn test_optimized_http_config_debug() {
let config = OptimizedHttpConfig::default();
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("OptimizedHttpConfig"));
assert!(debug_str.contains("pool_max_idle_per_host"));
assert!(debug_str.contains("user_agent"));
}
#[test]
fn test_optimized_http_config_custom_user_agent() {
let config = OptimizedHttpConfig {
user_agent: "custom-agent/1.0".to_string(),
..Default::default()
};
assert_eq!(config.user_agent, "custom-agent/1.0");
}
#[test]
fn test_optimized_http_config_compression_settings() {
let low_latency = OptimizedHttpConfig::low_latency();
assert!(!low_latency.gzip);
assert!(!low_latency.brotli);
let production = OptimizedHttpConfig::production();
assert!(production.gzip);
assert!(production.brotli);
}
#[tokio::test]
async fn test_http_config_build_client_with_no_compression() {
let config = OptimizedHttpConfig {
gzip: false,
brotli: false,
..Default::default()
};
let client = config.build_client();
assert!(client.is_ok());
}
#[test]
fn test_performance_metrics_zero_duration() {
let response_times = vec![100.0, 200.0, 150.0];
let duration = Duration::from_secs(0);
let total_bytes = 1024;
let metrics = PerformanceMetrics::calculate(
"zero_duration_test".to_string(),
response_times,
duration,
total_bytes,
);
assert_eq!(metrics.duration_seconds, 0.0);
assert_eq!(metrics.requests_per_second, 0.0);
assert_eq!(metrics.throughput_kbps, 0.0);
}
#[test]
fn test_performance_metrics_single_request() {
let response_times = vec![123.45];
let duration = Duration::from_secs(1);
let total_bytes = 512;
let metrics = PerformanceMetrics::calculate(
"single_request_test".to_string(),
response_times,
duration,
total_bytes,
);
assert_eq!(metrics.total_requests, 1);
assert_eq!(metrics.successful_requests, 1);
assert_eq!(metrics.failed_requests, 0);
assert_eq!(metrics.avg_response_time_ms, 123.45);
assert_eq!(metrics.min_response_time_ms, 123.45);
assert_eq!(metrics.max_response_time_ms, 123.45);
assert_eq!(metrics.p95_response_time_ms, 123.45);
assert_eq!(metrics.p99_response_time_ms, 123.45);
assert_eq!(metrics.requests_per_second, 1.0);
assert_eq!(metrics.error_rate, 0.0);
}
#[test]
fn test_performance_metrics_all_failures() {
let response_times = vec![-1.0, -1.0, -1.0];
let duration = Duration::from_secs(3);
let total_bytes = 0;
let metrics = PerformanceMetrics::calculate(
"all_failures_test".to_string(),
response_times,
duration,
total_bytes,
);
assert_eq!(metrics.total_requests, 3);
assert_eq!(metrics.successful_requests, 0);
assert_eq!(metrics.failed_requests, 3);
assert_eq!(metrics.error_rate, 100.0);
assert_eq!(metrics.avg_response_time_ms, 0.0);
assert_eq!(metrics.min_response_time_ms, 0.0);
assert_eq!(metrics.max_response_time_ms, 0.0);
}
#[test]
fn test_performance_metrics_large_dataset() {
let response_times: Vec<f64> = (1..=1000).map(|i| i as f64).collect();
let duration = Duration::from_secs(100);
let total_bytes = 1024 * 1000;
let metrics = PerformanceMetrics::calculate(
"large_dataset_test".to_string(),
response_times,
duration,
total_bytes,
);
assert_eq!(metrics.total_requests, 1000);
assert_eq!(metrics.successful_requests, 1000);
assert_eq!(metrics.avg_response_time_ms, 500.5); assert_eq!(metrics.min_response_time_ms, 1.0);
assert_eq!(metrics.max_response_time_ms, 1000.0);
assert!((metrics.p95_response_time_ms - 950.0).abs() < 5.0);
assert!((metrics.p99_response_time_ms - 990.0).abs() < 5.0);
assert_eq!(metrics.requests_per_second, 10.0);
assert_eq!(metrics.throughput_kbps, 10.0); }
#[test]
fn test_performance_metrics_mixed_results() {
let response_times = vec![50.0, -1.0, 75.0, 100.0, -1.0, 125.0, 150.0];
let duration = Duration::from_secs(7);
let total_bytes = 2048;
let metrics = PerformanceMetrics::calculate(
"mixed_results_test".to_string(),
response_times,
duration,
total_bytes,
);
assert_eq!(metrics.total_requests, 7);
assert_eq!(metrics.successful_requests, 5);
assert_eq!(metrics.failed_requests, 2);
assert_eq!(metrics.error_rate, (2.0 / 7.0) * 100.0);
assert_eq!(
metrics.avg_response_time_ms,
(50.0 + 75.0 + 100.0 + 125.0 + 150.0) / 5.0
);
assert_eq!(metrics.min_response_time_ms, 50.0);
assert_eq!(metrics.max_response_time_ms, 150.0);
}
#[test]
fn test_benchmark_config_clone() {
let config = BenchmarkConfig::default();
let cloned_config = config.clone();
assert_eq!(
config.concurrent_connections,
cloned_config.concurrent_connections
);
assert_eq!(
config.requests_per_connection,
cloned_config.requests_per_connection
);
assert_eq!(config.target_url, cloned_config.target_url);
assert_eq!(config.headers.len(), cloned_config.headers.len());
}
#[test]
fn test_benchmark_config_debug() {
let config = BenchmarkConfig::default();
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("BenchmarkConfig"));
assert!(debug_str.contains("concurrent_connections"));
assert!(debug_str.contains("target_url"));
}
#[test]
fn test_benchmark_config_custom_headers() {
let mut config = BenchmarkConfig::default();
config
.headers
.insert("Authorization".to_string(), "Bearer token".to_string());
config
.headers
.insert("Content-Type".to_string(), "application/json".to_string());
assert_eq!(config.headers.len(), 2);
assert_eq!(
config.headers.get("Authorization"),
Some(&"Bearer token".to_string())
);
assert_eq!(
config.headers.get("Content-Type"),
Some(&"application/json".to_string())
);
}
#[test]
fn test_benchmark_config_custom_values() {
let config = BenchmarkConfig {
concurrent_connections: 50,
requests_per_connection: 200,
warmup_requests: 25,
target_url: "https://example.com/api".to_string(),
..Default::default()
};
assert_eq!(config.concurrent_connections, 50);
assert_eq!(config.requests_per_connection, 200);
assert_eq!(config.warmup_requests, 25);
assert_eq!(config.target_url, "https://example.com/api");
}
#[test]
fn test_performance_metrics_serialize_deserialize() {
let metrics = PerformanceMetrics {
test_name: "serialize_test".to_string(),
total_requests: 100,
successful_requests: 95,
failed_requests: 5,
avg_response_time_ms: 123.45,
p95_response_time_ms: 200.0,
p99_response_time_ms: 250.0,
min_response_time_ms: 50.0,
max_response_time_ms: 300.0,
requests_per_second: 10.5,
duration_seconds: 9.52,
throughput_kbps: 15.3,
error_rate: 5.0,
};
let serialized = serde_json::to_string(&metrics);
assert!(serialized.is_ok());
let deserialized: Result<PerformanceMetrics, _> =
serde_json::from_str(&serialized.unwrap());
assert!(deserialized.is_ok());
let deserialized_metrics = deserialized.unwrap();
assert_eq!(metrics.test_name, deserialized_metrics.test_name);
assert_eq!(metrics.total_requests, deserialized_metrics.total_requests);
assert_eq!(
metrics.avg_response_time_ms,
deserialized_metrics.avg_response_time_ms
);
}
#[test]
fn test_performance_metrics_edge_case_percentiles() {
let response_times = vec![42.0];
let duration = Duration::from_secs(1);
let total_bytes = 100;
let metrics = PerformanceMetrics::calculate(
"single_percentile_test".to_string(),
response_times,
duration,
total_bytes,
);
assert_eq!(metrics.p95_response_time_ms, 42.0);
assert_eq!(metrics.p99_response_time_ms, 42.0);
}
#[test]
fn test_performance_metrics_edge_case_small_dataset() {
let response_times = vec![10.0, 20.0];
let duration = Duration::from_secs(1);
let total_bytes = 200;
let metrics = PerformanceMetrics::calculate(
"small_dataset_test".to_string(),
response_times,
duration,
total_bytes,
);
assert_eq!(metrics.total_requests, 2);
assert_eq!(metrics.avg_response_time_ms, 15.0);
assert_eq!(metrics.p95_response_time_ms, 10.0);
assert_eq!(metrics.p99_response_time_ms, 10.0);
}
#[test]
fn test_optimized_http_config_extreme_values() {
let config = OptimizedHttpConfig {
pool_max_idle_per_host: 0,
pool_idle_timeout: Duration::from_secs(0),
connect_timeout: Duration::from_millis(1),
request_timeout: Duration::from_millis(1),
..Default::default()
};
let client = config.build_client();
assert!(client.is_ok());
}
#[test]
fn test_optimized_http_config_very_large_values() {
let config = OptimizedHttpConfig {
pool_max_idle_per_host: 10000,
pool_idle_timeout: Duration::from_secs(86400), connect_timeout: Duration::from_secs(300), request_timeout: Duration::from_secs(3600), ..Default::default()
};
let client = config.build_client();
assert!(client.is_ok());
}
#[test]
fn test_performance_metrics_precision() {
let response_times = vec![0.001, 0.002, 0.003]; let duration = Duration::from_millis(1);
let total_bytes = 1;
let metrics = PerformanceMetrics::calculate(
"precision_test".to_string(),
response_times,
duration,
total_bytes,
);
assert_eq!(metrics.avg_response_time_ms, 0.002);
assert_eq!(metrics.min_response_time_ms, 0.001);
assert_eq!(metrics.max_response_time_ms, 0.003);
}
#[test]
fn test_performance_metrics_user_agent_format() {
let default_config = OptimizedHttpConfig::default();
let production_config = OptimizedHttpConfig::production();
let high_throughput_config = OptimizedHttpConfig::high_throughput();
let low_latency_config = OptimizedHttpConfig::low_latency();
let version = env!("CARGO_PKG_VERSION");
let expected_user_agent = format!("open-lark/{}", version);
assert_eq!(default_config.user_agent, expected_user_agent);
assert_eq!(production_config.user_agent, expected_user_agent);
assert_eq!(high_throughput_config.user_agent, expected_user_agent);
assert_eq!(low_latency_config.user_agent, expected_user_agent);
}
}