use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::collections::HashMap;
use std::time::{Duration, Instant};
use super::json_rpc::compare_sizes_auto;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkConfig {
pub iterations: usize,
pub warmup_iterations: usize,
pub message_sizes: Vec<usize>,
pub concurrency_levels: Vec<usize>,
pub include_comparison: bool,
}
impl Default for BenchmarkConfig {
fn default() -> Self {
Self {
iterations: 10000,
warmup_iterations: 1000,
message_sizes: vec![64, 256, 1024, 4096, 16384],
concurrency_levels: vec![1, 10, 50, 100],
include_comparison: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LatencyStats {
pub min: Duration,
pub max: Duration,
pub mean: Duration,
pub p50: Duration,
pub p95: Duration,
pub p99: Duration,
pub std_dev: Duration,
}
impl LatencyStats {
pub fn from_durations(mut durations: Vec<Duration>) -> Self {
if durations.is_empty() {
return Self {
min: Duration::ZERO,
max: Duration::ZERO,
mean: Duration::ZERO,
p50: Duration::ZERO,
p95: Duration::ZERO,
p99: Duration::ZERO,
std_dev: Duration::ZERO,
};
}
durations.sort();
let n = durations.len();
let min = durations[0];
let max = durations[n - 1];
let total: Duration = durations.iter().sum();
let mean = total / n as u32;
let p50 = durations[n / 2];
let p95 = durations[(n as f64 * 0.95) as usize];
let p99 = durations[(n as f64 * 0.99).min((n - 1) as f64) as usize];
let mean_nanos = mean.as_nanos() as f64;
let variance: f64 = durations
.iter()
.map(|d| {
let diff = d.as_nanos() as f64 - mean_nanos;
diff * diff
})
.sum::<f64>()
/ n as f64;
let std_dev = Duration::from_nanos(variance.sqrt() as u64);
Self {
min,
max,
mean,
p50,
p95,
p99,
std_dev,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThroughputResult {
pub messages_per_second: f64,
pub bytes_per_second: f64,
pub total_messages: usize,
pub total_bytes: usize,
pub duration: Duration,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryUsage {
pub heap_bytes: usize,
pub peak_bytes: usize,
pub allocation_count: usize,
}
impl Default for MemoryUsage {
fn default() -> Self {
Self {
heap_bytes: 0,
peak_bytes: 0,
allocation_count: 0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProtocolComparison {
pub payload_name: String,
pub mcp_size: usize,
pub dcp_size: usize,
pub size_ratio: f64,
pub mcp_encode_latency: Duration,
pub dcp_encode_latency: Duration,
pub latency_ratio: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkResult {
pub name: String,
pub latency: LatencyStats,
pub throughput: ThroughputResult,
pub memory: Option<MemoryUsage>,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkResults {
pub config: BenchmarkConfig,
pub results: Vec<BenchmarkResult>,
pub comparisons: Vec<ProtocolComparison>,
pub timestamp: String,
pub total_duration: Duration,
}
impl BenchmarkResults {
pub fn to_json(&self) -> String {
serde_json::to_string_pretty(self).unwrap_or_default()
}
pub fn to_markdown(&self) -> String {
let mut md = String::new();
md.push_str("# DCP Benchmark Results\n\n");
md.push_str(&format!("Generated: {}\n\n", self.timestamp));
md.push_str(&format!(
"Total Duration: {:.2}s\n\n",
self.total_duration.as_secs_f64()
));
md.push_str("## Configuration\n\n");
md.push_str(&format!("- Iterations: {}\n", self.config.iterations));
md.push_str(&format!(
"- Warmup Iterations: {}\n",
self.config.warmup_iterations
));
md.push_str(&format!(
"- Message Sizes: {:?}\n",
self.config.message_sizes
));
md.push_str(&format!(
"- Concurrency Levels: {:?}\n\n",
self.config.concurrency_levels
));
md.push_str("## Latency Results\n\n");
md.push_str("| Benchmark | Min | Mean | P50 | P95 | P99 | Max |\n");
md.push_str("|-----------|-----|------|-----|-----|-----|-----|\n");
for result in &self.results {
md.push_str(&format!(
"| {} | {:?} | {:?} | {:?} | {:?} | {:?} | {:?} |\n",
result.name,
result.latency.min,
result.latency.mean,
result.latency.p50,
result.latency.p95,
result.latency.p99,
result.latency.max
));
}
md.push('\n');
md.push_str("## Throughput Results\n\n");
md.push_str("| Benchmark | Messages/sec | MB/sec | Total Messages |\n");
md.push_str("|-----------|--------------|--------|----------------|\n");
for result in &self.results {
md.push_str(&format!(
"| {} | {:.0} | {:.2} | {} |\n",
result.name,
result.throughput.messages_per_second,
result.throughput.bytes_per_second / 1_000_000.0,
result.throughput.total_messages
));
}
md.push('\n');
if !self.comparisons.is_empty() {
md.push_str("## DCP vs MCP Comparison\n\n");
md.push_str("| Payload | MCP Size | DCP Size | Size Ratio | MCP Latency | DCP Latency | Latency Ratio |\n");
md.push_str("|---------|----------|----------|------------|-------------|-------------|---------------|\n");
for comp in &self.comparisons {
md.push_str(&format!(
"| {} | {} B | {} B | {:.2}x | {:?} | {:?} | {:.2}x |\n",
comp.payload_name,
comp.mcp_size,
comp.dcp_size,
comp.size_ratio,
comp.mcp_encode_latency,
comp.dcp_encode_latency,
comp.latency_ratio
));
}
md.push('\n');
}
md
}
}
pub struct BenchmarkSuite {
config: BenchmarkConfig,
results: Vec<BenchmarkResult>,
comparisons: Vec<ProtocolComparison>,
}
impl BenchmarkSuite {
pub fn new(config: BenchmarkConfig) -> Self {
Self {
config,
results: Vec::new(),
comparisons: Vec::new(),
}
}
pub fn with_defaults() -> Self {
Self::new(BenchmarkConfig::default())
}
pub fn run(&mut self) -> BenchmarkResults {
let start = Instant::now();
self.run_throughput_benchmarks();
self.run_latency_benchmarks();
if self.config.include_comparison {
self.run_protocol_comparison();
}
let total_duration = start.elapsed();
BenchmarkResults {
config: self.config.clone(),
results: self.results.clone(),
comparisons: self.comparisons.clone(),
timestamp: chrono_lite_timestamp(),
total_duration,
}
}
fn run_throughput_benchmarks(&mut self) {
for &size in &self.config.message_sizes.clone() {
let result = self.benchmark_throughput(size);
self.results.push(result);
}
}
fn run_latency_benchmarks(&mut self) {
for &size in &self.config.message_sizes.clone() {
let result = self.benchmark_latency(size);
self.results.push(result);
}
}
fn benchmark_throughput(&self, message_size: usize) -> BenchmarkResult {
let payload = generate_payload(message_size);
let iterations = self.config.iterations;
for _ in 0..self.config.warmup_iterations {
let _ = serde_json::to_string(&payload);
}
let start = Instant::now();
let mut total_bytes = 0;
for _ in 0..iterations {
let encoded = serde_json::to_string(&payload).unwrap_or_default();
total_bytes += encoded.len();
}
let duration = start.elapsed();
let messages_per_second = iterations as f64 / duration.as_secs_f64();
let bytes_per_second = total_bytes as f64 / duration.as_secs_f64();
BenchmarkResult {
name: format!("throughput_{}b", message_size),
latency: LatencyStats::from_durations(vec![duration / iterations as u32]),
throughput: ThroughputResult {
messages_per_second,
bytes_per_second,
total_messages: iterations,
total_bytes,
duration,
},
memory: None,
metadata: HashMap::new(),
}
}
fn benchmark_latency(&self, message_size: usize) -> BenchmarkResult {
let payload = generate_payload(message_size);
let iterations = self.config.iterations;
for _ in 0..self.config.warmup_iterations {
let _ = serde_json::to_string(&payload);
}
let mut latencies = Vec::with_capacity(iterations);
let mut total_bytes = 0;
for _ in 0..iterations {
let start = Instant::now();
let encoded = serde_json::to_string(&payload).unwrap_or_default();
latencies.push(start.elapsed());
total_bytes += encoded.len();
}
let total_duration: Duration = latencies.iter().sum();
let messages_per_second = iterations as f64 / total_duration.as_secs_f64();
let bytes_per_second = total_bytes as f64 / total_duration.as_secs_f64();
BenchmarkResult {
name: format!("latency_{}b", message_size),
latency: LatencyStats::from_durations(latencies),
throughput: ThroughputResult {
messages_per_second,
bytes_per_second,
total_messages: iterations,
total_bytes,
duration: total_duration,
},
memory: None,
metadata: HashMap::new(),
}
}
fn run_protocol_comparison(&mut self) {
let payloads = get_realistic_payloads();
for (name, params) in payloads {
let comparison = self.compare_protocols(&name, ¶ms);
self.comparisons.push(comparison);
}
}
fn compare_protocols(&self, name: &str, params: &Value) -> ProtocolComparison {
let size_comparison = compare_sizes_auto("tools/call", params);
let mcp_latency = {
let mut total = Duration::ZERO;
for _ in 0..1000 {
let start = Instant::now();
let _ = serde_json::to_string(params);
total += start.elapsed();
}
total / 1000
};
let dcp_latency = {
let mut total = Duration::ZERO;
for _ in 0..1000 {
let start = Instant::now();
let _ = simulate_binary_encode(params);
total += start.elapsed();
}
total / 1000
};
let latency_ratio = if dcp_latency.as_nanos() > 0 {
mcp_latency.as_nanos() as f64 / dcp_latency.as_nanos() as f64
} else {
1.0
};
ProtocolComparison {
payload_name: name.to_string(),
mcp_size: size_comparison.json_rpc_size,
dcp_size: size_comparison.dcp_size,
size_ratio: size_comparison.ratio,
mcp_encode_latency: mcp_latency,
dcp_encode_latency: dcp_latency,
latency_ratio,
}
}
}
fn generate_payload(target_size: usize) -> Value {
let base = json!({
"jsonrpc": "2.0",
"method": "tools/call",
"id": 1,
"params": {
"name": "test_tool"
}
});
let base_size = serde_json::to_string(&base).unwrap_or_default().len();
if target_size <= base_size {
return base;
}
let padding_size = target_size - base_size - 20; let padding = "x".repeat(padding_size.max(0));
json!({
"jsonrpc": "2.0",
"method": "tools/call",
"id": 1,
"params": {
"name": "test_tool",
"data": padding
}
})
}
fn get_realistic_payloads() -> Vec<(String, Value)> {
vec![
(
"simple_tool_call".to_string(),
json!({
"name": "read_file",
"arguments": {
"path": "/home/user/project/src/main.rs"
}
}),
),
(
"tool_with_options".to_string(),
json!({
"name": "search",
"arguments": {
"query": "function definition",
"path": "/home/user/project",
"include": ["*.rs", "*.ts"],
"exclude": ["target", "node_modules"],
"caseSensitive": false,
"maxResults": 100
}
}),
),
(
"code_execution".to_string(),
json!({
"name": "execute_code",
"arguments": {
"language": "python",
"code": "def fibonacci(n):\n if n <= 1:\n return n\n return fibonacci(n-1) + fibonacci(n-2)\n\nprint(fibonacci(10))",
"timeout": 30000
}
}),
),
(
"large_content".to_string(),
json!({
"name": "write_file",
"arguments": {
"path": "/home/user/project/output.txt",
"content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(100)
}
}),
),
(
"structured_data".to_string(),
json!({
"name": "create_resource",
"arguments": {
"type": "database",
"config": {
"host": "localhost",
"port": 5432,
"database": "myapp",
"user": "admin",
"ssl": true,
"poolSize": 10,
"timeout": 30000,
"retries": 3
}
}
}),
),
]
}
fn simulate_binary_encode(value: &Value) -> Vec<u8> {
let json_str = serde_json::to_string(value).unwrap_or_default();
let compressed_size = (json_str.len() as f64 * 0.6) as usize;
vec![0u8; compressed_size]
}
fn chrono_lite_timestamp() -> String {
use std::time::SystemTime;
let duration = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default();
format!("{}s since epoch", duration.as_secs())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_latency_stats() {
let durations: Vec<Duration> = (1..=100).map(|i| Duration::from_micros(i * 10)).collect();
let stats = LatencyStats::from_durations(durations);
assert_eq!(stats.min, Duration::from_micros(10));
assert_eq!(stats.max, Duration::from_micros(1000));
assert_eq!(stats.p50, Duration::from_micros(510));
}
#[test]
fn test_benchmark_config_default() {
let config = BenchmarkConfig::default();
assert_eq!(config.iterations, 10000);
assert!(!config.message_sizes.is_empty());
}
#[test]
fn test_generate_payload() {
let payload = generate_payload(256);
let size = serde_json::to_string(&payload).unwrap().len();
assert!(size >= 200 && size <= 300);
}
#[test]
fn test_benchmark_suite_runs() {
let config = BenchmarkConfig {
iterations: 100,
warmup_iterations: 10,
message_sizes: vec![64],
concurrency_levels: vec![1],
include_comparison: true,
};
let mut suite = BenchmarkSuite::new(config);
let results = suite.run();
assert!(!results.results.is_empty());
assert!(!results.comparisons.is_empty());
}
#[test]
fn test_results_to_json() {
let results = BenchmarkResults {
config: BenchmarkConfig::default(),
results: vec![],
comparisons: vec![],
timestamp: "test".to_string(),
total_duration: Duration::from_secs(1),
};
let json = results.to_json();
assert!(json.contains("config"));
assert!(json.contains("results"));
}
#[test]
fn test_results_to_markdown() {
let results = BenchmarkResults {
config: BenchmarkConfig::default(),
results: vec![],
comparisons: vec![],
timestamp: "test".to_string(),
total_duration: Duration::from_secs(1),
};
let md = results.to_markdown();
assert!(md.contains("# DCP Benchmark Results"));
assert!(md.contains("## Configuration"));
}
}