vault_audit_tools/commands/
kv_summary.rs1use anyhow::{Context, Result};
31use std::fs::File;
32use std::io::BufReader;
33
34fn format_number(n: usize) -> String {
35 let s = n.to_string();
36 let mut result = String::new();
37 for (i, c) in s.chars().rev().enumerate() {
38 if i > 0 && i % 3 == 0 {
39 result.push(',');
40 }
41 result.push(c);
42 }
43 result.chars().rev().collect()
44}
45
46pub fn run(csv_file: &str) -> Result<()> {
47 let file = File::open(csv_file).context("Failed to open CSV file")?;
48 let reader = BufReader::new(file);
49 let mut csv_reader = csv::Reader::from_reader(reader);
50
51 let mut rows = Vec::new();
52 for result in csv_reader.records() {
53 let record = result?;
54 rows.push(record);
55 }
56
57 if rows.is_empty() {
58 println!("No data found in {}", csv_file);
59 return Ok(());
60 }
61
62 println!("\n{}", "=".repeat(70));
63 println!("{:^70}", "KV Usage Summary Report");
64 println!("{:^70}", format!("Source: {}", csv_file));
65 println!("{}\n", "=".repeat(70));
66
67 let total_paths = rows.len();
69 let mut total_clients = 0;
70 let mut total_operations = 0;
71
72 let headers = csv_reader.headers()?.clone();
74 let unique_clients_idx = headers.iter().position(|h| h == "unique_clients");
75 let operations_idx = headers.iter().position(|h| h == "operations_count");
76
77 for row in &rows {
78 if let Some(idx) = unique_clients_idx {
79 if let Ok(n) = row.get(idx).unwrap_or("0").parse::<usize>() {
80 total_clients += n;
81 }
82 }
83 if let Some(idx) = operations_idx {
84 if let Ok(n) = row.get(idx).unwrap_or("0").parse::<usize>() {
85 total_operations += n;
86 }
87 }
88 }
89
90 println!("Overview:");
91 println!(" • Total KV Paths: {}", total_paths);
92 println!(
93 " • Total Unique Clients: {}",
94 format_number(total_clients)
95 );
96 println!(" • Total Operations: {}", format_number(total_operations));
97 println!("\n{}\n", "-".repeat(70));
98
99 let kv_path_idx = headers.iter().position(|h| h == "kv_path");
101 let entity_ids_idx = headers.iter().position(|h| h == "entity_ids");
102 let alias_names_idx = headers.iter().position(|h| h == "alias_names");
103 let sample_paths_idx = headers.iter().position(|h| h == "sample_paths_accessed");
104
105 for (i, row) in rows.iter().enumerate() {
106 println!(
107 "{}. KV Path: {}",
108 i + 1,
109 kv_path_idx.and_then(|idx| row.get(idx)).unwrap_or("N/A")
110 );
111
112 if let Some(idx) = unique_clients_idx {
113 println!(" Unique Clients: {}", row.get(idx).unwrap_or("0"));
114 }
115
116 if let Some(idx) = operations_idx {
117 println!(" Total Operations: {}", row.get(idx).unwrap_or("0"));
118 }
119
120 if let Some(idx) = entity_ids_idx {
121 println!(" Entity IDs: {}", row.get(idx).unwrap_or("N/A"));
122 }
123
124 if let Some(idx) = alias_names_idx {
125 if let Some(names) = row.get(idx) {
126 if !names.is_empty() {
127 println!(" Alias Names: {}", names);
128 }
129 }
130 }
131
132 if let Some(idx) = sample_paths_idx {
133 if let Some(paths) = row.get(idx) {
134 let display_paths = if paths.len() > 80 {
135 format!("{}...", &paths[..77])
136 } else {
137 paths.to_string()
138 };
139 println!(" Sample Paths: {}", display_paths);
140 }
141 }
142
143 println!();
144 }
145
146 println!("{}", "-".repeat(70));
147 println!("Report complete. Analyzed {} KV paths.\n", total_paths);
148
149 Ok(())
150}