Merge branch 'db-bench' into criterion

This commit is contained in:
hinto.janai 2024-11-28 09:05:09 -05:00
commit 3f2fc0eb22
No known key found for this signature in database
GPG key ID: D47CE05FA175A499
12 changed files with 927 additions and 5 deletions

13
Cargo.lock generated
View file

@ -798,6 +798,19 @@ dependencies = [
name = "cuprate-constants" name = "cuprate-constants"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "cuprate-criterion-database"
version = "0.0.0"
dependencies = [
"criterion",
"cuprate-blockchain",
"cuprate-database",
"cuprate-helper",
"function_name",
"rand",
"tempfile",
]
[[package]] [[package]]
name = "cuprate-criterion-example" name = "cuprate-criterion-example"
version = "0.0.0" version = "0.0.0"

View file

@ -3,6 +3,7 @@ resolver = "2"
members = [ members = [
# Binaries # Binaries
"binaries/cuprated", "binaries/cuprated",
# Benchmarks # Benchmarks
"benches/benchmark/bin", "benches/benchmark/bin",
"benches/benchmark/lib", "benches/benchmark/lib",
@ -10,6 +11,8 @@ members = [
"benches/criterion/example", "benches/criterion/example",
"benches/criterion/cuprate-json-rpc", "benches/criterion/cuprate-json-rpc",
"benches/criterion/cuprate-helper", "benches/criterion/cuprate-helper",
"benches/criterion/cuprate-database",
# Consensus # Consensus
"consensus", "consensus",
"consensus/context", "consensus/context",

View file

@ -0,0 +1,41 @@
[package]
name = "cuprate-criterion-database"
version = "0.0.0"
edition = "2021"
description = "Criterion benchmarking for cuprate-database"
license = "MIT"
authors = ["hinto-janai"]
repository = "https://github.com/Cuprate/cuprate/tree/main/benches/criterion/cuprate-database"
keywords = ["cuprate", "database", "benchmark"]
[features]
default = ["heed"]
heed = ["cuprate-database/heed", "cuprate-blockchain/heed"]
redb = ["cuprate-database/redb", "cuprate-blockchain/redb"]
[dependencies]
# FIXME:
# Some crates/features that are unused here but
# needed in other crates are pulled in, see:
# - <https://github.com/Cuprate/cuprate/issues/325>
#
# Remove:
# - rand
# - cuprate-blockchain/asynch
# - cuprate-blockchain/tx
criterion = { workspace = true }
cuprate-database = { path = "../../../storage/database" }
cuprate-blockchain = { path = "../../../storage/blockchain", features = ["service"] }
cuprate-helper = { path = "../../../helper", features = ["asynch", "fs", "thread", "tx"] }
function_name = { workspace = true }
tempfile = { workspace = true }
rand = { workspace = true, features = ["std", "std_rng"] }
[[bench]]
name = "main"
harness = false
[lints]
workspace = true

View file

@ -0,0 +1,48 @@
# `cuprate-database-benchmark`
This is a benchmarking suite that allows testing/benchmarking `cuprate-database` with [`criterion`](https://bheisler.github.io/criterion.rs/book/criterion_rs.html).
For more information on `cargo bench` and `criterion`:
- https://doc.rust-lang.org/cargo/commands/cargo-bench.html
- https://bheisler.github.io/criterion.rs/book/criterion_rs.html
<!-- Did you know markdown automatically increments number lists, even if they are all 1...? -->
1. [Usage](#Usage)
1. [File Structure](#file-structure)
- [`src/`](#src)
- [`benches/`](#benches)
# Usage
Ensure the system is as quiet as possible (no background tasks) before starting and during the benchmarks.
To start all benchmarks, run:
```bash
cargo bench --package cuprate-database-benchmarks
```
# File Structure
A quick reference of the structure of the folders & files in `cuprate-database`.
Note that `lib.rs/mod.rs` files are purely for re-exporting/visibility/lints, and contain no code. Each sub-directory has a corresponding `mod.rs`.
## `src/`
The top-level `src/` files.
The actual `cuprate-database-benchmark` library crate is just used as a helper for the benchmarks within `benches/`.
| File | Purpose |
|---------------------|---------|
| `helper.rs` | Helper functions
## `benches/`
The actual benchmarks.
Each file represents some logical benchmark grouping.
| File | Purpose |
|-----------------------|---------|
| `db.rs` | `trait Database{Ro,Rw,Iter}` benchmarks
| `db_multi_thread.rs` | Same as `db.rs` but multi-threaded
| `env.rs` | `trait {Env, EnvInner, TxR{o,w}, Tables[Mut]}` benchmarks
| `env_multi_thread.rs` | Same as `env.rs` but multi-threaded
| `service.rs` | `cuprate_database::service` benchmarks
| `storable.rs` | `trait Storable` benchmarks

View file

@ -0,0 +1,444 @@
//! Database operations.
//!
//! This module tests the functions from:
//! - [`cuprate_database::DatabaseRo`]
//! - [`cuprate_database::DatabaseRw`]
//! - [`cuprate_database::DatabaseIter`]
#![allow(unused_crate_dependencies, unused_attributes)]
#![expect(clippy::significant_drop_tightening)]
use std::time::Instant;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use function_name::named;
use cuprate_blockchain::{
tables::Outputs,
types::{Output, PreRctOutputId},
};
use cuprate_database::{DatabaseIter, DatabaseRo, DatabaseRw, Env, EnvInner};
use cuprate_criterion_database::{TmpEnv, KEY, VALUE};
criterion_group! {
name = benches;
config = Criterion::default();
targets =
// `DatabaseRo`
ro_get,
ro_len,
ro_first,
ro_last,
ro_is_empty,
ro_contains,
// `DatabaseRo` with a `TxRw`
rw_get,
rw_len,
rw_first,
rw_last,
rw_is_empty,
rw_contains,
// `DatabaseIter`
get_range,
iter,
keys,
values,
// `DatabaseRw`
put,
delete,
pop_first,
pop_last,
take,
}
criterion_main!(benches);
//---------------------------------------------------------------------------------------------------- DatabaseRo
// Read-only table operations.
// This uses `TxRw + TablesMut` briefly to insert values, then
// uses `TxRo + Tables` for the actual operation.
//
// See further below for using `TxRw + TablesMut` on the same operations.
/// [`DatabaseRo::get`]
#[named]
fn ro_get(c: &mut Criterion) {
let env = TmpEnv::new().with_key_value();
let env_inner = env.env.env_inner();
let tx_ro = env_inner.tx_ro().unwrap();
let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
let _: Output = table.get(black_box(&KEY)).unwrap();
});
});
}
/// [`DatabaseRo::len`]
#[named]
fn ro_len(c: &mut Criterion) {
let env = TmpEnv::new().with_key_value();
let env_inner = env.env.env_inner();
let tx_ro = env_inner.tx_ro().unwrap();
let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
black_box(table.len()).unwrap();
});
});
}
/// [`DatabaseRo::first`]
#[named]
fn ro_first(c: &mut Criterion) {
let env = TmpEnv::new().with_key_value();
let env_inner = env.env.env_inner();
let tx_ro = env_inner.tx_ro().unwrap();
let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
let (_, _): (PreRctOutputId, Output) = black_box(table.first()).unwrap();
});
});
}
/// [`DatabaseRo::last`]
#[named]
fn ro_last(c: &mut Criterion) {
let env = TmpEnv::new().with_key_value();
let env_inner = env.env.env_inner();
let tx_ro = env_inner.tx_ro().unwrap();
let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
let (_, _): (PreRctOutputId, Output) = black_box(table.last()).unwrap();
});
});
}
/// [`DatabaseRo::is_empty`]
#[named]
fn ro_is_empty(c: &mut Criterion) {
let env = TmpEnv::new().with_key_value();
let env_inner = env.env.env_inner();
let tx_ro = env_inner.tx_ro().unwrap();
let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
black_box(table.is_empty()).unwrap();
});
});
}
/// [`DatabaseRo::contains`]
#[named]
fn ro_contains(c: &mut Criterion) {
let env = TmpEnv::new().with_key_value();
let env_inner = env.env.env_inner();
let tx_ro = env_inner.tx_ro().unwrap();
let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
table.contains(black_box(&KEY)).unwrap();
});
});
}
//---------------------------------------------------------------------------------------------------- DatabaseRo (TxRw)
// These are the same benchmarks as above, but it uses a
// `TxRw` and a `TablesMut` instead to ensure our read/write tables
// using read operations perform the same as normal read-only tables.
/// [`DatabaseRw::get`]
#[named]
fn rw_get(c: &mut Criterion) {
let env = TmpEnv::new().with_key_value();
let env_inner = env.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
let table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
let _: Output = table.get(black_box(&KEY)).unwrap();
});
});
}
/// [`DatabaseRw::len`]
#[named]
fn rw_len(c: &mut Criterion) {
let env = TmpEnv::new().with_key_value();
let env_inner = env.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
let table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
black_box(table.len()).unwrap();
});
});
}
/// [`DatabaseRw::first`]
#[named]
fn rw_first(c: &mut Criterion) {
let env = TmpEnv::new().with_key_value();
let env_inner = env.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
let table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
let (_, _): (PreRctOutputId, Output) = black_box(table.first()).unwrap();
});
});
}
/// [`DatabaseRw::last`]
#[named]
fn rw_last(c: &mut Criterion) {
let env = TmpEnv::new().with_key_value();
let env_inner = env.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
let table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
let (_, _): (PreRctOutputId, Output) = black_box(table.last()).unwrap();
});
});
}
/// [`DatabaseRw::is_empty`]
#[named]
fn rw_is_empty(c: &mut Criterion) {
let env = TmpEnv::new().with_key_value();
let env_inner = env.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
let table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
black_box(table.is_empty()).unwrap();
});
});
}
/// [`DatabaseRw::contains`]
#[named]
fn rw_contains(c: &mut Criterion) {
let env = TmpEnv::new().with_key_value();
let env_inner = env.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
let table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
table.contains(black_box(&KEY)).unwrap();
});
});
}
//---------------------------------------------------------------------------------------------------- DatabaseIter
/// [`DatabaseIter::get_range`]
#[named]
fn get_range(c: &mut Criterion) {
let env = TmpEnv::new().with_key_value_100();
let env_inner = env.env.env_inner();
let tx_ro = env_inner.tx_ro().unwrap();
let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
let range = table.get_range(black_box(..)).unwrap();
for result in range {
let _: Output = black_box(result.unwrap());
}
});
});
}
/// [`DatabaseIter::iter`]
#[named]
fn iter(c: &mut Criterion) {
let env = TmpEnv::new().with_key_value_100();
let env_inner = env.env.env_inner();
let tx_ro = env_inner.tx_ro().unwrap();
let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
let iter = black_box(table.iter()).unwrap();
for result in iter {
let _: (PreRctOutputId, Output) = black_box(result.unwrap());
}
});
});
}
/// [`DatabaseIter::keys`]
#[named]
fn keys(c: &mut Criterion) {
let env = TmpEnv::new().with_key_value_100();
let env_inner = env.env.env_inner();
let tx_ro = env_inner.tx_ro().unwrap();
let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
let keys = black_box(table.keys()).unwrap();
for result in keys {
let _: PreRctOutputId = black_box(result.unwrap());
}
});
});
}
/// [`DatabaseIter::values`]
#[named]
fn values(c: &mut Criterion) {
let env = TmpEnv::new().with_key_value_100();
let env_inner = env.env.env_inner();
let tx_ro = env_inner.tx_ro().unwrap();
let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
let values = black_box(table.values()).unwrap();
for result in values {
let _: Output = black_box(result.unwrap());
}
});
});
}
//---------------------------------------------------------------------------------------------------- DatabaseRw
/// [`DatabaseRw::put`]
#[named]
fn put(c: &mut Criterion) {
let env = TmpEnv::new();
let env_inner = env.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
let mut key = KEY;
c.bench_function(function_name!(), |b| {
b.iter(|| {
table.put(black_box(&key), black_box(&VALUE)).unwrap();
key.amount += 1;
});
});
}
/// [`DatabaseRw::delete`]
#[named]
fn delete(c: &mut Criterion) {
let env = TmpEnv::new();
let env_inner = env.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
let mut key = KEY;
c.bench_function(function_name!(), |b| {
b.iter_custom(|iters| {
for _ in 0..iters {
table.put(&key, &VALUE).unwrap();
key.amount += 1;
}
key = KEY;
let start = Instant::now();
for _ in 0..iters {
table.delete(&key).unwrap();
key.amount += 1;
}
start.elapsed()
});
});
}
/// [`DatabaseRw::pop_first`]
#[named]
fn pop_first(c: &mut Criterion) {
let env = TmpEnv::new();
let env_inner = env.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
let mut key = KEY;
c.bench_function(function_name!(), |b| {
b.iter_custom(|iters| {
for _ in 0..iters {
table.put(&key, &VALUE).unwrap();
key.amount += 1;
}
key = KEY;
let start = Instant::now();
for _ in 0..iters {
table.pop_first().unwrap();
key.amount += 1;
}
start.elapsed()
});
});
}
/// [`DatabaseRw::pop_last`]
#[named]
fn pop_last(c: &mut Criterion) {
let env = TmpEnv::new();
let env_inner = env.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
let mut key = KEY;
c.bench_function(function_name!(), |b| {
b.iter_custom(|iters| {
for _ in 0..iters {
table.put(&key, &VALUE).unwrap();
key.amount += 1;
}
key = KEY;
let start = Instant::now();
for _ in 0..iters {
table.pop_last().unwrap();
key.amount += 1;
}
start.elapsed()
});
});
}
/// [`DatabaseRw::take`]
#[named]
fn take(c: &mut Criterion) {
let env = TmpEnv::new();
let env_inner = env.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
table.put(&KEY, &VALUE).unwrap();
let _: Output = black_box(table.take(&black_box(KEY)).unwrap());
});
});
}

