use rocksdb::{DB, IteratorMode, Options}; use rpki::storage::{column_family_descriptors, CF_REPOSITORY_VIEW}; use std::fs; use std::path::{Path, PathBuf}; fn usage() -> String { let bin = "repository_view_stats"; format!( "\ Usage: {bin} --db Options: --db RocksDB directory --help Show this help " ) } fn parse_args(argv: &[String]) -> Result { if argv.iter().any(|arg| arg == "--help" || arg == "-h") { return Err(usage()); } let mut db_path: Option = None; let mut i = 1usize; while i < argv.len() { match argv[i].as_str() { "--db" => { i += 1; let value = argv.get(i).ok_or("--db requires a value")?; db_path = Some(PathBuf::from(value)); } other => return Err(format!("unknown argument: {other}\n\n{}", usage())), } i += 1; } db_path.ok_or_else(|| format!("--db is required\n\n{}", usage())) } fn dir_size(path: &Path) -> Result> { let mut total = 0u64; for entry in fs::read_dir(path)? { let entry = entry?; let file_type = entry.file_type()?; if file_type.is_file() { total = total.saturating_add(entry.metadata()?.len()); } else if file_type.is_dir() { total = total.saturating_add(dir_size(&entry.path())?); } } Ok(total) } fn main() -> Result<(), Box> { let argv: Vec = std::env::args().collect(); let db_path = parse_args(&argv).map_err(|e| -> Box { e.into() })?; let mut opts = Options::default(); opts.create_if_missing(false); opts.create_missing_column_families(false); let db = DB::open_cf_descriptors(&opts, &db_path, column_family_descriptors())?; let cf = db .cf_handle(CF_REPOSITORY_VIEW) .ok_or("missing repository_view column family")?; let mut kv_count = 0u64; let mut key_bytes_total = 0u64; let mut value_bytes_total = 0u64; let mut max_key_bytes = 0usize; let mut max_value_bytes = 0usize; for entry in db.iterator_cf(cf, IteratorMode::Start) { let (key, value) = entry?; kv_count += 1; key_bytes_total += key.len() as u64; value_bytes_total += value.len() as u64; max_key_bytes = max_key_bytes.max(key.len()); max_value_bytes = max_value_bytes.max(value.len()); } let logical_total_bytes = key_bytes_total + value_bytes_total; let avg_key_bytes = if kv_count > 0 { key_bytes_total as f64 / kv_count as f64 } else { 0.0 }; let avg_value_bytes = if kv_count > 0 { value_bytes_total as f64 / kv_count as f64 } else { 0.0 }; let out = serde_json::json!({ "db_path": db_path.display().to_string(), "column_family": CF_REPOSITORY_VIEW, "kv_count": kv_count, "key_bytes_total": key_bytes_total, "value_bytes_total": value_bytes_total, "logical_total_bytes": logical_total_bytes, "db_dir_on_disk_bytes": dir_size(&db_path)?, "avg_key_bytes": avg_key_bytes, "avg_value_bytes": avg_value_bytes, "max_key_bytes": max_key_bytes, "max_value_bytes": max_value_bytes, }); println!("{}", serde_json::to_string_pretty(&out)?); Ok(()) }