ipfrs_storage/
exporters.rs

1//! Metric exporters for integration with monitoring systems
2//!
3//! Provides exporters for various formats: JSON, CSV, Prometheus, and custom formats
4//! for easy integration with monitoring and observability platforms.
5
6use crate::analyzer::StorageAnalysis;
7use crate::diagnostics::DiagnosticsReport;
8use crate::metrics::StorageMetrics;
9use serde_json;
10use std::fmt::Write as FmtWrite;
11
12/// Export format
13#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum ExportFormat {
15    /// JSON format
16    Json,
17    /// CSV format
18    Csv,
19    /// Prometheus metrics format
20    Prometheus,
21    /// Human-readable text format
22    Text,
23}
24
25/// Metric exporter
26pub struct MetricExporter;
27
28impl MetricExporter {
29    /// Export storage metrics to specified format
30    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    /// Export diagnostics report
40    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    /// Export storage analysis
50    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    // JSON exports
60    fn export_metrics_json(metrics: &StorageMetrics) -> String {
61        serde_json::to_string_pretty(metrics).unwrap_or_default()
62    }
63
64    // CSV exports
65    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    // Prometheus format exports
144    fn export_metrics_prometheus(metrics: &StorageMetrics) -> String {
145        let mut prom = String::new();
146
147        // Counters
148        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        // Gauges
185        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    // Text format exports
295    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
384/// Batch exporter for exporting multiple metrics at once
385pub struct BatchExporter {
386    exports: Vec<(String, String)>,
387}
388
389impl BatchExporter {
390    /// Create a new batch exporter
391    pub fn new() -> Self {
392        Self {
393            exports: Vec::new(),
394        }
395    }
396
397    /// Add metrics to export
398    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    /// Add diagnostics to export
404    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    /// Get all exports
415    pub fn get_exports(&self) -> &[(String, String)] {
416        &self.exports
417    }
418
419    /// Export all as a single document
420    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}