View file

@ -0,0 +1,183 @@
//! [`Env`] benchmarks.
#![allow(unused_crate_dependencies, unused_attributes)]
#![expect(clippy::significant_drop_tightening)]
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use function_name::named;
use cuprate_blockchain::tables::Outputs;
use cuprate_database::{
resize::{ResizeAlgorithm, PAGE_SIZE},
ConcreteEnv, Env, EnvInner, TxRo, TxRw,
};
use cuprate_criterion_database::TmpEnv;
criterion_group! {
name = benches;
config = Criterion::default();
targets =
// open,
env_inner,
tx_ro,
tx_rw,
open_db_ro,
open_db_rw,
create_db,
resize,
current_map_size,
disk_size_bytes,
}
criterion_main!(benches);
// FIXME: This function is hard to time due to:
// - heed errors
// - "too many open files" errors
//
// /// [`Env::open`].
// #[named]
// fn open(c: &mut Criterion) {
// c.bench_function(function_name!(), |b| {
// b.iter_custom(|_| {
// let tempdir = tempfile::tempdir().unwrap();
// let config = ConfigBuilder::new(tempdir.path().to_path_buf().into()).build();
//
// let now = std::time::Instant::now();
// ConcreteEnv::open(config).unwrap();
// let elapsed = now.elapsed();
//
// tempdir.close().unwrap();
// elapsed
// });
// });
// }
/// [`Env::env_inner`].
#[named]
fn env_inner(c: &mut Criterion) {
let env = TmpEnv::new();
c.bench_function(function_name!(), |b| {
b.iter(|| {
drop(black_box(env.env.env_inner()));
});
});
}
/// [`EnvInner::tx_ro`].
#[named]
fn tx_ro(c: &mut Criterion) {
let env = TmpEnv::new();
let env_inner = env.env.env_inner();
c.bench_function(function_name!(), |b| {
b.iter(|| {
let tx_ro = black_box(env_inner.tx_ro()).unwrap();
TxRo::commit(black_box(tx_ro)).unwrap();
});
});
}
/// [`EnvInner::tx_rw`].
#[named]
fn tx_rw(c: &mut Criterion) {
let env = TmpEnv::new();
let env_inner = env.env.env_inner();
c.bench_function(function_name!(), |b| {
b.iter(|| {
let tx_rw = black_box(env_inner.tx_rw()).unwrap();
TxRw::commit(black_box(tx_rw)).unwrap();
});
});
}
/// [`EnvInner::open_db_ro`].
#[named]
fn open_db_ro(c: &mut Criterion) {
// `with_key_value()` creates the `Outputs`
// table so the `open_db_ro` below doesn't panic.
let env = TmpEnv::new().with_key_value();
let env_inner = env.env.env_inner();
let tx_ro = env_inner.tx_ro().unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
});
});
}
/// [`EnvInner::open_db_rw`].
#[named]
fn open_db_rw(c: &mut Criterion) {
let env = TmpEnv::new();
let env_inner = env.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
});
});
}
/// [`EnvInner::create_db`].
#[named]
fn create_db(c: &mut Criterion) {
let env = TmpEnv::new();
let env_inner = env.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
c.bench_function(function_name!(), |b| {
b.iter(|| {
env_inner.create_db::<Outputs>(&tx_rw).unwrap();
});
});
}
/// [`Env::resize`].
#[named]
fn resize(c: &mut Criterion) {
let env = TmpEnv::new();
// Resize env.by the OS page size.
let resize = Some(ResizeAlgorithm::FixedBytes(*PAGE_SIZE));
c.bench_function(function_name!(), |b| {
b.iter(|| {
// This test is only valid for `Env`'s that need to resize manually.
if ConcreteEnv::MANUAL_RESIZE {
env.env.resize_map(resize);
}
});
});
}
/// [`Env::current_map_size`].
#[named]
fn current_map_size(c: &mut Criterion) {
let env = TmpEnv::new();
c.bench_function(function_name!(), |b| {
b.iter(|| {
// This test is only valid for `Env`'s that need to resize manually.
if ConcreteEnv::MANUAL_RESIZE {
black_box(env.env.current_map_size());
}
});
});
}
/// [`Env::disk_size_bytes`].
#[named]
fn disk_size_bytes(c: &mut Criterion) {
let env = TmpEnv::new();
c.bench_function(function_name!(), |b| {
b.iter(|| {
black_box(env.env.disk_size_bytes()).unwrap();
});
});
}

