Skip to content

Commit

Permalink
benches: initial implementation (#196)
Browse files Browse the repository at this point in the history
* add readme

* readme, basic examples

* name changes, bin impl

* example, docs

* book

* add `cuprate-criterion-example`

* docs, tracing

* fix clippy

* docs

* lib readme

* json-rpc benchmarks

* add to crates.md

* add `fixme`

* fix `cargo b` failing

this `cfg()` existing makes a regular workspace `cargo b` fail

* fix cargo.toml
  • Loading branch information
hinto-janai authored Nov 25, 2024
1 parent f3c1a5c commit caa08d5
Show file tree
Hide file tree
Showing 37 changed files with 1,188 additions and 43 deletions.
313 changes: 312 additions & 1 deletion Cargo.lock

Large diffs are not rendered by default.

95 changes: 60 additions & 35 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,36 +1,57 @@
[workspace]
resolver = "2"

members = [
# Binaries
"binaries/cuprated",
"constants",

# Benchmarks
"benches/benchmark/bin",
"benches/benchmark/lib",
"benches/benchmark/example",
"benches/criterion/example",
"benches/criterion/cuprate-json-rpc",

# Consensus
"consensus",
"consensus/context",
"consensus/fast-sync",
"consensus/rules",
"cryptonight",
"helper",

# Net
"net/epee-encoding",
"net/fixed-bytes",
"net/levin",
"net/wire",

# P2P
"p2p/p2p",
"p2p/p2p-core",
"p2p/bucket",
"p2p/dandelion-tower",
"p2p/async-buffer",
"p2p/address-book",

# Storage
"storage/blockchain",
"storage/service",
"storage/txpool",
"storage/database",
"pruning",
"test-utils",
"types",

# RPC
"rpc/json-rpc",
"rpc/types",
"rpc/interface",

# ZMQ
"zmq/types",

# Misc
"constants",
"cryptonight",
"helper",
"pruning",
"test-utils",
"types",
]

[profile.release]
Expand All @@ -53,34 +74,36 @@ opt-level = 3

[workspace.dependencies]
# Cuprate members
cuprate-fast-sync = { path = "consensus/fast-sync", default-features = false }
cuprate-consensus-rules = { path = "consensus/rules", default-features = false }
cuprate-constants = { path = "constants", default-features = false }
cuprate-consensus = { path = "consensus", default-features = false }
cuprate-consensus-context = { path = "consensus/context", default-features = false }
cuprate-cryptonight = { path = "cryptonight", default-features = false }
cuprate-helper = { path = "helper", default-features = false }
cuprate-epee-encoding = { path = "net/epee-encoding", default-features = false }
cuprate-fixed-bytes = { path = "net/fixed-bytes", default-features = false }
cuprate-levin = { path = "net/levin", default-features = false }
cuprate-wire = { path = "net/wire", default-features = false }
cuprate-p2p = { path = "p2p/p2p", default-features = false }
cuprate-p2p-core = { path = "p2p/p2p-core", default-features = false }
cuprate-p2p-bucket = { path = "p2p/p2p-bucket", default-features = false }
cuprate-dandelion-tower = { path = "p2p/dandelion-tower", default-features = false }
cuprate-async-buffer = { path = "p2p/async-buffer", default-features = false }
cuprate-address-book = { path = "p2p/address-book", default-features = false }
cuprate-blockchain = { path = "storage/blockchain", default-features = false }
cuprate-database = { path = "storage/database", default-features = false }
cuprate-database-service = { path = "storage/service", default-features = false }
cuprate-txpool = { path = "storage/txpool", default-features = false }
cuprate-pruning = { path = "pruning", default-features = false }
cuprate-test-utils = { path = "test-utils", default-features = false }
cuprate-types = { path = "types", default-features = false }
cuprate-json-rpc = { path = "rpc/json-rpc", default-features = false }
cuprate-rpc-types = { path = "rpc/types", default-features = false }
cuprate-rpc-interface = { path = "rpc/interface", default-features = false }
cuprate-zmq-types = { path = "zmq/types", default-features = false }
cuprate-benchmark-lib = { path = "benches/benchmark/lib", default-features = false }
cuprate-benchmark-example = { path = "benches/benchmark/example", default-features = false }
cuprate-fast-sync = { path = "consensus/fast-sync", default-features = false }
cuprate-consensus-rules = { path = "consensus/rules", default-features = false }
cuprate-constants = { path = "constants", default-features = false }
cuprate-consensus = { path = "consensus", default-features = false }
cuprate-consensus-context = { path = "consensus/context", default-features = false }
cuprate-cryptonight = { path = "cryptonight", default-features = false }
cuprate-helper = { path = "helper", default-features = false }
cuprate-epee-encoding = { path = "net/epee-encoding", default-features = false }
cuprate-fixed-bytes = { path = "net/fixed-bytes", default-features = false }
cuprate-levin = { path = "net/levin", default-features = false }
cuprate-wire = { path = "net/wire", default-features = false }
cuprate-p2p = { path = "p2p/p2p", default-features = false }
cuprate-p2p-core = { path = "p2p/p2p-core", default-features = false }
cuprate-p2p-bucket = { path = "p2p/p2p-bucket", default-features = false }
cuprate-dandelion-tower = { path = "p2p/dandelion-tower", default-features = false }
cuprate-async-buffer = { path = "p2p/async-buffer", default-features = false }
cuprate-address-book = { path = "p2p/address-book", default-features = false }
cuprate-blockchain = { path = "storage/blockchain", default-features = false }
cuprate-database = { path = "storage/database", default-features = false }
cuprate-database-service = { path = "storage/service", default-features = false }
cuprate-txpool = { path = "storage/txpool", default-features = false }
cuprate-pruning = { path = "pruning", default-features = false }
cuprate-test-utils = { path = "test-utils", default-features = false }
cuprate-types = { path = "types", default-features = false }
cuprate-json-rpc = { path = "rpc/json-rpc", default-features = false }
cuprate-rpc-types = { path = "rpc/types", default-features = false }
cuprate-rpc-interface = { path = "rpc/interface", default-features = false }
cuprate-zmq-types = { path = "zmq/types", default-features = false }

# External dependencies
anyhow = { version = "1", default-features = false }
Expand Down Expand Up @@ -125,6 +148,8 @@ tracing-subscriber = { version = "0.3", default-features = false }
tracing = { version = "0.1", default-features = false }

## workspace.dev-dependencies
criterion = { version = "0.5" }
function_name = { version = "0.3" }
monero-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce" }
monero-simple-request-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce" }
tempfile = { version = "3" }
Expand Down
6 changes: 5 additions & 1 deletion benches/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# TODO
# Benches
This directory contains Cuprate's benchmarks and benchmarking utilities.

See the [`Benchmarking` section in the Architecture book](https://architecture.cuprate.org/benchmarking/intro.html)
to see how to create and run these benchmarks.
43 changes: 43 additions & 0 deletions benches/benchmark/bin/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[package]
name = "cuprate-benchmark"
version = "0.0.0"
edition = "2021"
description = "Cuprate's benchmarking binary"
license = "MIT"
authors = ["hinto-janai"]
repository = "https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/bin"
keywords = ["cuprate", "benchmarking", "binary"]

[features]
# All new benchmarks should be added here!
all = ["example"]

# Non-benchmark features.
default = []
json = []
trace = []
debug = []
warn = []
info = []
error = []

# Benchmark features.
# New benchmarks should be added here!
example = [
"dep:cuprate-benchmark-example"
]

[dependencies]
cuprate-benchmark-lib = { workspace = true }
cuprate-benchmark-example = { workspace = true, optional = true }

cfg-if = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["std"] }
tracing = { workspace = true, features = ["std", "attributes"] }
tracing-subscriber = { workspace = true, features = ["fmt", "std", "env-filter"] }

[dev-dependencies]

[lints]
workspace = true
27 changes: 27 additions & 0 deletions benches/benchmark/bin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## `cuprate-benchmark`
This crate links all benchmarks together into a single binary that can be run as: `cuprate-benchmark`.

`cuprate-benchmark` will run all enabled benchmarks sequentially and print data at the end.

## Benchmarks
Benchmarks are opt-in and enabled via features.

| Feature | Enables which benchmark crate? |
|----------|--------------------------------|
| example | cuprate-benchmark-example |
| database | cuprate-benchmark-database |

## Features
These are features that aren't for enabling benchmarks, but rather for other things.

Since `cuprate-benchmark` is built right before it is ran,
these features almost act like command line arguments.

| Features | Does what |
|----------|-----------|
| json | Prints JSON timings instead of a markdown table
| trace | Use the `trace` log-level
| debug | Use the `debug` log-level
| warn | Use the `warn` log-level
| info | Use the `info` log-level (default)
| error | Use the `error` log-level
29 changes: 29 additions & 0 deletions benches/benchmark/bin/src/log.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use cfg_if::cfg_if;
use tracing::{info, instrument, Level};
use tracing_subscriber::FmtSubscriber;

/// Initializes the `tracing` logger.
#[instrument]
pub(crate) fn init_logger() {
const LOG_LEVEL: Level = {
cfg_if! {
if #[cfg(feature = "trace")] {
Level::TRACE
} else if #[cfg(feature = "debug")] {
Level::DEBUG
} else if #[cfg(feature = "warn")] {
Level::WARN
} else if #[cfg(feature = "info")] {
Level::INFO
} else if #[cfg(feature = "error")] {
Level::ERROR
} else {
Level::INFO
}
}
};

