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