View file

@ -0,0 +1,11 @@
#![expect(unused_crate_dependencies)]
mod db;
mod env;
mod storable;
criterion::criterion_main! {
db::benches,
env::benches,
storable::benches,
}

View file

@ -0,0 +1,66 @@
//! [`Storable`] benchmarks.
#![allow(unused_crate_dependencies, unused_attributes)]
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use function_name::named;
use cuprate_blockchain::types::{Output, PreRctOutputId};
use cuprate_database::Storable;
use cuprate_criterion_database::{KEY, VALUE};
criterion_group! {
name = benches;
config = Criterion::default();
targets =
pre_rct_output_id_as_bytes,
pre_rct_output_id_from_bytes,
output_as_bytes,
output_from_bytes
}
criterion_main!(benches);
/// [`PreRctOutputId`] cast as bytes.
#[named]
fn pre_rct_output_id_as_bytes(c: &mut Criterion) {
c.bench_function(function_name!(), |b| {
b.iter(|| {
black_box(Storable::as_bytes(black_box(&KEY)));
});
});
}
/// [`PreRctOutputId`] cast from bytes.
#[named]
fn pre_rct_output_id_from_bytes(c: &mut Criterion) {
let bytes = Storable::as_bytes(&KEY);
c.bench_function(function_name!(), |b| {
b.iter(|| {
let _: PreRctOutputId = black_box(Storable::from_bytes(black_box(bytes)));
});
});
}
/// [`Output`] cast as bytes.
#[named]
fn output_as_bytes(c: &mut Criterion) {
c.bench_function(function_name!(), |b| {
b.iter(|| {
black_box(Storable::as_bytes(black_box(&VALUE)));
});
});
}
/// [`Output`] cast from bytes.
#[named]
fn output_from_bytes(c: &mut Criterion) {
let bytes = Storable::as_bytes(&VALUE);
c.bench_function(function_name!(), |b| {
b.iter(|| {
let _: Output = black_box(Storable::from_bytes(black_box(bytes)));
});
});
}

