use alloc::string::{String, ToString};
use alloc::vec::Vec;
use alloc::{format, vec};
use super::types::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReportFormat {
Text,
Json,
Prometheus,
}
pub fn format_report(report: &FullReport, format: ReportFormat) -> String {
match format {
ReportFormat::Text => format_text_report(report),
ReportFormat::Json => format_json_report(report),
ReportFormat::Prometheus => format_prometheus_report(report),
}
}
fn format_text_report(report: &FullReport) -> String {
let mut lines = Vec::new();
lines.push("=== Storage Analytics Report ===".to_string());
lines.push(format!("Dataset: {}", report.dataset));
lines.push(format!("Health Score: {}/100", report.health_score));
lines.push(String::new());
lines.push("--- Usage Summary ---".to_string());
lines.push(format!(
"Total Capacity: {} bytes",
report.usage.total_capacity
));
lines.push(format!("Used Space: {} bytes", report.usage.used_space));
lines.push(format!("Free Space: {} bytes", report.usage.free_space));
lines.push(format!("Usage: {:.1}%", report.usage.usage_percent));
lines.push(format!("Files: {}", report.usage.file_count));
lines.push(format!("Directories: {}", report.usage.directory_count));
lines.push(format!("Snapshots: {}", report.usage.snapshot_count));
lines.push(format!(
"Data Reduction Ratio: {:.2}x",
report.usage.reduction_ratio
));
lines.push(String::new());
lines.push("--- I/O Statistics ---".to_string());
lines.push(format!("Read Ops: {}", report.io.read_ops));
lines.push(format!("Write Ops: {}", report.io.write_ops));
lines.push(format!("Bytes Read: {}", report.io.bytes_read));
lines.push(format!("Bytes Written: {}", report.io.bytes_written));
lines.push(format!(
"Avg Read Latency: {} us",
report.io.avg_read_latency_us
));
lines.push(format!(
"Avg Write Latency: {} us",
report.io.avg_write_latency_us
));
lines.push(format!(
"Cache Hit Rate: {:.1}%",
report.io.cache_hit_rate * 100.0
));
lines.push(String::new());
lines.push("--- Capacity Forecast ---".to_string());
lines.push(format!(
"Daily Growth Rate: {} bytes/day",
report.forecast.daily_growth_rate
));
if report.forecast.days_until_full >= 0 {
lines.push(format!(
"Days Until Full: {}",
report.forecast.days_until_full
));
} else {
lines.push("Days Until Full: N/A (declining)".to_string());
}
lines.push(format!(
"Recommendation: {:?}",
report.forecast.recommendation
));
lines.push(format!(
"Confidence: {:.0}%",
report.forecast.confidence * 100.0
));
lines.push(String::new());
if !report.recommendations.is_empty() {
lines.push("--- Recommendations ---".to_string());
for rec in &report.recommendations {
lines.push(format!("• {}", rec));
}
}
lines.join("\n")
}
fn format_json_report(report: &FullReport) -> String {
let mut json = String::new();
json.push_str("{\n");
json.push_str(&format!(" \"dataset\": \"{}\",\n", report.dataset));
json.push_str(&format!(" \"health_score\": {},\n", report.health_score));
json.push_str(&format!(" \"generated_at\": {},\n", report.generated_at));
json.push_str(" \"usage\": {\n");
json.push_str(&format!(
" \"total_capacity\": {},\n",
report.usage.total_capacity
));
json.push_str(&format!(
" \"used_space\": {},\n",
report.usage.used_space
));
json.push_str(&format!(
" \"free_space\": {},\n",
report.usage.free_space
));
json.push_str(&format!(
" \"usage_percent\": {:.2},\n",
report.usage.usage_percent
));
json.push_str(&format!(
" \"file_count\": {},\n",
report.usage.file_count
));
json.push_str(&format!(
" \"reduction_ratio\": {:.2}\n",
report.usage.reduction_ratio
));
json.push_str(" },\n");
json.push_str(" \"io\": {\n");
json.push_str(&format!(" \"read_ops\": {},\n", report.io.read_ops));
json.push_str(&format!(" \"write_ops\": {},\n", report.io.write_ops));
json.push_str(&format!(
" \"read_throughput\": {},\n",
report.io.read_throughput
));
json.push_str(&format!(
" \"write_throughput\": {},\n",
report.io.write_throughput
));
json.push_str(&format!(
" \"cache_hit_rate\": {:.2}\n",
report.io.cache_hit_rate
));
json.push_str(" },\n");
json.push_str(" \"forecast\": {\n");
json.push_str(&format!(
" \"daily_growth_rate\": {},\n",
report.forecast.daily_growth_rate
));
json.push_str(&format!(
" \"days_until_full\": {},\n",
report.forecast.days_until_full
));
json.push_str(&format!(
" \"confidence\": {:.2}\n",
report.forecast.confidence
));
json.push_str(" },\n");
json.push_str(" \"recommendations\": [\n");
for (i, rec) in report.recommendations.iter().enumerate() {
if i < report.recommendations.len() - 1 {
json.push_str(&format!(" \"{}\",\n", rec));
} else {
json.push_str(&format!(" \"{}\"\n", rec));
}
}
json.push_str(" ]\n");
json.push_str("}\n");
json
}
fn format_prometheus_report(report: &FullReport) -> String {
let mut lines = Vec::new();
let dataset = &report.dataset;
lines.push(format!(
"lcpfs_capacity_bytes{{dataset=\"{}\"}} {}",
dataset, report.usage.total_capacity
));
lines.push(format!(
"lcpfs_used_bytes{{dataset=\"{}\"}} {}",
dataset, report.usage.used_space
));
lines.push(format!(
"lcpfs_free_bytes{{dataset=\"{}\"}} {}",
dataset, report.usage.free_space
));
lines.push(format!(
"lcpfs_usage_percent{{dataset=\"{}\"}} {}",
dataset, report.usage.usage_percent
));
lines.push(format!(
"lcpfs_file_count{{dataset=\"{}\"}} {}",
dataset, report.usage.file_count
));
lines.push(format!(
"lcpfs_directory_count{{dataset=\"{}\"}} {}",
dataset, report.usage.directory_count
));
lines.push(format!(
"lcpfs_snapshot_count{{dataset=\"{}\"}} {}",
dataset, report.usage.snapshot_count
));
lines.push(format!(
"lcpfs_reduction_ratio{{dataset=\"{}\"}} {}",
dataset, report.usage.reduction_ratio
));
lines.push(format!(
"lcpfs_read_ops_total{{dataset=\"{}\"}} {}",
dataset, report.io.read_ops
));
lines.push(format!(
"lcpfs_write_ops_total{{dataset=\"{}\"}} {}",
dataset, report.io.write_ops
));
lines.push(format!(
"lcpfs_read_bytes_total{{dataset=\"{}\"}} {}",
dataset, report.io.bytes_read
));
lines.push(format!(
"lcpfs_write_bytes_total{{dataset=\"{}\"}} {}",
dataset, report.io.bytes_written
));
lines.push(format!(
"lcpfs_read_latency_us{{dataset=\"{}\"}} {}",
dataset, report.io.avg_read_latency_us
));
lines.push(format!(
"lcpfs_write_latency_us{{dataset=\"{}\"}} {}",
dataset, report.io.avg_write_latency_us
));
lines.push(format!(
"lcpfs_read_throughput_bps{{dataset=\"{}\"}} {}",
dataset, report.io.read_throughput
));
lines.push(format!(
"lcpfs_write_throughput_bps{{dataset=\"{}\"}} {}",
dataset, report.io.write_throughput
));
lines.push(format!(
"lcpfs_cache_hit_rate{{dataset=\"{}\"}} {}",
dataset, report.io.cache_hit_rate
));
lines.push(format!(
"lcpfs_daily_growth_bytes{{dataset=\"{}\"}} {}",
dataset, report.forecast.daily_growth_rate
));
lines.push(format!(
"lcpfs_days_until_full{{dataset=\"{}\"}} {}",
dataset, report.forecast.days_until_full
));
lines.push(format!(
"lcpfs_health_score{{dataset=\"{}\"}} {}",
dataset, report.health_score
));
lines.push(format!(
"lcpfs_dedup_ratio{{dataset=\"{}\"}} {}",
dataset, report.dedup.dedup_ratio
));
lines.push(format!(
"lcpfs_dedup_space_saved_bytes{{dataset=\"{}\"}} {}",
dataset, report.dedup.space_saved
));
lines.push(format!(
"lcpfs_compression_ratio{{dataset=\"{}\"}} {}",
dataset, report.compression.compression_ratio
));
lines.push(format!(
"lcpfs_compression_space_saved_bytes{{dataset=\"{}\"}} {}",
dataset, report.compression.space_saved
));
lines.join("\n")
}
pub fn format_summary(report: &FullReport) -> String {
format!(
"{}: {:.1}% used ({} / {} bytes), Health: {}/100",
report.dataset,
report.usage.usage_percent,
format_bytes(report.usage.used_space),
format_bytes(report.usage.total_capacity),
report.health_score
)
}
pub fn format_bytes(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = 1024 * KB;
const GB: u64 = 1024 * MB;
const TB: u64 = 1024 * GB;
const PB: u64 = 1024 * TB;
if bytes >= PB {
format!("{:.2} PB", bytes as f64 / PB as f64)
} else if bytes >= TB {
format!("{:.2} TB", bytes as f64 / TB as f64)
} else if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2} KB", bytes as f64 / KB as f64)
} else {
format!("{} B", bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_bytes() {
assert_eq!(format_bytes(0), "0 B");
assert_eq!(format_bytes(512), "512 B");
assert_eq!(format_bytes(1024), "1.00 KB");
assert_eq!(format_bytes(1024 * 1024), "1.00 MB");
assert_eq!(format_bytes(1024 * 1024 * 1024), "1.00 GB");
}
#[test]
fn test_format_text_report() {
let report = FullReport {
dataset: "test".to_string(),
health_score: 85,
..Default::default()
};
let text = format_text_report(&report);
assert!(text.contains("test"));
assert!(text.contains("85"));
}
#[test]
fn test_format_prometheus() {
let report = FullReport {
dataset: "pool/data".to_string(),
usage: UsageSummary {
used_space: 1000,
..Default::default()
},
..Default::default()
};
let prom = format_prometheus_report(&report);
assert!(prom.contains("lcpfs_used_bytes"));
assert!(prom.contains("pool/data"));
}
}