1use serde::{Deserialize, Serialize};
4use serde_json::{json, Value};
5use std::collections::HashMap;
6use std::time::{Duration, Instant};
7
8use super::json_rpc::compare_sizes_auto;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct BenchmarkConfig {
13 pub iterations: usize,
15 pub warmup_iterations: usize,
17 pub message_sizes: Vec<usize>,
19 pub concurrency_levels: Vec<usize>,
21 pub include_comparison: bool,
23}
24
25impl Default for BenchmarkConfig {
26 fn default() -> Self {
27 Self {
28 iterations: 10000,
29 warmup_iterations: 1000,
30 message_sizes: vec![64, 256, 1024, 4096, 16384],
31 concurrency_levels: vec![1, 10, 50, 100],
32 include_comparison: true,
33 }
34 }
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct LatencyStats {
40 pub min: Duration,
42 pub max: Duration,
44 pub mean: Duration,
46 pub p50: Duration,
48 pub p95: Duration,
50 pub p99: Duration,
52 pub std_dev: Duration,
54}
55
56impl LatencyStats {
57 pub fn from_durations(mut durations: Vec<Duration>) -> Self {
59 if durations.is_empty() {
60 return Self {
61 min: Duration::ZERO,
62 max: Duration::ZERO,
63 mean: Duration::ZERO,
64 p50: Duration::ZERO,
65 p95: Duration::ZERO,
66 p99: Duration::ZERO,
67 std_dev: Duration::ZERO,
68 };
69 }
70
71 durations.sort();
72 let n = durations.len();
73
74 let min = durations[0];
75 let max = durations[n - 1];
76
77 let total: Duration = durations.iter().sum();
78 let mean = total / n as u32;
79
80 let p50 = durations[n / 2];
81 let p95 = durations[(n as f64 * 0.95) as usize];
82 let p99 = durations[(n as f64 * 0.99).min((n - 1) as f64) as usize];
83
84 let mean_nanos = mean.as_nanos() as f64;
86 let variance: f64 = durations
87 .iter()
88 .map(|d| {
89 let diff = d.as_nanos() as f64 - mean_nanos;
90 diff * diff
91 })
92 .sum::<f64>()
93 / n as f64;
94 let std_dev = Duration::from_nanos(variance.sqrt() as u64);
95
96 Self {
97 min,
98 max,
99 mean,
100 p50,
101 p95,
102 p99,
103 std_dev,
104 }
105 }
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct ThroughputResult {
111 pub messages_per_second: f64,
113 pub bytes_per_second: f64,
115 pub total_messages: usize,
117 pub total_bytes: usize,
119 pub duration: Duration,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct MemoryUsage {
126 pub heap_bytes: usize,
128 pub peak_bytes: usize,
130 pub allocation_count: usize,
132}
133
134impl Default for MemoryUsage {
135 fn default() -> Self {
136 Self {
137 heap_bytes: 0,
138 peak_bytes: 0,
139 allocation_count: 0,
140 }
141 }
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct ProtocolComparison {
147 pub payload_name: String,
149 pub mcp_size: usize,
151 pub dcp_size: usize,
153 pub size_ratio: f64,
155 pub mcp_encode_latency: Duration,
157 pub dcp_encode_latency: Duration,
159 pub latency_ratio: f64,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct BenchmarkResult {
166 pub name: String,
168 pub latency: LatencyStats,
170 pub throughput: ThroughputResult,
172 pub memory: Option<MemoryUsage>,
174 pub metadata: HashMap<String, String>,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct BenchmarkResults {
181 pub config: BenchmarkConfig,
183 pub results: Vec<BenchmarkResult>,
185 pub comparisons: Vec<ProtocolComparison>,
187 pub timestamp: String,
189 pub total_duration: Duration,
191}
192
193impl BenchmarkResults {
194 pub fn to_json(&self) -> String {
196 serde_json::to_string_pretty(self).unwrap_or_default()
197 }
198
199 pub fn to_markdown(&self) -> String {
201 let mut md = String::new();
202
203 md.push_str("# DCP Benchmark Results\n\n");
204 md.push_str(&format!("Generated: {}\n\n", self.timestamp));
205 md.push_str(&format!(
206 "Total Duration: {:.2}s\n\n",
207 self.total_duration.as_secs_f64()
208 ));
209
210 md.push_str("## Configuration\n\n");
212 md.push_str(&format!("- Iterations: {}\n", self.config.iterations));
213 md.push_str(&format!(
214 "- Warmup Iterations: {}\n",
215 self.config.warmup_iterations
216 ));
217 md.push_str(&format!(
218 "- Message Sizes: {:?}\n",
219 self.config.message_sizes
220 ));
221 md.push_str(&format!(
222 "- Concurrency Levels: {:?}\n\n",
223 self.config.concurrency_levels
224 ));
225
226 md.push_str("## Latency Results\n\n");
228 md.push_str("| Benchmark | Min | Mean | P50 | P95 | P99 | Max |\n");
229 md.push_str("|-----------|-----|------|-----|-----|-----|-----|\n");
230
231 for result in &self.results {
232 md.push_str(&format!(
233 "| {} | {:?} | {:?} | {:?} | {:?} | {:?} | {:?} |\n",
234 result.name,
235 result.latency.min,
236 result.latency.mean,
237 result.latency.p50,
238 result.latency.p95,
239 result.latency.p99,
240 result.latency.max
241 ));
242 }
243 md.push('\n');
244
245 md.push_str("## Throughput Results\n\n");
247 md.push_str("| Benchmark | Messages/sec | MB/sec | Total Messages |\n");
248 md.push_str("|-----------|--------------|--------|----------------|\n");
249
250 for result in &self.results {
251 md.push_str(&format!(
252 "| {} | {:.0} | {:.2} | {} |\n",
253 result.name,
254 result.throughput.messages_per_second,
255 result.throughput.bytes_per_second / 1_000_000.0,
256 result.throughput.total_messages
257 ));
258 }
259 md.push('\n');
260
261 if !self.comparisons.is_empty() {
263 md.push_str("## DCP vs MCP Comparison\n\n");
264 md.push_str("| Payload | MCP Size | DCP Size | Size Ratio | MCP Latency | DCP Latency | Latency Ratio |\n");
265 md.push_str("|---------|----------|----------|------------|-------------|-------------|---------------|\n");
266
267 for comp in &self.comparisons {
268 md.push_str(&format!(
269 "| {} | {} B | {} B | {:.2}x | {:?} | {:?} | {:.2}x |\n",
270 comp.payload_name,
271 comp.mcp_size,
272 comp.dcp_size,
273 comp.size_ratio,
274 comp.mcp_encode_latency,
275 comp.dcp_encode_latency,
276 comp.latency_ratio
277 ));
278 }
279 md.push('\n');
280 }
281
282 md
283 }
284}
285
286pub struct BenchmarkSuite {
288 config: BenchmarkConfig,
289 results: Vec<BenchmarkResult>,
290 comparisons: Vec<ProtocolComparison>,
291}
292
293impl BenchmarkSuite {
294 pub fn new(config: BenchmarkConfig) -> Self {
296 Self {
297 config,
298 results: Vec::new(),
299 comparisons: Vec::new(),
300 }
301 }
302
303 pub fn with_defaults() -> Self {
305 Self::new(BenchmarkConfig::default())
306 }
307
308 pub fn run(&mut self) -> BenchmarkResults {
310 let start = Instant::now();
311
312 self.run_throughput_benchmarks();
314
315 self.run_latency_benchmarks();
317
318 if self.config.include_comparison {
320 self.run_protocol_comparison();
321 }
322
323 let total_duration = start.elapsed();
324
325 BenchmarkResults {
326 config: self.config.clone(),
327 results: self.results.clone(),
328 comparisons: self.comparisons.clone(),
329 timestamp: chrono_lite_timestamp(),
330 total_duration,
331 }
332 }
333
334 fn run_throughput_benchmarks(&mut self) {
335 for &size in &self.config.message_sizes.clone() {
336 let result = self.benchmark_throughput(size);
337 self.results.push(result);
338 }
339 }
340
341 fn run_latency_benchmarks(&mut self) {
342 for &size in &self.config.message_sizes.clone() {
343 let result = self.benchmark_latency(size);
344 self.results.push(result);
345 }
346 }
347
348 fn benchmark_throughput(&self, message_size: usize) -> BenchmarkResult {
349 let payload = generate_payload(message_size);
350 let iterations = self.config.iterations;
351
352 for _ in 0..self.config.warmup_iterations {
354 let _ = serde_json::to_string(&payload);
355 }
356
357 let start = Instant::now();
358 let mut total_bytes = 0;
359
360 for _ in 0..iterations {
361 let encoded = serde_json::to_string(&payload).unwrap_or_default();
362 total_bytes += encoded.len();
363 }
364
365 let duration = start.elapsed();
366 let messages_per_second = iterations as f64 / duration.as_secs_f64();
367 let bytes_per_second = total_bytes as f64 / duration.as_secs_f64();
368
369 BenchmarkResult {
370 name: format!("throughput_{}b", message_size),
371 latency: LatencyStats::from_durations(vec![duration / iterations as u32]),
372 throughput: ThroughputResult {
373 messages_per_second,
374 bytes_per_second,
375 total_messages: iterations,
376 total_bytes,
377 duration,
378 },
379 memory: None,
380 metadata: HashMap::new(),
381 }
382 }
383
384 fn benchmark_latency(&self, message_size: usize) -> BenchmarkResult {
385 let payload = generate_payload(message_size);
386 let iterations = self.config.iterations;
387
388 for _ in 0..self.config.warmup_iterations {
390 let _ = serde_json::to_string(&payload);
391 }
392
393 let mut latencies = Vec::with_capacity(iterations);
394 let mut total_bytes = 0;
395
396 for _ in 0..iterations {
397 let start = Instant::now();
398 let encoded = serde_json::to_string(&payload).unwrap_or_default();
399 latencies.push(start.elapsed());
400 total_bytes += encoded.len();
401 }
402
403 let total_duration: Duration = latencies.iter().sum();
404 let messages_per_second = iterations as f64 / total_duration.as_secs_f64();
405 let bytes_per_second = total_bytes as f64 / total_duration.as_secs_f64();
406
407 BenchmarkResult {
408 name: format!("latency_{}b", message_size),
409 latency: LatencyStats::from_durations(latencies),
410 throughput: ThroughputResult {
411 messages_per_second,
412 bytes_per_second,
413 total_messages: iterations,
414 total_bytes,
415 duration: total_duration,
416 },
417 memory: None,
418 metadata: HashMap::new(),
419 }
420 }
421
422 fn run_protocol_comparison(&mut self) {
423 let payloads = get_realistic_payloads();
424
425 for (name, params) in payloads {
426 let comparison = self.compare_protocols(&name, ¶ms);
427 self.comparisons.push(comparison);
428 }
429 }
430
431 fn compare_protocols(&self, name: &str, params: &Value) -> ProtocolComparison {
432 let size_comparison = compare_sizes_auto("tools/call", params);
433
434 let mcp_latency = {
436 let mut total = Duration::ZERO;
437 for _ in 0..1000 {
438 let start = Instant::now();
439 let _ = serde_json::to_string(params);
440 total += start.elapsed();
441 }
442 total / 1000
443 };
444
445 let dcp_latency = {
447 let mut total = Duration::ZERO;
448 for _ in 0..1000 {
449 let start = Instant::now();
450 let _ = simulate_binary_encode(params);
451 total += start.elapsed();
452 }
453 total / 1000
454 };
455
456 let latency_ratio = if dcp_latency.as_nanos() > 0 {
457 mcp_latency.as_nanos() as f64 / dcp_latency.as_nanos() as f64
458 } else {
459 1.0
460 };
461
462 ProtocolComparison {
463 payload_name: name.to_string(),
464 mcp_size: size_comparison.json_rpc_size,
465 dcp_size: size_comparison.dcp_size,
466 size_ratio: size_comparison.ratio,
467 mcp_encode_latency: mcp_latency,
468 dcp_encode_latency: dcp_latency,
469 latency_ratio,
470 }
471 }
472}
473
474fn generate_payload(target_size: usize) -> Value {
476 let base = json!({
477 "jsonrpc": "2.0",
478 "method": "tools/call",
479 "id": 1,
480 "params": {
481 "name": "test_tool"
482 }
483 });
484
485 let base_size = serde_json::to_string(&base).unwrap_or_default().len();
486
487 if target_size <= base_size {
488 return base;
489 }
490
491 let padding_size = target_size - base_size - 20; let padding = "x".repeat(padding_size.max(0));
493
494 json!({
495 "jsonrpc": "2.0",
496 "method": "tools/call",
497 "id": 1,
498 "params": {
499 "name": "test_tool",
500 "data": padding
501 }
502 })
503}
504
505fn get_realistic_payloads() -> Vec<(String, Value)> {
507 vec![
508 (
509 "simple_tool_call".to_string(),
510 json!({
511 "name": "read_file",
512 "arguments": {
513 "path": "/home/user/project/src/main.rs"
514 }
515 }),
516 ),
517 (
518 "tool_with_options".to_string(),
519 json!({
520 "name": "search",
521 "arguments": {
522 "query": "function definition",
523 "path": "/home/user/project",
524 "include": ["*.rs", "*.ts"],
525 "exclude": ["target", "node_modules"],
526 "caseSensitive": false,
527 "maxResults": 100
528 }
529 }),
530 ),
531 (
532 "code_execution".to_string(),
533 json!({
534 "name": "execute_code",
535 "arguments": {
536 "language": "python",
537 "code": "def fibonacci(n):\n if n <= 1:\n return n\n return fibonacci(n-1) + fibonacci(n-2)\n\nprint(fibonacci(10))",
538 "timeout": 30000
539 }
540 }),
541 ),
542 (
543 "large_content".to_string(),
544 json!({
545 "name": "write_file",
546 "arguments": {
547 "path": "/home/user/project/output.txt",
548 "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(100)
549 }
550 }),
551 ),
552 (
553 "structured_data".to_string(),
554 json!({
555 "name": "create_resource",
556 "arguments": {
557 "type": "database",
558 "config": {
559 "host": "localhost",
560 "port": 5432,
561 "database": "myapp",
562 "user": "admin",
563 "ssl": true,
564 "poolSize": 10,
565 "timeout": 30000,
566 "retries": 3
567 }
568 }
569 }),
570 ),
571 ]
572}
573
574fn simulate_binary_encode(value: &Value) -> Vec<u8> {
576 let json_str = serde_json::to_string(value).unwrap_or_default();
578 let compressed_size = (json_str.len() as f64 * 0.6) as usize;
580 vec![0u8; compressed_size]
581}
582
583fn chrono_lite_timestamp() -> String {
585 use std::time::SystemTime;
586 let duration = SystemTime::now()
587 .duration_since(SystemTime::UNIX_EPOCH)
588 .unwrap_or_default();
589 format!("{}s since epoch", duration.as_secs())
590}
591
592#[cfg(test)]
593mod tests {
594 use super::*;
595
596 #[test]
597 fn test_latency_stats() {
598 let durations: Vec<Duration> = (1..=100).map(|i| Duration::from_micros(i * 10)).collect();
599
600 let stats = LatencyStats::from_durations(durations);
601
602 assert_eq!(stats.min, Duration::from_micros(10));
603 assert_eq!(stats.max, Duration::from_micros(1000));
604 assert_eq!(stats.p50, Duration::from_micros(510));
606 }
607
608 #[test]
609 fn test_benchmark_config_default() {
610 let config = BenchmarkConfig::default();
611 assert_eq!(config.iterations, 10000);
612 assert!(!config.message_sizes.is_empty());
613 }
614
615 #[test]
616 fn test_generate_payload() {
617 let payload = generate_payload(256);
618 let size = serde_json::to_string(&payload).unwrap().len();
619 assert!(size >= 200 && size <= 300);
621 }
622
623 #[test]
624 fn test_benchmark_suite_runs() {
625 let config = BenchmarkConfig {
626 iterations: 100,
627 warmup_iterations: 10,
628 message_sizes: vec![64],
629 concurrency_levels: vec![1],
630 include_comparison: true,
631 };
632
633 let mut suite = BenchmarkSuite::new(config);
634 let results = suite.run();
635
636 assert!(!results.results.is_empty());
637 assert!(!results.comparisons.is_empty());
638 }
639
640 #[test]
641 fn test_results_to_json() {
642 let results = BenchmarkResults {
643 config: BenchmarkConfig::default(),
644 results: vec![],
645 comparisons: vec![],
646 timestamp: "test".to_string(),
647 total_duration: Duration::from_secs(1),
648 };
649
650 let json = results.to_json();
651 assert!(json.contains("config"));
652 assert!(json.contains("results"));
653 }
654
655 #[test]
656 fn test_results_to_markdown() {
657 let results = BenchmarkResults {
658 config: BenchmarkConfig::default(),
659 results: vec![],
660 comparisons: vec![],
661 timestamp: "test".to_string(),
662 total_duration: Duration::from_secs(1),
663 };
664
665 let md = results.to_markdown();
666 assert!(md.contains("# DCP Benchmark Results"));
667 assert!(md.contains("## Configuration"));
668 }
669}