Skip to content

Commit 43eee8b

Browse files
committed
Add benchmark suite.
1 parent a8d700b commit 43eee8b

3 files changed

Lines changed: 115 additions & 3 deletions

File tree

src/benchmark.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use std:: {
2+
fs,
3+
io,
4+
path::Path,
5+
sync::{Arc, Mutex},
6+
};
7+
8+
use colored::Colorize;
9+
10+
use crate::{RankMethod, index_directory, model::{InMemoryModel, Model}};
11+
12+
// Benchmark function logic
13+
pub fn calculate_dir_size(dir_path: &Path) -> io::Result<u64> {
14+
let mut total_size = 0;
15+
if dir_path.is_dir() {
16+
for entry in fs::read_dir(dir_path)? {
17+
let entry = entry?;
18+
let path = entry.path();
19+
if path.is_dir() {
20+
total_size += calculate_dir_size(&path)?;
21+
} else {
22+
total_size += entry.metadata()?.len();
23+
}
24+
}
25+
}
26+
Ok(total_size)
27+
}
28+
29+
pub fn run_benchmark(dir_path: &Path) -> Result<(), ()> {
30+
println!("\n{}: Starting benchmarks on '{}'", "INFO".cyan(), dir_path.display());
31+
32+
// 1. Storage Efficiency
33+
let raw_size = calculate_dir_size(dir_path).unwrap_or(0);
34+
println!("Raw Corpus Size: {:.2} MB", raw_size as f64 / 1_048_576.0);
35+
36+
// 2. Indexing Throughput
37+
let model = Arc::new(Mutex::<InMemoryModel>::new(Default::default()));
38+
println!("{}: Indexing directory (this may take a while)...", "INFO".cyan());
39+
let start_time = std::time::Instant::now();
40+
41+
let mut index_path = dir_path.to_path_buf();
42+
index_path.push(".docsense.benchmark.json");
43+
let index_str = index_path.to_str().unwrap().to_string();
44+
45+
index_directory(dir_path, Arc::clone(&model), Some(&index_str))?;
46+
47+
let indexing_duration = start_time.elapsed();
48+
let index_size = fs::metadata(&index_path).map(|m| m.len()).unwrap_or(0);
49+
50+
println!("Indexing Time: {:?}", indexing_duration);
51+
println!("Index File Size: {:.2} MB", index_size as f64 / 1_048_576.0);
52+
println!("Space Reduction Ratio: {:.2}x\n", raw_size as f64 / index_size.max(1) as f64);
53+
54+
// 3. Query Latency
55+
println!("{}: Running query latency tests...", "INFO".cyan());
56+
57+
let queries = vec![
58+
"opengl",
59+
"texture array shader",
60+
"missingkeywordthatdoesnotexist",
61+
];
62+
63+
let model_lock = model.lock().unwrap();
64+
65+
for rank_method in vec![RankMethod::Tfidf, RankMethod::Bm25] {
66+
println!("\nRanking Method: {:?}", rank_method);
67+
println!("{:<30} | {:<15} | {:<15}", "Query", "Avg Latency", "Top Result Score");
68+
println!("{:-<66}", "");
69+
70+
for query_str in &queries {
71+
let query = query_str.chars().collect::<Vec<char>>();
72+
73+
// Warm up
74+
let _ = model_lock.search_query(&query, &model_lock, rank_method.clone());
75+
76+
let iters = 10;
77+
let mut total_duration = std::time::Duration::new(0, 0);
78+
let mut top_score = 0.0;
79+
80+
for _ in 0..iters {
81+
let start_query = std::time::Instant::now();
82+
let results = model_lock.search_query(&query, &model_lock, rank_method.clone()).unwrap_or_default();
83+
total_duration += start_query.elapsed();
84+
85+
if let Some((_, score)) = results.first() {
86+
top_score = *score;
87+
}
88+
}
89+
90+
let avg_duration = total_duration / iters;
91+
println!("{:<30} | {:<15?} | {:.4}", query_str, avg_duration, top_score);
92+
}
93+
}
94+
95+
println!("\n{}: Benchmark complete.", "INFO".cyan());
96+
Ok(())
97+
}

src/main.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod parser;
1212
mod lexer;
1313
mod server;
1414
mod model;
15+
mod benchmark;
1516

1617
use crate::model::*;
1718
use poppler::{Document};
@@ -187,7 +188,7 @@ fn append_folder_to_model(dir_path: &Path, model: Arc<Mutex<InMemoryModel>>, pro
187188

188189
visited.insert(file_path.clone());
189190

190-
// Main
191+
// Main logic
191192
let mut model = model.lock().unwrap();
192193
if model.requires_reindexing(&file_path, last_modified)? {
193194
println!("{}: Indexing {} ...", "INFO".cyan(), file_path_str.bright_cyan());
@@ -236,7 +237,7 @@ fn fetch_model(index_path: &str) -> Result<InMemoryModel, ()> {
236237
return Ok(model);
237238
}
238239

239-
fn index_directory(dir_path: &Path, model: Arc<Mutex<InMemoryModel>>, index_path: Option<&str>) -> Result<(), ()> {
240+
pub(crate) fn index_directory(dir_path: &Path, model: Arc<Mutex<InMemoryModel>>, index_path: Option<&str>) -> Result<(), ()> {
240241
let root_dir = fs::canonicalize(dir_path).unwrap_or_else(|err| {
241242
eprintln!("{}: Could not canonicalize root dir {} as {}", "ERROR".bold().red(), dir_path.display().to_string().bright_blue(), err.to_string().red());
242243
exit(1);
@@ -274,7 +275,7 @@ fn index_directory(dir_path: &Path, model: Arc<Mutex<InMemoryModel>>, index_path
274275
}
275276

276277
#[derive(ValueEnum, Clone, Debug, PartialEq)]
277-
enum RankMethod {
278+
pub(crate) enum RankMethod {
278279
Tfidf,
279280
Bm25
280281
}
@@ -340,6 +341,11 @@ fn entry() -> Result<(), ()> {
340341
// TODO: Print the information of server start at the end of logging
341342
return server::start(&address, Arc::clone(&model), rank_method, root_dir);
342343
}
344+
345+
Commands::Benchmark { dir_path } => {
346+
use benchmark::run_benchmark;
347+
run_benchmark(Path::new(&dir_path))?;
348+
}
343349
}
344350
Ok(())
345351
}

src/parser.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,14 @@ pub enum Commands {
5555
address: String,
5656
#[arg(short, long, default_value = "tfidf", value_enum, help = "Ranking algorithm to use")]
5757
rank_method: RankMethod,
58+
},
59+
60+
#[command(
61+
about = "Run comprehensive benchmarks",
62+
long_about = "Benchmarks indexing and search performance on the provided directory to generate research metrics."
63+
)]
64+
Benchmark {
65+
#[arg(help = "Path to the corpus directory to benchmark")]
66+
dir_path: String,
5867
}
5968
}

0 commit comments

Comments
 (0)