High-performance implementation of plcbundle written in Rust
at main 341 lines 12 kB view raw
1use anyhow::Result; 2use clap::Parser; 3use plcbundle::index::Index; 4use plcbundle::*; 5use std::path::{Path, PathBuf}; 6 7use super::utils; 8 9#[derive(Parser)] 10#[command( 11 about = "Show comprehensive repository status", 12 long_about = "Display a comprehensive overview of the repository's current state, including 13bundle statistics, DID index status, mempool state, and actionable health 14recommendations. 15 16This command provides a quick way to understand your repository's health and 17configuration. It shows total bundles, operations, unique DIDs, storage usage, 18compression ratios, and the status of optional components like the DID index 19and mempool. 20 21Use --detailed to see additional information like recent bundles and more 22comprehensive statistics. Use --json for machine-readable output suitable for 23monitoring systems or automated health checks. 24 25The command automatically identifies potential issues (like missing DID index) 26and suggests next steps to improve repository functionality. This makes it an 27excellent starting point for understanding repository state and planning 28maintenance tasks.", 29 alias = "info", 30 help_template = crate::clap_help!( 31 examples: " # Show repository status\n \ 32 {bin} status\n\n \ 33 # Show detailed status with recent bundles\n \ 34 {bin} status --detailed\n\n \ 35 # JSON output for scripting\n \ 36 {bin} status --json\n\n \ 37 # Using legacy 'info' alias\n \ 38 {bin} info" 39 ) 40)] 41pub struct StatusCommand { 42 /// Show detailed information 43 #[arg(short, long)] 44 pub detailed: bool, 45 46 /// Output as JSON 47 #[arg(long)] 48 pub json: bool, 49} 50 51pub fn run(cmd: StatusCommand, dir: PathBuf) -> Result<()> { 52 let manager = utils::create_manager(dir.clone(), false, false, false)?; 53 let index = manager.get_index(); 54 55 if cmd.json { 56 print_json_status(&manager, &index, &dir)?; 57 } else { 58 print_human_status(&manager, &index, &dir, cmd.detailed)?; 59 } 60 61 Ok(()) 62} 63 64fn print_human_status( 65 manager: &BundleManager, 66 index: &Index, 67 dir: &Path, 68 detailed: bool, 69) -> Result<()> { 70 let dir_display = utils::display_path(dir); 71 72 // Header 73 println!("📦 PLC Bundle Repository Status"); 74 println!("═══════════════════════════════════════════════════════════════"); 75 println!(); 76 77 // Repository Information 78 println!("📁 Repository"); 79 println!("───────────────────────────────────────────────────────────────"); 80 println!(" Directory: {}", dir_display.display()); 81 println!(" Version: {}", index.version); 82 println!(" Origin: {}", index.origin); 83 println!(" Last Updated: {}", index.updated_at); 84 println!(); 85 86 // Bundle Statistics 87 println!("📊 Bundle Statistics"); 88 println!("───────────────────────────────────────────────────────────────"); 89 90 let is_empty = utils::is_repository_empty(manager); 91 92 if is_empty { 93 println!(" Status: Empty (no bundles)"); 94 println!(); 95 } else { 96 let total_bundles = index.last_bundle; 97 let total_operations = plcbundle::constants::total_operations_from_bundles(total_bundles); 98 99 println!( 100 " Total Bundles: {}", 101 utils::format_number(total_bundles as u64) 102 ); 103 println!( 104 " Operations: ~{}", 105 utils::format_number(total_operations) 106 ); 107 println!( 108 " Unique DIDs: {}", 109 utils::format_number(calculate_total_dids(index) as u64) 110 ); 111 println!(); 112 113 // Storage 114 println!( 115 " Compressed: {}", 116 utils::format_bytes(index.total_size_bytes) 117 ); 118 println!( 119 " Uncompressed: {}", 120 utils::format_bytes(index.total_uncompressed_size_bytes) 121 ); 122 123 let compression_ratio = if index.total_uncompressed_size_bytes > 0 { 124 (1.0 - index.total_size_bytes as f64 / index.total_uncompressed_size_bytes as f64) 125 * 100.0 126 } else { 127 0.0 128 }; 129 println!(" Compression: {:.1}%", compression_ratio); 130 println!(); 131 132 // Recent bundles 133 if detailed { 134 let recent_count = 5; 135 let start_idx = index.bundles.len().saturating_sub(recent_count); 136 let recent_bundles = &index.bundles[start_idx..]; 137 138 println!(" Recent Bundles:"); 139 for meta in recent_bundles { 140 println!( 141 " #{:<6} {}{} ({} ops, {} DIDs)", 142 meta.bundle_number, 143 &meta.start_time[..19], // Just date and time without timezone 144 &meta.end_time[..19], 145 utils::format_number(meta.operation_count as u64), 146 utils::format_number(meta.did_count as u64) 147 ); 148 } 149 println!(); 150 } 151 } 152 153 // DID Index Status 154 println!("🔍 DID Index"); 155 println!("───────────────────────────────────────────────────────────────"); 156 157 let did_index_exists = check_did_index_exists(dir); 158 159 if did_index_exists { 160 println!(" Status: ✓ Built"); 161 162 let stats = manager.get_did_index_stats_struct(); 163 if stats.total_dids > 0 { 164 println!( 165 " Indexed DIDs: {}", 166 utils::format_number(stats.total_dids as u64) 167 ); 168 } 169 } else { 170 println!(" Status: ✗ Not built"); 171 println!(" Hint: Run 'plcbundle index build' to create DID index"); 172 } 173 println!(); 174 175 // Mempool Status 176 println!("⏳ Mempool"); 177 println!("───────────────────────────────────────────────────────────────"); 178 179 if let Ok(mempool_stats) = manager.get_mempool_stats() { 180 if mempool_stats.count > 0 { 181 println!( 182 " Operations: {}", 183 utils::format_number(mempool_stats.count as u64) 184 ); 185 println!(" Target Bundle: #{}", mempool_stats.target_bundle); 186 println!( 187 " Can Bundle: {}", 188 if mempool_stats.can_create_bundle { 189 "Yes" 190 } else { 191 "No" 192 } 193 ); 194 195 if let Some(first) = mempool_stats.first_time { 196 println!(" First Op: {}", first); 197 } 198 if let Some(last) = mempool_stats.last_time { 199 println!(" Last Op: {}", last); 200 } 201 } else { 202 println!(" Status: Empty"); 203 } 204 } else { 205 println!(" Status: Not initialized"); 206 } 207 println!(); 208 209 // Health & Recommendations 210 if !is_empty { 211 println!("💡 Status & Recommendations"); 212 println!("───────────────────────────────────────────────────────────────"); 213 214 let mut suggestions = Vec::new(); 215 let mut hints = Vec::new(); 216 217 if !did_index_exists { 218 suggestions.push("Build DID index for fast lookups: plcbundle index build"); 219 } 220 221 if let Ok(mempool_stats) = manager.get_mempool_stats() 222 && mempool_stats.count >= plcbundle::constants::BUNDLE_SIZE 223 { 224 suggestions.push("Mempool ready to create new bundle: plcbundle sync"); 225 } 226 227 // Add general hints 228 hints.push("Verify integrity: plcbundle verify --chain"); 229 if did_index_exists { 230 hints.push("Verify DID index: plcbundle index verify"); 231 } 232 hints.push("List bundles: plcbundle ls"); 233 234 if suggestions.is_empty() { 235 println!(" ✓ Repository is healthy"); 236 println!(); 237 if detailed { 238 println!(" Useful commands:"); 239 for hint in hints { 240 println!("{}", hint); 241 } 242 } 243 } else { 244 println!(" Suggestions:"); 245 for suggestion in suggestions { 246 println!("{}", suggestion); 247 } 248 println!(); 249 if detailed { 250 println!(" Useful commands:"); 251 for hint in hints { 252 println!("{}", hint); 253 } 254 } 255 } 256 println!(); 257 } 258 259 Ok(()) 260} 261 262fn print_json_status(manager: &BundleManager, index: &Index, dir: &Path) -> Result<()> { 263 use serde_json::json; 264 265 let dir_display = utils::display_path(dir); 266 let is_empty = utils::is_repository_empty(manager); 267 268 let mut status = json!({ 269 "repository": { 270 "directory": dir_display.display().to_string(), 271 "version": index.version, 272 "origin": index.origin, 273 "last_updated": index.updated_at, 274 }, 275 "bundles": { 276 "count": index.last_bundle, 277 "total_size_bytes": index.total_size_bytes, 278 "total_uncompressed_size_bytes": index.total_uncompressed_size_bytes, 279 "compression_ratio": if index.total_uncompressed_size_bytes > 0 { 280 (1.0 - index.total_size_bytes as f64 / index.total_uncompressed_size_bytes as f64) * 100.0 281 } else { 282 0.0 283 }, 284 } 285 }); 286 287 // DID Index 288 let did_index_exists = check_did_index_exists(dir); 289 let mut did_index_info = json!({ 290 "exists": did_index_exists, 291 }); 292 293 if did_index_exists { 294 let stats = manager.get_did_index_stats_struct(); 295 if stats.total_dids > 0 { 296 did_index_info["indexed_dids"] = json!(stats.total_dids); 297 } 298 } 299 status["did_index"] = did_index_info; 300 301 // Mempool 302 let mut mempool_info = json!({}); 303 if let Ok(mempool_stats) = manager.get_mempool_stats() { 304 mempool_info = json!({ 305 "count": mempool_stats.count, 306 "target_bundle": mempool_stats.target_bundle, 307 "can_create_bundle": mempool_stats.can_create_bundle, 308 "validated": mempool_stats.validated, 309 "first_time": mempool_stats.first_time.map(|t| t.to_rfc3339()), 310 "last_time": mempool_stats.last_time.map(|t| t.to_rfc3339()), 311 }); 312 } 313 status["mempool"] = mempool_info; 314 315 // Health 316 let mut health = Vec::new(); 317 if !is_empty && !did_index_exists { 318 health.push("did_index_not_built"); 319 } 320 if let Ok(mempool_stats) = manager.get_mempool_stats() 321 && mempool_stats.count >= plcbundle::constants::BUNDLE_SIZE 322 { 323 health.push("mempool_ready_to_bundle"); 324 } 325 status["health"] = json!(health); 326 327 println!("{}", sonic_rs::to_string_pretty(&status)?); 328 329 Ok(()) 330} 331 332fn check_did_index_exists(dir: &Path) -> bool { 333 let did_index_dir = dir 334 .join(plcbundle::constants::DID_INDEX_DIR) 335 .join(plcbundle::constants::DID_INDEX_SHARDS); 336 did_index_dir.exists() && did_index_dir.is_dir() 337} 338 339fn calculate_total_dids(index: &Index) -> u32 { 340 index.bundles.iter().map(|b| b.did_count).sum() 341}