FmtSubscriber::builder().with_max_level(LOG_LEVEL).init();

info!("Log level: {LOG_LEVEL}");
}
49 changes: 49 additions & 0 deletions benches/benchmark/bin/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#![doc = include_str!("../README.md")]
#![allow(
unused_crate_dependencies,
reason = "this crate imports many potentially unused dependencies"
)]

mod log;
mod print;
mod run;
mod timings;

use cfg_if::cfg_if;

/// What `main()` does:
/// 1. Run all enabled benchmarks
/// 2. Record benchmark timings
/// 3. Print timing data
///
/// To add a new benchmark to be ran here:
/// 1. Copy + paste a `cfg_if` block
/// 2. Change it to your benchmark's feature flag
/// 3. Change it to your benchmark's type
#[allow(
clippy::allow_attributes,
unused_variables,
unused_mut,
unreachable_code,
reason = "clippy does not account for all cfg()s"
)]
fn main() {
log::init_logger();

let mut timings = timings::Timings::new();

cfg_if! {
if #[cfg(not(any(feature = "example")))] {
println!("No feature specified. Use `--features $BENCHMARK_FEATURE` when building.");
return;
}
}

cfg_if! {
if #[cfg(feature = "example")] {
run::run_benchmark::<cuprate_benchmark_example::Example>(&mut timings);
}
}

