diff --git a/core/dr-catalog/examples/catalog_bench.rs b/core/dr-catalog/examples/catalog_bench.rs index c384ecc..f5227f7 100644 --- a/core/dr-catalog/examples/catalog_bench.rs +++ b/core/dr-catalog/examples/catalog_bench.rs @@ -7,8 +7,15 @@ //! and the backfill that runs inside it, step by step. Run it against a //! *copy* of a real catalog: opening migrates and backfills, which write. //! +//! Then what the library screen reads on every keystroke and scroll: the +//! filter chips' counts, the keyword panel, and the grid's total. Two of +//! those live in `dr-ui` (`library::local_original_count` and the grid +//! count), whose `library` module is private; their SQL is spelled here as +//! it is spelled there, and has to be kept in step by hand. +//! //! The figures are for reading side by side before and after a change; they -//! are not a gate. Compare the `cpu` column when the machine is busy. +//! are not a gate. Compare the `cpu` column when the machine is busy. The +//! answers are printed too, so two builds can be checked for agreeing. use std::path::PathBuf; use std::time::{Duration, Instant}; @@ -44,6 +51,8 @@ fn main() { keywords::adopt_orphan_terms(conn).unwrap(); }); + interactive(conn); + // A sync pass: the upload snapshot, then a merge of the catalog with a // copy of itself — every row a match, which is the steady state. let scratch = path.with_extension("bench-snapshot"); @@ -84,6 +93,102 @@ fn main() { } } +/// What one click in the library reads: a rating or label keystroke +/// refreshes the chips, a selection change redraws the keyword panel, and +/// every scroll reload counts the grid. +fn interactive(conn: &rusqlite::Connection) { + println!( + " rating_histogram {:?}, label_histogram {:?}, local originals {}, grid {} / rated {}", + rating::rating_histogram(conn).unwrap(), + rating::label_histogram(conn).unwrap(), + local_original_count(conn), + grid_count(conn, ""), + grid_count(conn, RATED_AT_LEAST_ONE), + ); + let words = keywords::list(conn).unwrap(); + println!( + " keywords::list {} terms, digest {:016x}", + words.len(), + digest(&format!("{words:?}")) + ); + // A selection the size of a grid window, from the start of the library. + let selection: Vec = conn + .prepare("SELECT id FROM images ORDER BY id LIMIT 120") + .unwrap() + .query_map([], |r| Ok(dr_types::ImageId(r.get::<_, i64>(0)? as u64))) + .unwrap() + .collect::>() + .unwrap(); + println!( + " keywords::for_images digest {:016x}", + digest(&format!( + "{:?}", + keywords::for_images(conn, &selection).unwrap() + )) + ); + + time("rating::rating_histogram", 50, || { + rating::rating_histogram(conn).unwrap(); + }); + time("library::local_original_count", 50, || { + local_original_count(conn); + }); + time("rating::label_histogram", 50, || { + rating::label_histogram(conn).unwrap(); + }); + time("keywords::list", 50, || { + keywords::list(conn).unwrap(); + }); + time("keywords::for_images (120)", 50, || { + keywords::for_images(conn, &selection).unwrap(); + }); + time("grid count", 50, || { + grid_count(conn, ""); + }); + time("grid count, rated >= 1", 50, || { + grid_count(conn, RATED_AT_LEAST_ONE); + }); +} + +/// `dr_ui::library::local_original_count`, spelled as it is there. +fn local_original_count(conn: &rusqlite::Connection) -> i64 { + conn.query_row( + "SELECT count(*) FROM images i + WHERE i.shadowed_by IS NULL AND i.trashed_at IS NULL + AND EXISTS (SELECT 1 FROM image_cache ic + WHERE ic.image_id = i.id AND ic.tier_actual >= 2)", + [], + |r| r.get(0), + ) + .unwrap() +} + +/// `RatingFilter::sql` for one star and up. +const RATED_AT_LEAST_ONE: &str = " AND coalesce((SELECT dv.rating FROM versions dv + WHERE dv.image_id = i.id AND dv.is_default = 1 + LIMIT 1), 0) >= 1"; + +/// `dr_ui::library::total_images_filtered`, spelled as it is there. +fn grid_count(conn: &rusqlite::Connection, rated: &str) -> i64 { + let folded = dr_catalog::bursts::not_collapsed_away("i"); + conn.query_row( + &format!( + "SELECT count(*) FROM images i + WHERE i.shadowed_by IS NULL AND i.trashed_at IS NULL{rated} AND {folded}" + ), + [], + |r| r.get(0), + ) + .unwrap() +} + +/// FNV-1a, to print a long answer as something two runs can compare. +fn digest(s: &str) -> u64 { + s.bytes().fold(0xcbf29ce484222325, |h, b| { + (h ^ u64::from(b)).wrapping_mul(0x100000001b3) + }) +} + /// Run `f` a few times and print the best wall-clock, the median, and the /// best CPU time — the figure to compare across runs on a busy machine. fn time(label: &str, runs: usize, mut f: impl FnMut()) {