View file

@ -0,0 +1,17 @@
//! General constants.
use cuprate_blockchain::types::{Output, OutputFlags, PreRctOutputId};
/// The (1st) key.
pub const KEY: PreRctOutputId = PreRctOutputId {
amount: 1,
amount_index: 123,
};
/// The expected value.
pub const VALUE: Output = Output {
key: [35; 32],
height: 45_761_798,
output_flags: OutputFlags::empty(),
tx_idx: 2_353_487,
};

View file

@ -0,0 +1,7 @@
#![allow(unused_crate_dependencies, reason = "used in benchmarks")]
mod constants;
mod tmp_env;
pub use constants::{KEY, VALUE};
pub use tmp_env::TmpEnv;

View file

@ -0,0 +1,88 @@
//! An [`Env`] inside a [`TempDir`].
use tempfile::TempDir;
use cuprate_blockchain::tables::Outputs;
use cuprate_database::{
config::ConfigBuilder, resize::PAGE_SIZE, ConcreteEnv, DatabaseRw, Env, EnvInner, TxRw,
};
use crate::constants::{KEY, VALUE};
/// A temporary in-memory [`Env`].
///
/// This is a [`ConcreteEnv`] that uses [`TempDir`] as the
/// backing file location - this is an in-memory file on Linux.
pub struct TmpEnv {
pub env: ConcreteEnv,
pub tempdir: TempDir,
}
impl Default for TmpEnv {
fn default() -> Self {
Self::new()
}
}
impl TmpEnv {
/// Create an `Env` in a temporary directory.
///
/// The directory is automatically removed after the [`TempDir`] is dropped.
#[expect(clippy::missing_panics_doc)]
pub fn new() -> Self {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().to_path_buf().into();
let config = ConfigBuilder::new(path).low_power().build();
let env = ConcreteEnv::open(config).unwrap();
// Resize to a very large map to prevent resize errors.
if ConcreteEnv::MANUAL_RESIZE {
// SAFETY: no write transactions exist yet.
unsafe {
env.env_inner()
.resize(PAGE_SIZE.get() * 1024 * 1024 * 1024)
.unwrap();
}
}
Self { env, tempdir }
}
/// Inserts [`KEY`] and [`VALUE`] inside the [`Outputs`] table.
#[must_use]
pub fn with_key_value(self) -> Self {
let env_inner = self.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
table.put(&KEY, &VALUE).unwrap();
drop(table);
tx_rw.commit().unwrap();
drop(env_inner);
self
}
/// Inserts [`VALUE`] inside the [`Outputs`] table 100 times.
///
/// The key is an incrementing [`KEY`], i.e. the keys are
/// `KEY + {0..99}`, each one has [`VALUE`] as the value.
#[must_use]
pub fn with_key_value_100(self) -> Self {
let env_inner = self.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
let mut key = KEY;
for _ in 0..100 {
table.put(&key, &VALUE).unwrap();
key.amount += 1;
}
drop(table);
tx_rw.commit().unwrap();
drop(env_inner);
self
}
}

View file

@ -78,11 +78,12 @@ impl Drop for ConcreteEnv {
// TODO: use tracing. // TODO: use tracing.
// <https://github.com/LMDB/lmdb/blob/b8e54b4c31378932b69f1298972de54a565185b1/libraries/liblmdb/lmdb.h#L49-L61> // <https://github.com/LMDB/lmdb/blob/b8e54b4c31378932b69f1298972de54a565185b1/libraries/liblmdb/lmdb.h#L49-L61>
let result = self.env.read().unwrap().clear_stale_readers(); drop(self.env.read().unwrap().clear_stale_readers());
match result { // let result = self.env.read().unwrap().clear_stale_readers();
Ok(n) => println!("LMDB stale readers cleared: {n}"), // match result {
Err(e) => println!("LMDB stale reader clear error: {e:?}"), // Ok(n) => println!("LMDB stale readers cleared: {n}"),
} // Err(e) => println!("LMDB stale reader clear error: {e:?}"),
// }
} }
} }