// Copyright 2019-2020 Georges Racinet // // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. use hg::revlog::node::*; use hg::revlog::nodemap::*; use hg::revlog::*; use memmap2::MmapOptions; use rand::Rng; use std::fs::File; use std::io; use std::io::Write; use std::path::{Path, PathBuf}; use std::time::Instant; mod index; use index::Index; fn mmap_index(repo_path: &Path) -> Index { let mut path = PathBuf::from(repo_path); path.extend([".hg", "store", "00changelog.i"].iter()); Index::load_mmap(path) } fn mmap_nodemap(path: &Path) -> NodeTree { let file = File::open(path).unwrap(); let mmap = unsafe { MmapOptions::new().map(&file).unwrap() }; let len = mmap.len(); NodeTree::load_bytes(Box::new(mmap), len) } /// Scan the whole index and create the corresponding nodemap file at `path` fn create(index: &Index, path: &Path) -> io::Result<()> { let mut file = File::create(path)?; let start = Instant::now(); let mut nm = NodeTree::default(); for rev in 0..index.len() { let rev = Revision(rev as BaseRevision); nm.insert(index, index.node(rev).unwrap(), rev).unwrap(); } eprintln!("Nodemap constructed in RAM in {:?}", start.elapsed()); file.write_all(&nm.into_readonly_and_added_bytes().1)?; eprintln!("Nodemap written to disk"); Ok(()) } fn query(index: &Index, nm: &NodeTree, prefix: &str) { let start = Instant::now(); let res = NodePrefix::from_hex(prefix).map(|p| nm.find_bin(index, p)); println!("Result found in {:?}: {:?}", start.elapsed(), res); } fn bench(index: &Index, nm: &NodeTree, queries: usize) { let len = index.len() as u32; let mut rng = rand::thread_rng(); let nodes: Vec = (0..queries) .map(|_| { *index .node(Revision((rng.gen::() % len) as BaseRevision)) .unwrap() }) .collect(); if queries < 10 { let nodes_hex: Vec = nodes.iter().map(|n| format!("{:x}", n)).collect(); println!("Nodes: {:?}", nodes_hex); } let mut last: Option = None; let start = Instant::now(); for node in nodes.iter() { last = nm.find_bin(index, node.into()).unwrap(); } let elapsed = start.elapsed(); println!( "Did {} queries in {:?} (mean {:?}), last was {:x} with result {:?}", queries, elapsed, elapsed / (queries as u32), nodes.last().unwrap(), last ); } fn main() { use clap::{Parser, Subcommand}; #[derive(Parser)] #[command()] /// Nodemap pure Rust example struct App { // Path to the repository, always necessary for its index #[arg(short, long)] repository: PathBuf, // Path to the nodemap file, independent of REPOSITORY #[arg(short, long)] nodemap_file: PathBuf, #[command(subcommand)] command: Command, } #[derive(Subcommand)] enum Command { /// Create `NODEMAP_FILE` by scanning repository index Create, /// Query `NODEMAP_FILE` for `prefix` Query { prefix: String }, /// Perform #`QUERIES` random successful queries on `NODEMAP_FILE` Bench { queries: usize }, } let app = App::parse(); let repo = &app.repository; let nm_path = &app.nodemap_file; let index = mmap_index(repo); let nm = mmap_nodemap(nm_path); match &app.command { Command::Create => { println!( "Creating nodemap file {} for repository {}", nm_path.display(), repo.display() ); create(&index, Path::new(nm_path)).unwrap(); } Command::Bench { queries } => { println!( "Doing {} random queries in nodemap file {} of repository {}", queries, nm_path.display(), repo.display() ); bench(&index, &nm, *queries); } Command::Query { prefix } => { println!( "Querying {} in nodemap file {} of repository {}", prefix, nm_path.display(), repo.display() ); query(&index, &nm, prefix); } } }