diff --git a/Cargo.lock b/Cargo.lock index e1f7a7b..a8f89c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,6 +40,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.16" @@ -61,6 +70,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "0.6.13" @@ -109,12 +124,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "anyhow" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" - [[package]] name = "async-lock" version = "3.3.0" @@ -332,6 +341,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.0.88" @@ -365,6 +380,33 @@ dependencies = [ "windows-targets 0.52.4", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -470,6 +512,40 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + [[package]] name = "crossbeam" version = "0.8.4" @@ -620,17 +696,10 @@ dependencies = [ name = "cuprate-database-benchmark" version = "0.0.0" dependencies = [ - "anyhow", - "cfg-if", - "clap", + "criterion", "cuprate-database", "cuprate-helper", - "rayon", - "serde", - "serde_json", - "strum", - "toml_edit 0.22.9", - "tracing", + "tempfile", ] [[package]] @@ -1091,6 +1160,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1425,6 +1504,26 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -1792,6 +1891,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + [[package]] name = "openssl" version = "0.10.64" @@ -2049,7 +2154,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_edit 0.21.1", + "toml_edit", ] [[package]] @@ -2264,6 +2369,29 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + [[package]] name = "regex-syntax" version = "0.8.2" @@ -2439,6 +2567,15 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.23" @@ -2526,15 +2663,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_spanned" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" -dependencies = [ - "serde", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2662,28 +2790,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" -[[package]] -name = "strum" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.52", -] - [[package]] name = "subtle" version = "2.5.0" @@ -2847,6 +2953,16 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -2948,9 +3064,6 @@ name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" -dependencies = [ - "serde", -] [[package]] name = "toml_edit" @@ -2960,20 +3073,7 @@ checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap 2.2.5", "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" -dependencies = [ - "indexmap 2.2.5", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.6.5", + "winnow", ] [[package]] @@ -3156,6 +3256,16 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3263,6 +3373,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3448,15 +3567,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winnow" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" -dependencies = [ - "memchr", -] - [[package]] name = "winreg" version = "0.50.0" diff --git a/Cargo.toml b/Cargo.toml index 9404f3a..348a857 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ clap = { version = "4.5.4", default-features = false } chrono = { version = "0.4.31", default-features = false } crypto-bigint = { version = "0.5.5", default-features = false } crossbeam = { version = "0.8.4", default-features = false } +criterion = { version = "0.5.1", default-features = false } curve25519-dalek = { version = "4.1.1", default-features = false } dalek-ff-group = { git = "https://github.com/Cuprate/serai.git", rev = "347d4cf", default-features = false } dirs = { version = "5.0.1", default-features = false } diff --git a/database/benchmark/Cargo.toml b/database/benchmark/Cargo.toml index 8baf898..70b2227 100644 --- a/database/benchmark/Cargo.toml +++ b/database/benchmark/Cargo.toml @@ -2,7 +2,7 @@ name = "cuprate-database-benchmark" version = "0.0.0" edition = "2021" -description = "Benchmarking binary for Cuprate's database(s)" +description = "Benchmarking for Cuprate's database abstraction" license = "MIT" authors = ["hinto-janai"] repository = "https://github.com/Cuprate/cuprate/tree/main/database/benchmark" @@ -12,16 +12,12 @@ keywords = ["cuprate", "database", "benchmark"] default = [] [dependencies] -anyhow = { workspace = true } -cfg-if = { workspace = true } -clap = { workspace = true, features = ["default", "derive", "cargo"] } -cuprate-database = { path = "../", features = ["default", "serde"] } +criterion = { workspace = true } +cuprate-database = { path = "../", features = ["default"] } cuprate-helper = { path = "../../helper", features = ["fs", "thread"] } -serde = { workspace = true, features = ["default", "derive"] } -serde_json = { workspace = true, features = ["default"] } -strum = { workspace = true, features = ["default", "derive"] } -tracing = { workspace = true } -toml_edit = { workspace = true, features = ["default", "serde"] } -rayon = { workspace = true, optional = true } +# cuprate-types = { path = "../../types", features = ["service"] } +tempfile = { version = "3.10.0" } -[dev-dependencies] \ No newline at end of file +[[bench]] +name = "lib" +harness = false \ No newline at end of file diff --git a/database/benchmark/benches/env.rs b/database/benchmark/benches/env.rs new file mode 100644 index 0000000..646bdc1 --- /dev/null +++ b/database/benchmark/benches/env.rs @@ -0,0 +1,204 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Import +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +use cuprate_database::{ConcreteEnv, Env, EnvInner, TxRo, TxRw}; + +use crate::tmp_concrete_env; + +//---------------------------------------------------------------------------------------------------- +/// [`Env::open`]. +fn open(c: &mut Criterion) { + c.bench_function("tmp_concrete_env", |b| b.iter(|| tmp_concrete_env())); +} + +/// Create and commit read-only transactions. +fn tx_ro(c: &mut Criterion) { + let (env, _tempdir) = tmp_concrete_env(); + let env_inner = env.env_inner(); + + c.bench_function("tx_ro", |b| { + b.iter(|| { + let tx_ro = env_inner.tx_ro().unwrap(); + TxRo::commit(tx_ro).unwrap(); + }) + }); +} + +/// Create and commit read/write transactions. +fn tx_rw(c: &mut Criterion) { + let (env, _tempdir) = tmp_concrete_env(); + let env_inner = env.env_inner(); + + c.bench_function("tx_rw", |b| { + b.iter(|| { + let tx_rw = env_inner.tx_rw().unwrap(); + TxRw::commit(tx_rw).unwrap(); + }) + }); +} + +/// Open all database tables in read-only mode. +fn open_tables(c: &mut Criterion) { + let (env, _tempdir) = tmp_concrete_env(); + let env_inner = env.env_inner(); + let tx_ro = env_inner.tx_ro().unwrap(); + + c.bench_function("open_tables", |b| { + b.iter(|| { + env_inner.open_tables(&tx_ro).unwrap(); + }) + }); +} + +/// Open all database tables in read/write mode. +fn open_tables_mut(c: &mut Criterion) { + let (env, _tempdir) = tmp_concrete_env(); + let env_inner = env.env_inner(); + let mut tx_rw = env_inner.tx_ro().unwrap(); + + c.bench_function("open_tables_mut", |b| { + b.iter(|| { + env_inner.open_tables_mut(&mut tx_rw).unwrap(); + }) + }); +} + +/// Test `Env` resizes. +#[test] +fn resize() { + // This test is only valid for `Env`'s that need to resize manually. + if !ConcreteEnv::MANUAL_RESIZE { + return; + } + + let (env, _tempdir) = tmp_concrete_env(); + + // Resize by the OS page size. + let page_size = crate::resize::page_size(); + let old_size = env.current_map_size(); + env.resize_map(Some(ResizeAlgorithm::FixedBytes(page_size))); + + // Assert it resized exactly by the OS page size. + let new_size = env.current_map_size(); + assert_eq!(new_size, old_size + page_size.get()); +} + +/// Test that `Env`'s that don't manually resize. +#[test] +#[should_panic = "unreachable"] +fn non_manual_resize_1() { + if ConcreteEnv::MANUAL_RESIZE { + unreachable!(); + } else { + let (env, _tempdir) = tmp_concrete_env(); + env.resize_map(None); + } +} + +#[test] +#[should_panic = "unreachable"] +fn non_manual_resize_2() { + if ConcreteEnv::MANUAL_RESIZE { + unreachable!(); + } else { + let (env, _tempdir) = tmp_concrete_env(); + env.current_map_size(); + } +} + +/// Test all `DatabaseR{o,w}` operations. +#[test] +fn db_read_write() { + let (env, _tempdir) = tmp_concrete_env(); + let env_inner = env.env_inner(); + let mut tx_rw = env_inner.tx_rw().unwrap(); + let mut table = env_inner.open_db_rw::(&mut tx_rw).unwrap(); + + /// The (1st) key. + const KEY: PreRctOutputId = PreRctOutputId { + amount: 1, + amount_index: 123, + }; + /// The expected value. + const VALUE: Output = Output { + key: [35; 32], + height: 45_761_798, + output_flags: 0, + tx_idx: 2_353_487, + }; + + /// Assert 2 `Output`'s are equal, and that accessing + /// their fields don't result in an unaligned panic. + fn assert_same(output: Output) { + assert_eq!(output, VALUE); + assert_eq!(output.key, VALUE.key); + assert_eq!(output.height, VALUE.height); + assert_eq!(output.output_flags, VALUE.output_flags); + assert_eq!(output.tx_idx, VALUE.tx_idx); + } + + // Insert `0..100` keys. + let mut key = KEY; + for i in 0..100 { + table.put(&key, &VALUE).unwrap(); + key.amount += 1; + } + + // Assert the 1st key is there. + { + let value: Output = table.get(&KEY).unwrap(); + assert_same(value); + } + + // Assert the whole range is there. + { + let range = table.get_range(..).unwrap(); + let mut i = 0; + for result in range { + let value: Output = result.unwrap(); + assert_same(value); + + i += 1; + } + assert_eq!(i, 100); + } + + // `get_range()` tests. + let mut key = KEY; + key.amount += 100; + let range = KEY..key; + + // Assert count is correct. + assert_eq!(100, table.get_range(range.clone()).unwrap().count()); + + // Assert each returned value from the iterator is owned. + { + let mut iter = table.get_range(range.clone()).unwrap(); + let value: Output = iter.next().unwrap().unwrap(); // 1. take value out + drop(iter); // 2. drop the `impl Iterator + 'a` + assert_same(value); // 3. assert even without the iterator, the value is alive + } + + // Assert each value is the same. + { + let mut iter = table.get_range(range).unwrap(); + for _ in 0..100 { + let value: Output = iter.next().unwrap().unwrap(); + assert_same(value); + } + } + + // Assert deleting works. + table.delete(&KEY).unwrap(); + let value = table.get(&KEY); + assert!(matches!(value, Err(RuntimeError::KeyNotFound))); +} + +pub fn criterion_benchmark(c: &mut Criterion) { + c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20)))); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/database/benchmark/benches/main.rs b/database/benchmark/benches/main.rs new file mode 100644 index 0000000..a0a7052 --- /dev/null +++ b/database/benchmark/benches/main.rs @@ -0,0 +1,88 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Lints +// Forbid lints. +// Our code, and code generated (e.g macros) cannot overrule these. +#![forbid( + // `unsafe` is allowed but it _must_ be + // commented with `SAFETY: reason`. + clippy::undocumented_unsafe_blocks, + + // Never. + unused_unsafe, + redundant_semicolons, + unused_allocation, + coherence_leak_check, + while_true, + clippy::missing_docs_in_private_items, + + // Maybe can be put into `#[deny]`. + unconditional_recursion, + for_loops_over_fallibles, + unused_braces, + unused_doc_comments, + unused_labels, + keyword_idents, + non_ascii_idents, + variant_size_differences, + single_use_lifetimes, + + // Probably can be put into `#[deny]`. + future_incompatible, + let_underscore, + break_with_label_and_loop, + duplicate_macro_attributes, + exported_private_dependencies, + large_assignments, + overlapping_range_endpoints, + semicolon_in_expressions_from_macros, + noop_method_call, + unreachable_pub, +)] +// Deny lints. +// Some of these are `#[allow]`'ed on a per-case basis. +#![deny( + clippy::all, + clippy::correctness, + clippy::suspicious, + clippy::style, + clippy::complexity, + clippy::perf, + clippy::pedantic, + clippy::nursery, + clippy::cargo, + unused_mut, + missing_docs, + deprecated, + unused_comparisons, + nonstandard_style +)] +#![allow(unreachable_code, unused_variables, dead_code, unused_imports)] // TODO: remove +#![allow( + // FIXME: this lint affects crates outside of + // `database/` for some reason, allow for now. + clippy::cargo_common_metadata, + + // FIXME: adding `#[must_use]` onto everything + // might just be more annoying than useful... + // although it is sometimes nice. + clippy::must_use_candidate, + + // TODO: should be removed after all `todo!()`'s are gone. + clippy::diverging_sub_expression, + + clippy::module_name_repetitions, + clippy::module_inception, + clippy::redundant_pub_crate, + clippy::option_if_let_else, +)] +// Allow some lints when running in debug mode. +#![cfg_attr(debug_assertions, allow(clippy::todo, clippy::multiple_crate_versions))] + +use criterion::criterion_main; + +mod env; + +criterion_main! { + env::benches, +} diff --git a/database/benchmark/src/bench.rs b/database/benchmark/src/bench.rs deleted file mode 100644 index 0ba60a0..0000000 --- a/database/benchmark/src/bench.rs +++ /dev/null @@ -1,141 +0,0 @@ -//! TODO - -//---------------------------------------------------------------------------------------------------- Import -use std::{ - borrow::Cow, - collections::BTreeSet, - path::{Path, PathBuf}, - time::Instant, -}; - -use serde::{Deserialize, Serialize}; -use strum::{ - Display, EnumCount, EnumIs, EnumIter, EnumString, IntoStaticStr, VariantArray, VariantIterator, -}; - -use cuprate_database::ConcreteEnv; - -use crate::config::Config; - -//---------------------------------------------------------------------------------------------------- TODO -/// TODO -#[derive( - Clone, - Copy, - PartialEq, - PartialOrd, - Ord, - Eq, - Serialize, - Deserialize, - Debug, - Hash, - Display, - EnumCount, - EnumIs, - EnumIter, - EnumString, - IntoStaticStr, - VariantArray, -)] -pub(crate) enum Benchmark { - /// Maps to [`env_open`]. - EnvOpen, -} - -impl Benchmark { - /// Map [`Benchmark`] to the proper benchmark function. - #[inline(always)] - fn benchmark_fn(self) -> fn(&ConcreteEnv) { - match self { - Self::EnvOpen => env_open, - } - } -} - -//---------------------------------------------------------------------------------------------------- Stats -/// TODO -#[derive(Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize, Debug)] -struct Stats { - /// Timings for [`env_open`]. - env_open: Option, -} - -impl Stats { - /// Create a new [`Stats`] with no benchmark timings. - const fn new() -> Self { - Self { env_open: None } - } - - /// This maps [`Benchmark`]s to a specific field in [`Stats`] - /// which then gets updated to the passed `time`. - #[inline(always)] - fn update_benchmark_time(&mut self, benchmark: Benchmark, time: f32) { - *match benchmark { - Benchmark::EnvOpen => &mut self.env_open, - } = Some(time); - } -} - -//---------------------------------------------------------------------------------------------------- TODO -/// TODO -pub(crate) struct Benchmarker { - /// TODO - env: ConcreteEnv, - /// TODO - config: Config, - /// TODO - stats: Stats, -} - -//---------------------------------------------------------------------------------------------------- Drop -impl Drop for Benchmarker { - fn drop(&mut self) { - let stats_json = serde_json::to_string_pretty(&self.stats).unwrap(); - println!("{stats_json}"); - } -} - -//---------------------------------------------------------------------------------------------------- Impl -impl Benchmarker { - /// TODO - #[cold] - #[inline(never)] - pub(crate) const fn new(env: ConcreteEnv, config: Config) -> Self { - Self { - env, - config, - stats: Stats::new(), - } - } - - /// Start all benchmark that are selected by the user. - /// `main()` calls this once. - #[cold] - #[inline(never)] - pub(crate) fn bench_all(mut self) { - for benchmark in &self.config.benchmark_set { - bench(*benchmark, &self.env, &mut self.stats); - } - } -} - -//---------------------------------------------------------------------------------------------------- Benchmark functions -/// TODO -#[inline(always)] -fn bench(benchmark: Benchmark, env: &ConcreteEnv, stats: &mut Stats) { - // Start the benchmark timer. - let instant = Instant::now(); - - // Benchmark. - benchmark.benchmark_fn()(env); - - // Update the time. - stats.update_benchmark_time(benchmark, instant.elapsed().as_secs_f32()); -} - -/// TODO -#[inline(always)] -fn env_open(env: &ConcreteEnv) { - println!("TODO"); -} diff --git a/database/benchmark/src/cli.rs b/database/benchmark/src/cli.rs deleted file mode 100644 index 43a2b6a..0000000 --- a/database/benchmark/src/cli.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! Command line argument parsing and handling. - -//---------------------------------------------------------------------------------------------------- Use -use std::path::PathBuf; - -use clap::{crate_name, Args, Parser, Subcommand}; - -use cuprate_helper::fs::cuprate_database_dir; - -use crate::config::Config; - -//---------------------------------------------------------------------------------------------------- Constants - -//---------------------------------------------------------------------------------------------------- CLI Parser (clap) -/// `struct` encompassing all possible CLI argument values. -/// -/// This gets called by `main()` once, at the very beginning and is responsible for: -/// - parsing/validating input values -/// - routing certain `--flags` to function paths (and exiting) -/// - possibly handing `Config` back off to `main()` for continued execution -#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Parser)] -#[allow(clippy::struct_excessive_bools)] -pub struct Cli { - //------------------------------------------------------------------------------ Config options - /// Set filter level for console logs. - #[arg( - long, - value_name = "OFF|ERROR|INFO|WARN|DEBUG|TRACE", - verbatim_doc_comment - )] - pub(crate) log_level: Option, // FIXME: tracing::Level{Filter} doesn't work with clap? - - /// TODO - #[arg(long, value_name = "PATH", verbatim_doc_comment)] - pub(crate) config: Option, - - /// TODO - #[arg(long, value_name = "PATH", verbatim_doc_comment)] - pub(crate) db_config: Option, - - //------------------------------------------------------------------------------ Early Return - // These are flags that do something then immediately return. - // - // Regardless of other flags provided, these will force a return. - #[arg(long, verbatim_doc_comment)] - /// Print the configuration `cuprate-database-benchmark` - /// would have used, but don't actually startup. - /// - /// This will go through the regular process of: - /// - Reading command-line - /// - Reading disk for config - /// - /// and then print the config out as TOML, and exit. - pub(crate) dry_run: bool, - - #[arg(long, verbatim_doc_comment)] - /// Print the PATH used by `cuprate-database`. - pub(crate) path: bool, - - #[arg(long, verbatim_doc_comment)] - /// Delete all `cuprated-database` files that are on disk. - /// - /// This deletes the PATH returned by `--path`, aka, the - /// directory that stores all database related files. - pub(crate) delete: bool, - - #[arg(short, long)] - /// Print various stats and exit. - pub(crate) version: bool, -} - -//---------------------------------------------------------------------------------------------------- CLI default -// impl Default for Cli { -// fn default() -> Self { -// Self { -// log_level: None, -// db_config: None, -// dry_run: false, -// path: false, -// delete: false, -// version: false, -// } -// } -// } - -//---------------------------------------------------------------------------------------------------- CLI argument handling -impl Cli { - /// `main()` calls this once. - pub fn init() -> Self { - match Self::parse().handle_args() { - Ok(cli) => cli, - Err(exit_code) => std::process::exit(exit_code), - } - } - - /// Handle all the values, routing code, and exiting early if needed. - /// - /// The order of the `if`'s are the precedence of the `--flags`'s - /// themselves, e.g `--version` will execute over all else. - fn handle_args(self) -> Result { - // TODO: - // Calling `exit()` on each branch could - // be replaced with something better, - // although exit codes must be maintained. - - //-------------------------------------------------- Version. - if self.version { - todo!(); - return Err(0); - } - - //-------------------------------------------------- Path. - if self.path { - let path = cuprate_database_dir(); - println!("{}", path.display()); - return Err(0); - } - - //-------------------------------------------------- Delete. - if self.delete { - let path = cuprate_database_dir(); - - if path.exists() { - println!( - "{}: PATH does not exist '{}'", - crate_name!(), - path.display() - ); - return Err(0); - } - - match std::fs::remove_dir_all(path) { - Ok(()) => { - println!("{}: deleted '{}'", crate_name!(), path.display()); - return Err(0); - } - Err(e) => { - eprintln!("{} error: {} - {e}", crate_name!(), path.display()); - return Err(1); - } - } - } - - //-------------------------------------------------- Return `Config` to `main()` - Ok(self) - } -} diff --git a/database/benchmark/src/config.rs b/database/benchmark/src/config.rs deleted file mode 100644 index 0f23bbf..0000000 --- a/database/benchmark/src/config.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! TODO - -//---------------------------------------------------------------------------------------------------- Import -use std::{ - borrow::Cow, - collections::BTreeSet, - path::{Path, PathBuf}, -}; - -use serde::{Deserialize, Serialize}; -use strum::IntoEnumIterator; - -use crate::{bench::Benchmark, cli::Cli}; - -//---------------------------------------------------------------------------------------------------- Config -/// TODO -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Serialize, Deserialize)] -pub(crate) struct Config { - /// TODO - pub(crate) iterations: usize, - /// TODO - pub(crate) benchmark_set: BTreeSet, -} - -impl Config { - /// Create a new [`Config`] with sane default settings. - pub(crate) fn new() -> Self { - Self { - iterations: 100_000, - benchmark_set: Benchmark::iter().collect(), - } - } - - /// TODO - pub(crate) fn merge(&mut self, cli: &Cli) { - todo!() - } -} - -impl Default for Config { - fn default() -> Self { - Self::new() - } -} diff --git a/database/benchmark/src/constants.rs b/database/benchmark/src/constants.rs deleted file mode 100644 index 83cdeb8..0000000 --- a/database/benchmark/src/constants.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! General constants used throughout `cuprate-database`. - -//---------------------------------------------------------------------------------------------------- Import -use cfg_if::cfg_if; - -//---------------------------------------------------------------------------------------------------- Error Messages - -//---------------------------------------------------------------------------------------------------- Tests -#[cfg(test)] -mod test {} diff --git a/database/benchmark/src/free.rs b/database/benchmark/src/free.rs deleted file mode 100644 index a9b9385..0000000 --- a/database/benchmark/src/free.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! General free functions (related to the database). -//! -//! TODO. - -//---------------------------------------------------------------------------------------------------- Import - -//---------------------------------------------------------------------------------------------------- Free functions - -//---------------------------------------------------------------------------------------------------- Tests -#[cfg(test)] -mod test { - // use super::*; -} diff --git a/database/benchmark/src/helper.rs b/database/benchmark/src/helper.rs new file mode 100644 index 0000000..51bf099 --- /dev/null +++ b/database/benchmark/src/helper.rs @@ -0,0 +1,20 @@ +//! TODO + +// TODO: set-up env based on config + +//---------------------------------------------------------------------------------------------------- Import +use cuprate_database::{config::Config, ConcreteEnv, Env}; + +//---------------------------------------------------------------------------------------------------- Tests +/// Create an `Env` in a temporarily directory. +/// The directory is automatically removed after the `TempDir` is dropped. +/// +/// TODO: changing this to `-> impl Env` causes lifetime errors... +#[allow(clippy::missing_panics_doc)] +pub fn tmp_concrete_env() -> (ConcreteEnv, tempfile::TempDir) { + let tempdir = tempfile::tempdir().unwrap(); + let config = Config::low_power(Some(tempdir.path().into())); + let env = ConcreteEnv::open(config).unwrap(); + + (env, tempdir) +} diff --git a/database/benchmark/src/lib.rs b/database/benchmark/src/lib.rs new file mode 100644 index 0000000..91ffb3f --- /dev/null +++ b/database/benchmark/src/lib.rs @@ -0,0 +1,83 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Lints +// Forbid lints. +// Our code, and code generated (e.g macros) cannot overrule these. +#![forbid( + // `unsafe` is allowed but it _must_ be + // commented with `SAFETY: reason`. + clippy::undocumented_unsafe_blocks, + + // Never. + unused_unsafe, + redundant_semicolons, + unused_allocation, + coherence_leak_check, + while_true, + clippy::missing_docs_in_private_items, + + // Maybe can be put into `#[deny]`. + unconditional_recursion, + for_loops_over_fallibles, + unused_braces, + unused_doc_comments, + unused_labels, + keyword_idents, + non_ascii_idents, + variant_size_differences, + single_use_lifetimes, + + // Probably can be put into `#[deny]`. + future_incompatible, + let_underscore, + break_with_label_and_loop, + duplicate_macro_attributes, + exported_private_dependencies, + large_assignments, + overlapping_range_endpoints, + semicolon_in_expressions_from_macros, + noop_method_call, + unreachable_pub, +)] +// Deny lints. +// Some of these are `#[allow]`'ed on a per-case basis. +#![deny( + clippy::all, + clippy::correctness, + clippy::suspicious, + clippy::style, + clippy::complexity, + clippy::perf, + clippy::pedantic, + clippy::nursery, + clippy::cargo, + unused_mut, + missing_docs, + deprecated, + unused_comparisons, + nonstandard_style +)] +#![allow(unreachable_code, unused_variables, dead_code, unused_imports)] // TODO: remove +#![allow( + // FIXME: this lint affects crates outside of + // `database/` for some reason, allow for now. + clippy::cargo_common_metadata, + + // FIXME: adding `#[must_use]` onto everything + // might just be more annoying than useful... + // although it is sometimes nice. + clippy::must_use_candidate, + + // TODO: should be removed after all `todo!()`'s are gone. + clippy::diverging_sub_expression, + + clippy::module_name_repetitions, + clippy::module_inception, + clippy::redundant_pub_crate, + clippy::option_if_let_else, +)] +// Allow some lints when running in debug mode. +#![cfg_attr(debug_assertions, allow(clippy::todo, clippy::multiple_crate_versions))] + +mod helper; +pub use helper::tmp_concrete_env; diff --git a/database/benchmark/src/main.rs b/database/benchmark/src/main.rs deleted file mode 100644 index 5be3b9a..0000000 --- a/database/benchmark/src/main.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! `cuprate-database` testing & benchmarking binary. - -//---------------------------------------------------------------------------------------------------- Lints -// Forbid lints. -// Our code, and code generated (e.g macros) cannot overrule these. -#![deny( - clippy::all, - clippy::correctness, - clippy::suspicious, - clippy::style, - clippy::complexity, - clippy::perf, - clippy::pedantic, - clippy::nursery, - clippy::cargo, - clippy::undocumented_unsafe_blocks, - unused_unsafe, - redundant_semicolons, - unused_allocation, - coherence_leak_check, - while_true, - clippy::missing_docs_in_private_items, - unconditional_recursion, - for_loops_over_fallibles, - unused_doc_comments, - unused_labels, - keyword_idents, - non_ascii_idents, - variant_size_differences, - single_use_lifetimes, - future_incompatible, - let_underscore, - break_with_label_and_loop, - duplicate_macro_attributes, - exported_private_dependencies, - large_assignments, - overlapping_range_endpoints, - semicolon_in_expressions_from_macros, - noop_method_call, - unused_mut, - missing_docs, - deprecated, - unused_comparisons, - nonstandard_style -)] -#![allow(unreachable_code, unused_variables, dead_code, unused_imports)] // TODO: remove -#![allow( - // FIXME: this lint affects crates outside of - // `database/` for some reason, allow for now. - clippy::cargo_common_metadata, - - // FIXME: adding `#[must_use]` onto everything - // might just be more annoying than useful... - // although it is sometimes nice. - clippy::must_use_candidate, - - // TODO: should be removed after all `todo!()`'s are gone. - clippy::diverging_sub_expression, - - clippy::inline_always, - clippy::module_name_repetitions, - clippy::module_inception, - clippy::redundant_pub_crate, - clippy::option_if_let_else, -)] -// Allow some lints when running in debug mode. -#![cfg_attr(debug_assertions, allow(clippy::todo, clippy::multiple_crate_versions))] - -//---------------------------------------------------------------------------------------------------- Public API -// Import private modules, export public types. -// -// Documentation for each module is located in the respective file. -mod bench; -mod cli; -mod config; -mod constants; -mod free; - -//---------------------------------------------------------------------------------------------------- Private - -//---------------------------------------------------------------------------------------------------- Import -use cuprate_database::Env; - -use crate::{cli::Cli, config::Config}; - -//---------------------------------------------------------------------------------------------------- Main -fn main() { - // Handle CLI arguments. - let cli: Cli = if std::env::args_os().len() > 1 { - // Some arguments were passed, run all the `clap` code. - Cli::init() - } else { - // No arguments were passed, use the default. - Cli::default() - }; - - // Load the benchmark config. - let config = if let Some(path) = cli.config.as_ref() { - let bytes = std::fs::read(path).unwrap(); - let mut disk: Config = toml_edit::de::from_slice(&bytes).unwrap(); - disk.merge(&cli); - disk - } else { - Config::new() - }; - - // Load the database config. - let db_config = if let Some(path) = cli.db_config { - let bytes = std::fs::read(path).unwrap(); - toml_edit::de::from_slice(&bytes).unwrap() - } else { - cuprate_database::config::Config::new(None) - }; - - // Print config before starting. - eprintln!("cuprate-database-benchmark configuration:\n{config:#?}"); - - // If `dry_run`, exit cleanly. - if cli.dry_run { - std::process::exit(0); - } - - // Create database. - let env = cuprate_database::ConcreteEnv::open(db_config).unwrap(); - - // Start benchmarking/tests. - let mut benchmarker = crate::bench::Benchmarker::new(env, todo!()); - benchmarker.bench_all(); -}