1use crate::analyzer::StorageAnalysis;
7use crate::diagnostics::DiagnosticsReport;
8use crate::metrics::StorageMetrics;
9use serde_json;
10use std::fmt::Write as FmtWrite;
11
12#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum ExportFormat {
15 Json,
17 Csv,
19 Prometheus,
21 Text,
23}
24
25pub struct MetricExporter;
27
28impl MetricExporter {
29 pub fn export_metrics(metrics: &StorageMetrics, format: ExportFormat) -> String {
31 match format {
32 ExportFormat::Json => Self::export_metrics_json(metrics),
33 ExportFormat::Csv => Self::export_metrics_csv(metrics),
34 ExportFormat::Prometheus => Self::export_metrics_prometheus(metrics),
35 ExportFormat::Text => Self::export_metrics_text(metrics),
36 }
37 }
38
39 pub fn export_diagnostics(report: &DiagnosticsReport, format: ExportFormat) -> String {
41 match format {
42 ExportFormat::Json => serde_json::to_string_pretty(report).unwrap_or_default(),
43 ExportFormat::Csv => Self::export_diagnostics_csv(report),
44 ExportFormat::Prometheus => Self::export_diagnostics_prometheus(report),
45 ExportFormat::Text => Self::export_diagnostics_text(report),
46 }
47 }
48
49 pub fn export_analysis(analysis: &StorageAnalysis, format: ExportFormat) -> String {
51 match format {
52 ExportFormat::Json => serde_json::to_string_pretty(analysis).unwrap_or_default(),
53 ExportFormat::Csv => Self::export_analysis_csv(analysis),
54 ExportFormat::Prometheus => Self::export_analysis_prometheus(analysis),
55 ExportFormat::Text => Self::export_analysis_text(analysis),
56 }
57 }
58
59 fn export_metrics_json(metrics: &StorageMetrics) -> String {
61 serde_json::to_string_pretty(metrics).unwrap_or_default()
62 }
63
64 fn export_metrics_csv(metrics: &StorageMetrics) -> String {
66 let mut csv = String::new();
67 csv.push_str("metric,value\n");
68 csv.push_str(&format!("put_count,{}\n", metrics.put_count));
69 csv.push_str(&format!("get_count,{}\n", metrics.get_count));
70 csv.push_str(&format!("has_count,{}\n", metrics.has_count));
71 csv.push_str(&format!("delete_count,{}\n", metrics.delete_count));
72 csv.push_str(&format!("get_hits,{}\n", metrics.get_hits));
73 csv.push_str(&format!("get_misses,{}\n", metrics.get_misses));
74 csv.push_str(&format!("bytes_written,{}\n", metrics.bytes_written));
75 csv.push_str(&format!("bytes_read,{}\n", metrics.bytes_read));
76 csv.push_str(&format!(
77 "avg_put_latency_us,{}\n",
78 metrics.avg_put_latency_us
79 ));
80 csv.push_str(&format!(
81 "avg_get_latency_us,{}\n",
82 metrics.avg_get_latency_us
83 ));
84 csv.push_str(&format!("cache_hit_rate,{:.4}\n", metrics.cache_hit_rate()));
85 csv
86 }
87
88 fn export_diagnostics_csv(report: &DiagnosticsReport) -> String {
89 let mut csv = String::new();
90 csv.push_str("metric,value\n");
91 csv.push_str(&format!("backend,{}\n", report.backend));
92 csv.push_str(&format!("total_blocks,{}\n", report.total_blocks));
93 csv.push_str(&format!("health_score,{}\n", report.health_score));
94 csv.push_str(&format!(
95 "write_throughput,{:.2}\n",
96 report.performance.write_throughput
97 ));
98 csv.push_str(&format!(
99 "read_throughput,{:.2}\n",
100 report.performance.read_throughput
101 ));
102 csv.push_str(&format!(
103 "peak_memory_usage,{}\n",
104 report.performance.peak_memory_usage
105 ));
106 csv.push_str(&format!(
107 "successful_ops,{}\n",
108 report.health.successful_ops
109 ));
110 csv.push_str(&format!("failed_ops,{}\n", report.health.failed_ops));
111 csv.push_str(&format!("success_rate,{:.4}\n", report.health.success_rate));
112 csv
113 }
114
115 fn export_analysis_csv(analysis: &StorageAnalysis) -> String {
116 let mut csv = String::new();
117 csv.push_str("metric,value\n");
118 csv.push_str(&format!("backend,{}\n", analysis.backend));
119 csv.push_str(&format!("grade,{}\n", analysis.grade));
120 csv.push_str(&format!(
121 "health_score,{}\n",
122 analysis.diagnostics.health_score
123 ));
124 csv.push_str(&format!(
125 "read_write_ratio,{:.4}\n",
126 analysis.workload.read_write_ratio
127 ));
128 csv.push_str(&format!(
129 "avg_block_size,{}\n",
130 analysis.workload.avg_block_size
131 ));
132 csv.push_str(&format!(
133 "workload_type,{:?}\n",
134 analysis.workload.workload_type
135 ));
136 csv.push_str(&format!(
137 "recommendation_count,{}\n",
138 analysis.recommendations.len()
139 ));
140 csv
141 }
142
143 fn export_metrics_prometheus(metrics: &StorageMetrics) -> String {
145 let mut prom = String::new();
146
147 let _ = writeln!(
149 prom,
150 "# HELP ipfrs_storage_put_total Total number of put operations"
151 );
152 let _ = writeln!(prom, "# TYPE ipfrs_storage_put_total counter");
153 let _ = writeln!(prom, "ipfrs_storage_put_total {}", metrics.put_count);
154
155 let _ = writeln!(
156 prom,
157 "# HELP ipfrs_storage_get_total Total number of get operations"
158 );
159 let _ = writeln!(prom, "# TYPE ipfrs_storage_get_total counter");
160 let _ = writeln!(prom, "ipfrs_storage_get_total {}", metrics.get_count);
161
162 let _ = writeln!(
163 prom,
164 "# HELP ipfrs_storage_bytes_written_total Total bytes written"
165 );
166 let _ = writeln!(prom, "# TYPE ipfrs_storage_bytes_written_total counter");
167 let _ = writeln!(
168 prom,
169 "ipfrs_storage_bytes_written_total {}",
170 metrics.bytes_written
171 );
172
173 let _ = writeln!(
174 prom,
175 "# HELP ipfrs_storage_bytes_read_total Total bytes read"
176 );
177 let _ = writeln!(prom, "# TYPE ipfrs_storage_bytes_read_total counter");
178 let _ = writeln!(
179 prom,
180 "ipfrs_storage_bytes_read_total {}",
181 metrics.bytes_read
182 );
183
184 let _ = writeln!(
186 prom,
187 "# HELP ipfrs_storage_avg_put_latency_us Average put latency in microseconds"
188 );
189 let _ = writeln!(prom, "# TYPE ipfrs_storage_avg_put_latency_us gauge");
190 let _ = writeln!(
191 prom,
192 "ipfrs_storage_avg_put_latency_us {}",
193 metrics.avg_put_latency_us
194 );
195
196 let _ = writeln!(
197 prom,
198 "# HELP ipfrs_storage_cache_hit_rate Cache hit rate (0-1)"
199 );
200 let _ = writeln!(prom, "# TYPE ipfrs_storage_cache_hit_rate gauge");
201 let _ = writeln!(
202 prom,
203 "ipfrs_storage_cache_hit_rate {:.4}",
204 metrics.cache_hit_rate()
205 );
206
207 prom
208 }
209
210 fn export_diagnostics_prometheus(report: &DiagnosticsReport) -> String {
211 let mut prom = String::new();
212
213 let _ = writeln!(
214 prom,
215 "# HELP ipfrs_storage_health_score Storage health score (0-100)"
216 );
217 let _ = writeln!(prom, "# TYPE ipfrs_storage_health_score gauge");
218 let _ = writeln!(
219 prom,
220 "ipfrs_storage_health_score{{backend=\"{}\"}} {}",
221 report.backend, report.health_score
222 );
223
224 let _ = writeln!(
225 prom,
226 "# HELP ipfrs_storage_write_throughput Write throughput in blocks/sec"
227 );
228 let _ = writeln!(prom, "# TYPE ipfrs_storage_write_throughput gauge");
229 let _ = writeln!(
230 prom,
231 "ipfrs_storage_write_throughput{{backend=\"{}\"}} {:.2}",
232 report.backend, report.performance.write_throughput
233 );
234
235 let _ = writeln!(
236 prom,
237 "# HELP ipfrs_storage_read_throughput Read throughput in blocks/sec"
238 );
239 let _ = writeln!(prom, "# TYPE ipfrs_storage_read_throughput gauge");
240 let _ = writeln!(
241 prom,
242 "ipfrs_storage_read_throughput{{backend=\"{}\"}} {:.2}",
243 report.backend, report.performance.read_throughput
244 );
245
246 let _ = writeln!(
247 prom,
248 "# HELP ipfrs_storage_peak_memory_usage Peak memory usage in bytes"
249 );
250 let _ = writeln!(prom, "# TYPE ipfrs_storage_peak_memory_usage gauge");
251 let _ = writeln!(
252 prom,
253 "ipfrs_storage_peak_memory_usage{{backend=\"{}\"}} {}",
254 report.backend, report.performance.peak_memory_usage
255 );
256
257 prom
258 }
259
260 fn export_analysis_prometheus(analysis: &StorageAnalysis) -> String {
261 let mut prom = String::new();
262
263 let grade_score = match analysis.grade.as_str() {
264 "A" => 5,
265 "B" => 4,
266 "C" => 3,
267 "D" => 2,
268 _ => 1,
269 };
270
271 let _ = writeln!(prom, "# HELP ipfrs_storage_grade Storage grade (1-5)");
272 let _ = writeln!(prom, "# TYPE ipfrs_storage_grade gauge");
273 let _ = writeln!(
274 prom,
275 "ipfrs_storage_grade{{backend=\"{}\"}} {}",
276 analysis.backend, grade_score
277 );
278
279 let _ = writeln!(
280 prom,
281 "# HELP ipfrs_storage_recommendation_count Number of recommendations"
282 );
283 let _ = writeln!(prom, "# TYPE ipfrs_storage_recommendation_count gauge");
284 let _ = writeln!(
285 prom,
286 "ipfrs_storage_recommendation_count{{backend=\"{}\"}} {}",
287 analysis.backend,
288 analysis.recommendations.len()
289 );
290
291 prom
292 }
293
294 fn export_metrics_text(metrics: &StorageMetrics) -> String {
296 format!(
297 "Storage Metrics:\n\
298 Put Operations: {}\n\
299 Get Operations: {}\n\
300 Has Operations: {}\n\
301 Delete Operations: {}\n\
302 Cache Hits: {}\n\
303 Cache Misses: {}\n\
304 Cache Hit Rate: {:.2}%\n\
305 Bytes Written: {}\n\
306 Bytes Read: {}\n\
307 Avg Put Latency: {}μs\n\
308 Avg Get Latency: {}μs\n\
309 Avg Has Latency: {}μs\n\
310 Peak Put Latency: {}μs\n\
311 Peak Get Latency: {}μs\n\
312 Errors: {}\n",
313 metrics.put_count,
314 metrics.get_count,
315 metrics.has_count,
316 metrics.delete_count,
317 metrics.get_hits,
318 metrics.get_misses,
319 metrics.cache_hit_rate() * 100.0,
320 metrics.bytes_written,
321 metrics.bytes_read,
322 metrics.avg_put_latency_us,
323 metrics.avg_get_latency_us,
324 metrics.avg_has_latency_us,
325 metrics.peak_put_latency_us,
326 metrics.peak_get_latency_us,
327 metrics.error_count,
328 )
329 }
330
331 fn export_diagnostics_text(report: &DiagnosticsReport) -> String {
332 format!(
333 "=== Diagnostics Report: {} ===\n\
334 Total Blocks: {}\n\
335 Health Score: {}/100\n\n\
336 Performance:\n\
337 - Write Throughput: {:.2} blocks/sec\n\
338 - Read Throughput: {:.2} blocks/sec\n\
339 - Avg Write Latency: {:?}\n\
340 - Avg Read Latency: {:?}\n\
341 - Peak Memory Usage: {:.2} MB\n\n\
342 Health:\n\
343 - Successful Ops: {}\n\
344 - Failed Ops: {}\n\
345 - Success Rate: {:.2}%\n\
346 - Integrity OK: {}\n\
347 - Responsive: {}\n",
348 report.backend,
349 report.total_blocks,
350 report.health_score,
351 report.performance.write_throughput,
352 report.performance.read_throughput,
353 report.performance.avg_write_latency,
354 report.performance.avg_read_latency,
355 report.performance.peak_memory_usage as f64 / (1024.0 * 1024.0),
356 report.health.successful_ops,
357 report.health.failed_ops,
358 report.health.success_rate * 100.0,
359 report.health.integrity_ok,
360 report.health.responsive,
361 )
362 }
363
364 fn export_analysis_text(analysis: &StorageAnalysis) -> String {
365 format!(
366 "=== Storage Analysis: {} ===\n\
367 Grade: {}\n\
368 Health Score: {}/100\n\
369 Workload Type: {:?}\n\
370 Read/Write Ratio: {:.2}% reads\n\
371 Avg Block Size: {} bytes\n\
372 Recommendations: {}\n",
373 analysis.backend,
374 analysis.grade,
375 analysis.diagnostics.health_score,
376 analysis.workload.workload_type,
377 analysis.workload.read_write_ratio * 100.0,
378 analysis.workload.avg_block_size,
379 analysis.recommendations.len(),
380 )
381 }
382}
383
384pub struct BatchExporter {
386 exports: Vec<(String, String)>,
387}
388
389impl BatchExporter {
390 pub fn new() -> Self {
392 Self {
393 exports: Vec::new(),
394 }
395 }
396
397 pub fn add_metrics(&mut self, name: &str, metrics: &StorageMetrics, format: ExportFormat) {
399 let exported = MetricExporter::export_metrics(metrics, format);
400 self.exports.push((name.to_string(), exported));
401 }
402
403 pub fn add_diagnostics(
405 &mut self,
406 name: &str,
407 report: &DiagnosticsReport,
408 format: ExportFormat,
409 ) {
410 let exported = MetricExporter::export_diagnostics(report, format);
411 self.exports.push((name.to_string(), exported));
412 }
413
414 pub fn get_exports(&self) -> &[(String, String)] {
416 &self.exports
417 }
418
419 pub fn export_all(&self) -> String {
421 let mut result = String::new();
422 for (name, content) in &self.exports {
423 result.push_str(&format!("=== {name} ===\n"));
424 result.push_str(content);
425 result.push_str("\n\n");
426 }
427 result
428 }
429}
430
431impl Default for BatchExporter {
432 fn default() -> Self {
433 Self::new()
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440
441 fn sample_metrics() -> StorageMetrics {
442 StorageMetrics {
443 put_count: 1000,
444 get_count: 2000,
445 has_count: 500,
446 delete_count: 100,
447 get_hits: 1800,
448 get_misses: 200,
449 bytes_written: 1024000,
450 bytes_read: 2048000,
451 avg_put_latency_us: 100,
452 avg_get_latency_us: 50,
453 avg_has_latency_us: 25,
454 peak_put_latency_us: 500,
455 peak_get_latency_us: 200,
456 error_count: 10,
457 batch_op_count: 50,
458 batch_items_count: 500,
459 avg_batch_size: 10,
460 }
461 }
462
463 #[test]
464 fn test_export_metrics_json() {
465 let metrics = sample_metrics();
466 let exported = MetricExporter::export_metrics(&metrics, ExportFormat::Json);
467 assert!(exported.contains("put_count"));
468 assert!(exported.contains("1000"));
469 }
470
471 #[test]
472 fn test_export_metrics_csv() {
473 let metrics = sample_metrics();
474 let exported = MetricExporter::export_metrics(&metrics, ExportFormat::Csv);
475 assert!(exported.contains("metric,value"));
476 assert!(exported.contains("put_count,1000"));
477 }
478
479 #[test]
480 fn test_export_metrics_prometheus() {
481 let metrics = sample_metrics();
482 let exported = MetricExporter::export_metrics(&metrics, ExportFormat::Prometheus);
483 assert!(exported.contains("# HELP"));
484 assert!(exported.contains("# TYPE"));
485 assert!(exported.contains("ipfrs_storage_put_total"));
486 }
487
488 #[test]
489 fn test_export_metrics_text() {
490 let metrics = sample_metrics();
491 let exported = MetricExporter::export_metrics(&metrics, ExportFormat::Text);
492 assert!(exported.contains("Storage Metrics"));
493 assert!(exported.contains("Put Operations: 1000"));
494 }
495
496 #[test]
497 fn test_batch_exporter() {
498 let mut exporter = BatchExporter::new();
499 let metrics = sample_metrics();
500
501 exporter.add_metrics("test1", &metrics, ExportFormat::Json);
502 exporter.add_metrics("test2", &metrics, ExportFormat::Csv);
503
504 assert_eq!(exporter.get_exports().len(), 2);
505
506 let all = exporter.export_all();
507 assert!(all.contains("=== test1 ==="));
508 assert!(all.contains("=== test2 ==="));
509 }
510}