anya_core/testing/
performance.rs1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::time::Instant;
4use thiserror::Error;
10
11pub mod cache;
13pub mod database;
14pub mod runner;
15pub mod transaction;
16
17#[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
39pub type Result<T> = std::result::Result<T, PerfTestError>;
41
42#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
44pub enum MetricType {
45 TPS,
47
48 LatencyMs,
50
51 MemoryMB,
53
54 CpuPercent,
56
57 DbOpsPerSecond,
59
60 CacheHitRate,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct TestResult {
67 pub name: String,
69
70 pub timestamp: String,
72
73 pub duration_ms: u64,
75
76 pub metrics: HashMap<String, f64>,
78
79 pub metric_types: HashMap<String, MetricType>,
81
82 pub parameters: HashMap<String, String>,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct TestConfig {
89 pub name: String,
91
92 pub iterations: usize,
94
95 pub warmup_iterations: usize,
97
98 pub duration_limit_secs: u64,
100
101 pub parameters: HashMap<String, String>,
103}
104
105pub trait PerformanceTestable {
107 fn run_test(&self, config: &TestConfig) -> Result<TestResult>;
109
110 fn name(&self) -> &str;
112}
113
114pub struct PerformanceTestRunner {
116 configs: Vec<TestConfig>,
118
119 components: Vec<Box<dyn PerformanceTestable>>,
121
122 results: Vec<TestResult>,
124}
125
126impl Default for PerformanceTestRunner {
127 fn default() -> Self {
128 Self::new()
129 }
130}
131
132impl PerformanceTestRunner {
133 pub fn new() -> Self {
135 Self {
136 configs: Vec::new(),
137 components: Vec::new(),
138 results: Vec::new(),
139 }
140 }
141
142 pub fn add_config(&mut self, config: TestConfig) {
144 self.configs.push(config);
145 }
146
147 pub fn add_component(&mut self, component: Box<dyn PerformanceTestable>) {
149 self.components.push(component);
150 }
151
152 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 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 pub fn get_results(&self) -> &[TestResult] {
200 &self.results
201 }
202
203 pub fn generate_report_markdown(&self) -> String {
205 let mut markdown = String::new();
206
207 markdown.push_str("# Performance Test Results\n\n");
209
210 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 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
249pub struct Timer {
251 start: Option<Instant>,
253
254 end: Option<Instant>,
256}
257
258impl Default for Timer {
259 fn default() -> Self {
260 Self::new()
261 }
262}
263
264impl Timer {
265 pub fn new() -> Self {
267 Self {
268 start: None,
269 end: None,
270 }
271 }
272
273 pub fn start(&mut self) {
275 self.start = Some(Instant::now());
276 self.end = None;
277 }
278
279 pub fn stop(&mut self) {
281 self.end = Some(Instant::now());
282 }
283
284 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 pub fn elapsed_secs(&self) -> Result<f64> {
297 Ok(self.elapsed_ms()? as f64 / 1000.0)
298 }
299}