vault_audit_tools/commands/
entity_gaps.rs1use crate::audit::types::AuditEntry;
31use crate::utils::progress::ProgressBar;
32use crate::utils::reader::open_file;
33use anyhow::Result;
34use std::collections::HashMap;
35use std::io::{BufRead, BufReader};
36
37fn format_number(n: usize) -> String {
38 let s = n.to_string();
39 let mut result = String::new();
40 for (i, c) in s.chars().rev().enumerate() {
41 if i > 0 && i % 3 == 0 {
42 result.push(',');
43 }
44 result.push(c);
45 }
46 result.chars().rev().collect()
47}
48
49pub fn run(log_files: &[String], _window_seconds: u64) -> Result<()> {
50 let mut operations_by_type: HashMap<String, usize> = HashMap::new();
51 let mut paths_accessed: HashMap<String, usize> = HashMap::new();
52 let mut total_lines = 0;
53 let mut no_entity_operations = 0;
54
55 for (file_idx, log_file) in log_files.iter().enumerate() {
57 eprintln!(
58 "[{}/{}] Processing: {}",
59 file_idx + 1,
60 log_files.len(),
61 log_file
62 );
63
64 let file_size = std::fs::metadata(log_file).ok().map(|m| m.len() as usize);
66 let mut progress = if let Some(size) = file_size {
67 ProgressBar::new(size, "Processing")
68 } else {
69 ProgressBar::new_spinner("Processing")
70 };
71
72 let file = open_file(log_file)?;
73 let reader = BufReader::new(file);
74
75 let mut file_lines = 0;
76 let mut bytes_read = 0;
77
78 for line in reader.lines() {
79 file_lines += 1;
80 total_lines += 1;
81 let line = line?;
82 bytes_read += line.len() + 1; if file_lines % 10_000 == 0 {
86 if let Some(size) = file_size {
87 progress.update(bytes_read.min(size));
88 } else {
89 progress.update(file_lines);
90 }
91 }
92
93 let entry: AuditEntry = match serde_json::from_str(&line) {
94 Ok(e) => e,
95 Err(_) => continue,
96 };
97
98 if entry.entity_id().is_some() {
100 continue;
101 }
102
103 no_entity_operations += 1;
104
105 if let Some(op) = entry.operation() {
107 *operations_by_type.entry(op.to_string()).or_insert(0) += 1;
108 }
109
110 if let Some(path) = entry.path() {
111 *paths_accessed.entry(path.to_string()).or_insert(0) += 1;
112 }
113 }
114
115 progress.finish_with_message(&format!(
116 "Processed {} lines from this file",
117 format_number(file_lines)
118 ));
119 }
120
121 eprintln!("\nTotal: Processed {} lines", format_number(total_lines));
122 eprintln!(
123 "Found {} operations with no entity ID",
124 format_number(no_entity_operations)
125 );
126
127 if no_entity_operations == 0 {
128 println!("\nNo operations without entity ID found!");
129 return Ok(());
130 }
131
132 println!("\n{}", "=".repeat(100));
133 println!("NO-ENTITY OPERATIONS ANALYSIS");
134 println!("{}", "=".repeat(100));
135
136 println!("\n1. SUMMARY");
137 println!("{}", "-".repeat(100));
138 println!(
139 "Total no-entity operations: {}",
140 format_number(no_entity_operations)
141 );
142 println!(
143 "Percentage of all operations: {:.2}%",
144 (no_entity_operations as f64 / total_lines as f64) * 100.0
145 );
146
147 println!("\n2. OPERATION TYPE DISTRIBUTION");
148 println!("{}", "-".repeat(100));
149 println!("{:<30} {:<15} {:<15}", "Operation", "Count", "Percentage");
150 println!("{}", "-".repeat(100));
151
152 let mut sorted_ops: Vec<_> = operations_by_type.iter().collect();
153 sorted_ops.sort_by(|a, b| b.1.cmp(a.1));
154
155 for (op, count) in sorted_ops.iter().take(20) {
156 let percentage = (**count as f64 / no_entity_operations as f64) * 100.0;
157 println!(
158 "{:<30} {:<15} {:<15.2}%",
159 op,
160 format_number(**count),
161 percentage
162 );
163 }
164
165 println!("\n3. TOP 30 PATHS ACCESSED");
166 println!("{}", "-".repeat(100));
167 println!("{:<70} {:<15} {:<15}", "Path", "Count", "% of No-Entity");
168 println!("{}", "-".repeat(100));
169
170 let mut sorted_paths: Vec<_> = paths_accessed.iter().collect();
171 sorted_paths.sort_by(|a, b| b.1.cmp(a.1));
172
173 for (path, count) in sorted_paths.iter().take(30) {
174 let percentage = (**count as f64 / no_entity_operations as f64) * 100.0;
175 let display_path = if path.len() > 68 {
176 format!("{}...", &path[..65])
177 } else {
178 path.to_string()
179 };
180 println!(
181 "{:<70} {:<15} {:<15.2}%",
182 display_path,
183 format_number(**count),
184 percentage
185 );
186 }
187
188 println!("\n{}", "=".repeat(100));
189
190 Ok(())
191}