print::print_timings(&timings);
}
38 changes: 38 additions & 0 deletions benches/benchmark/bin/src/print.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#![expect(dead_code, reason = "code hidden behind feature flags")]

use cfg_if::cfg_if;

use crate::timings::Timings;

/// Print the final the final markdown table of benchmark timings.
pub(crate) fn print_timings(timings: &Timings) {
println!("\nFinished all benchmarks, printing results:");

cfg_if! {
if #[cfg(feature = "json")] {
print_timings_json(timings);
} else {
print_timings_markdown(timings);
}
}
}

/// Default timing formatting.
pub(crate) fn print_timings_markdown(timings: &Timings) {
let mut s = String::new();
s.push_str("| Benchmark | Time (seconds) |\n");
s.push_str("|------------------------------------|----------------|");

#[expect(clippy::iter_over_hash_type)]
for (k, v) in timings {
s += &format!("\n| {k:<34} | {v:<14} |");
}

println!("\n{s}");
}

/// Enabled via `json` feature.
pub(crate) fn print_timings_json(timings: &Timings) {
let json = serde_json::to_string_pretty(timings).unwrap();
println!("\n{json}");
}
36 changes: 36 additions & 0 deletions benches/benchmark/bin/src/run.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use tracing::{info, instrument, trace};

use cuprate_benchmark_lib::Benchmark;

use crate::timings::Timings;

/// Run a [`Benchmark`] and record its timing.
#[instrument(skip_all)]
pub(crate) fn run_benchmark<B: Benchmark>(timings: &mut Timings) {
// Get the benchmark name.
let name = B::name();
trace!("Running benchmark: {name}");

// Setup the benchmark input.
let input = B::SETUP();

// Sleep before running the benchmark.
trace!("Pre-benchmark, sleeping for: {:?}", B::POST_SLEEP_DURATION);
std::thread::sleep(B::PRE_SLEEP_DURATION);

// Run/time the benchmark.
let now = std::time::Instant::now();
B::MAIN(input);
let time = now.elapsed().as_secs_f32();

// Print the benchmark timings.
info!("{name:>34} ... {time}");
assert!(
timings.insert(name, time).is_none(),
"There were 2 benchmarks with the same name - this collides the final output: {name}",
);

// Sleep for a cooldown period after the benchmark run.
trace!("Post-benchmark, sleeping for: {:?}", B::POST_SLEEP_DURATION);
std::thread::sleep(B::POST_SLEEP_DURATION);
}
5 changes: 5 additions & 0 deletions benches/benchmark/bin/src/timings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// Benchmark timing data.
///
/// - Key = benchmark name
/// - Value = benchmark time in seconds
pub(crate) type Timings = std::collections::HashMap<&'static str, f32>;
Loading

0 comments on commit caa08d5

Please sign in to comment.