Files
2026-10-13 19:21:58 -03:00

137 lines
3.8 KiB
Rust

//! Scale, measured in release: 20 000 generated notes indexed in under 5 s and
//! a single `update_note` in under 5 ms.
//!
//! Run with `cargo test -p notescraft-index --release --test perf -- --ignored
//! --nocapture`; the numbers are printed.
use std::time::Instant;
use notescraft_index::Index;
/// Notes generated for the scale run.
const NOTES: usize = 20_000;
fn note_body(i: usize) -> String {
format!(
"---\ntags: [topic/{}, status/{}]\naliases: [Alias{i}]\n---\n# Heading {i}\n\n\
Paragraph about [[Note {}]] and [[topic/Note {}]] and [[#Heading {i}]] ^blk-{i}\n\n\
Body #inline/{i} with [a link](Note {}.md) and ![[assets/pic.png]].\n",
i % 7,
i % 3,
i,
(i + 1) % NOTES,
(i + 2) % NOTES,
)
}
fn path_of(i: usize) -> String {
format!("folder{}/Note {i}.md", i % 20)
}
#[test]
#[ignore = "perf: run with --release --ignored"]
fn twenty_thousand_notes_index_in_under_five_seconds() {
let bodies: Vec<String> = (0..NOTES).map(note_body).collect();
let mut index = Index::new();
index.add_file("assets/pic.png").unwrap();
let start = Instant::now();
for (i, body) in bodies.iter().enumerate() {
index.update_note(&path_of(i), body).unwrap();
}
let elapsed = start.elapsed();
println!("indexed {NOTES} notes in {elapsed:?}");
assert_eq!(index.notes().len(), NOTES);
assert!(elapsed.as_secs_f64() < 5.0, "20 000 notes must index in under 5 s, took {elapsed:?}");
}
#[test]
#[ignore = "perf: run with --release --ignored"]
fn one_update_is_under_five_milliseconds() {
let bodies: Vec<String> = (0..NOTES).map(note_body).collect();
let mut index = Index::new();
for (i, body) in bodies.iter().enumerate() {
index.update_note(&path_of(i), body).unwrap();
}
let mut worst = std::time::Duration::ZERO;
let mut checked = 0;
for (i, body) in bodies.iter().enumerate() {
let path = path_of(i);
let start = Instant::now();
index.update_note(&path, body).unwrap();
let took = start.elapsed();
worst = worst.max(took);
checked += 1;
if checked >= 2_000 {
break;
}
}
println!("worst single update over {checked} notes: {worst:?}");
assert!(worst.as_secs_f64() < 0.005, "a single update must stay under 5 ms, worst was {worst:?}");
}
#[test]
#[ignore = "perf: run with --release --ignored"]
fn queries_over_twenty_thousand_notes_stay_usable() {
let mut index = Index::new();
index.add_file("assets/pic.png").unwrap();
for i in 0..NOTES {
index.update_note(&path_of(i), &note_body(i)).unwrap();
}
for (name, took) in [
(
"resolve",
query_time(&index, |i| {
let _ = i.resolve("folder0/Note 0.md", "Note 12345");
}),
),
(
"outgoing",
query_time(&index, |i| {
let _ = i.outgoing("folder0/Note 0.md");
}),
),
(
"unresolved",
query_time(&index, |i| {
let _ = i.unresolved();
}),
),
(
"backlinks",
query_time(&index, |i| {
let _ = i.backlinks("folder0/Note 0.md");
}),
),
(
"orphans",
query_time(&index, |i| {
let _ = i.orphans();
}),
),
(
"edges",
query_time(&index, |i| {
let _ = i.all_link_edges();
}),
),
(
"tags",
query_time(&index, |i| {
let _ = i.tags();
}),
),
] {
println!("{name}: {took:?}");
}
}
fn query_time(index: &Index, query: impl Fn(&Index)) -> std::time::Duration {
let start = Instant::now();
query(index);
start.elapsed()
}