anya_core/testing/
performance.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::time::Instant;
4/// Performance Testing Framework [BPC-3]
5///
6/// This module provides tools for performance testing of the Bitcoin
7/// implementation, including transaction throughput benchmarking,
8/// database access pattern analysis, and cache optimization.
9use thiserror::Error;
10
11// Module exports
12pub mod cache;
13pub mod database;
14pub mod runner;
15pub mod transaction;
16
17/// Error types for performance testing
18#[derive(Debug, Error)]
19pub enum PerfTestError {
20    #[error("Test error: {0}")]
21    TestError(String),
22
23    #[error("Configuration error: {0}")]
24    ConfigurationError(String),
25
26    #[error("Measurement error: {0}")]
27    MeasurementError(String),
28
29    #[error("Database error: {0}")]
30    DatabaseError(String),
31
32    #[error("Bitcoin error: {0}")]
33    BitcoinError(String),
34
35    #[error("I/O error: {0}")]
36    IoError(#[from] std::io::Error),
37}
38
39/// Result type for performance operations
40pub type Result<T> = std::result::Result<T, PerfTestError>;
41
42/// Performance metric type
43#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
44pub enum MetricType {
45    /// Transactions per second
46    TPS,
47
48    /// Milliseconds per operation
49    LatencyMs,
50
51    /// Memory usage in megabytes
52    MemoryMB,
53
54    /// CPU usage percentage
55    CpuPercent,
56
57    /// Database operations per second
58    DbOpsPerSecond,
59
60    /// Cache hit rate percentage
61    CacheHitRate,
62}
63
64/// Performance test result
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct TestResult {
67    /// Test name
68    pub name: String,
69
70    /// Test timestamp
71    pub timestamp: String,
72
73    /// Duration in milliseconds
74    pub duration_ms: u64,
75
76    /// Metrics collected
77    pub metrics: HashMap<String, f64>,
78
79    /// Metric types
80    pub metric_types: HashMap<String, MetricType>,
81
82    /// Configuration parameters
83    pub parameters: HashMap<String, String>,
84}
85
86/// Performance test configuration
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct TestConfig {
89    /// Test name
90    pub name: String,
91
92    /// Number of iterations
93    pub iterations: usize,
94
95    /// Warmup iterations
96    pub warmup_iterations: usize,
97
98    /// Test duration limit in seconds
99    pub duration_limit_secs: u64,
100
101    /// Configuration parameters
102    pub parameters: HashMap<String, String>,
103}
104
105/// Trait for performance testable components
106pub trait PerformanceTestable {
107    /// Run a performance test
108    fn run_test(&self, config: &TestConfig) -> Result<TestResult>;
109
110    /// Get the name of the component
111    fn name(&self) -> &str;
112}
113
114/// Performance test runner
115pub struct PerformanceTestRunner {
116    /// Test configurations
117    configs: Vec<TestConfig>,
118
119    /// Test components
120    components: Vec<Box<dyn PerformanceTestable>>,
121
122    /// Results
123    results: Vec<TestResult>,
124}
125
126impl Default for PerformanceTestRunner {
127    fn default() -> Self {
128        Self::new()
129    }
130}
131
132impl PerformanceTestRunner {
133    /// Create a new performance test runner
134    pub fn new() -> Self {
135        Self {
136            configs: Vec::new(),
137            components: Vec::new(),
138            results: Vec::new(),
139        }
140    }
141
142    /// Add a test configuration
143    pub fn add_config(&mut self, config: TestConfig) {
144        self.configs.push(config);
145    }
146
147    /// Add a testable component
148    pub fn add_component(&mut self, component: Box<dyn PerformanceTestable>) {
149        self.components.push(component);
150    }
151
152    /// Run all tests
153    pub fn run_all_tests(&mut self) -> Result<()> {
154        for config in &self.configs {
155            for component in &self.components {
156                if component.name() == config.name || config.name == "all" {
157                    println!(
158                        "Running test: {} on component: {}",
159                        config.name,
160                        component.name()
161                    );
162                    let result = component.run_test(config)?;
163                    self.results.push(result);
164                }
165            }
166        }
167
168        Ok(())
169    }
170
171    /// Run a specific test
172    pub fn run_test(&mut self, test_name: &str) -> Result<()> {
173        let config = self
174            .configs
175            .iter()
176            .find(|c| c.name == test_name)
177            .ok_or_else(|| {
178                PerfTestError::ConfigurationError(format!(
179                    "Test configuration not found: {test_name}"
180                ))
181            })?;
182
183        for component in &self.components {
184            if component.name() == config.name || config.name == "all" {
185                println!(
186                    "Running test: {} on component: {}",
187                    config.name,
188                    component.name()
189                );
190                let result = component.run_test(config)?;
191                self.results.push(result);
192            }
193        }
194
195        Ok(())
196    }
197
198    /// Get all results
199    pub fn get_results(&self) -> &[TestResult] {
200        &self.results
201    }
202
203    /// Generate a report as markdown
204    pub fn generate_report_markdown(&self) -> String {
205        let mut markdown = String::new();
206
207        // Title
208        markdown.push_str("# Performance Test Results\n\n");
209
210        // Metadata
211        markdown.push_str(&format!(
212            "- **Date:** {}\n",
213            chrono::Utc::now().to_rfc3339()
214        ));
215        markdown.push_str(&format!("- **Total Tests:** {}\n\n", self.results.len()));
216
217        // Results
218        for result in &self.results {
219            markdown.push_str(&format!("## Test: {}\n\n", result.name));
220            markdown.push_str(&format!("- **Duration:** {} ms\n", result.duration_ms));
221            markdown.push_str("- **Metrics:**\n");
222
223            for (name, value) in &result.metrics {
224                let type_str = match result.metric_types.get(name) {
225                    Some(MetricType::TPS) => "TPS",
226                    Some(MetricType::LatencyMs) => "ms",
227                    Some(MetricType::MemoryMB) => "MB",
228                    Some(MetricType::CpuPercent) => "%",
229                    Some(MetricType::DbOpsPerSecond) => "ops/s",
230                    Some(MetricType::CacheHitRate) => "%",
231                    None => "",
232                };
233
234                markdown.push_str(&format!("  - **{name}:** {value:.2} {type_str}\n"));
235            }
236
237            markdown.push_str("- **Parameters:**\n");
238            for (name, value) in &result.parameters {
239                markdown.push_str(&format!("  - **{name}:** {value}\n"));
240            }
241
242            markdown.push('\n');
243        }
244
245        markdown
246    }
247}
248
249/// Basic timer utility for measuring performance
250pub struct Timer {
251    /// Start time
252    start: Option<Instant>,
253
254    /// End time
255    end: Option<Instant>,
256}
257
258impl Default for Timer {
259    fn default() -> Self {
260        Self::new()
261    }
262}
263
264impl Timer {
265    /// Create a new timer
266    pub fn new() -> Self {
267        Self {
268            start: None,
269            end: None,
270        }
271    }
272
273    /// Start the timer
274    pub fn start(&mut self) {
275        self.start = Some(Instant::now());
276        self.end = None;
277    }
278
279    /// Stop the timer
280    pub fn stop(&mut self) {
281        self.end = Some(Instant::now());
282    }
283
284    /// Get the elapsed time in milliseconds
285    pub fn elapsed_ms(&self) -> Result<u64> {
286        match (self.start, self.end) {
287            (Some(start), Some(end)) => Ok(end.duration_since(start).as_millis() as u64),
288            (Some(start), None) => Ok(Instant::now().duration_since(start).as_millis() as u64),
289            _ => Err(PerfTestError::MeasurementError(
290                "Timer not started".to_string(),
291            )),
292        }
293    }
294
295    /// Get the elapsed time in seconds
296    pub fn elapsed_secs(&self) -> Result<f64> {
297        Ok(self.elapsed_ms()? as f64 / 1000.0)
298    }
299}