Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/atuin-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name = "atuin-client"
edition = "2024"
description = "client library for atuin"
autobenches = false

rust-version = { workspace = true }
version = { workspace = true }
Expand Down Expand Up @@ -86,5 +87,5 @@ divan = "0.1.14"
tempfile = "3"

[[bench]]
name = "record_store"
name = "benchmarks"
harness = false
38 changes: 38 additions & 0 deletions crates/atuin-client/benches/_util/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use rand::SeedableRng;
use rand::rngs::StdRng;
use time::OffsetDateTime;
use time::macros::datetime;

/// Utility used to create a benchmarking context.
///
/// Generally useful for establishing stable and robust benchmarks. It's an anti-pattern to use a
/// bare `rand` accessor as that causes benchmarks to be non-deterministic.
pub struct BenchCtx {
rng: StdRng,
}

impl BenchCtx {
// Changing any of these values will result in irreproducible benchmarks.
const SEED_RNG: u64 = 42;
const SEED_NOW: OffsetDateTime = datetime!(2026-01-01 12:59:59 -5);

pub fn new() -> Self {
Self {
rng: StdRng::seed_from_u64(Self::SEED_RNG),
}
}

/// Access a random number generator which is stable across the given benchmark.
pub fn rng(&mut self) -> &mut StdRng {
&mut self.rng
}

/// Get the timestamp recognized as the current timestamp in the stable benchmarking
/// environment.
///
/// Using the standard library will provide timestamps which are not stable and will result in
/// irreproducible benchmarks.
pub fn now(&self) -> OffsetDateTime {
Self::SEED_NOW
}
}
1 change: 1 addition & 0 deletions crates/atuin-client/benches/_util/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod context;
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod _util;
mod history;
mod ordering;
mod record;

fn main() {
Expand Down
35 changes: 35 additions & 0 deletions crates/atuin-client/benches/history.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use atuin_client::history::History;
use rand::Rng;
use rand::seq::SliceRandom;

use crate::_util::context::BenchCtx;

pub struct BenchHistory;

impl BenchHistory {
/// List of commands which will be used to create some sort of history.
const SEED_COMMANDS: [&str; 6] = [
"cargo build --release",
"git commit -m 'fix bug'",
"curl -s https://example.com/api",
"grep -rn pattern src/",
"docker compose up -d",
"ls -la /tmp",
];

pub fn one(ctx: &mut BenchCtx) -> History {
let now = ctx.now().unix_timestamp();
let cmd = *Self::SEED_COMMANDS.choose(ctx.rng()).unwrap();
History::import()
.command(cmd)
.timestamp(
time::OffsetDateTime::from_unix_timestamp(ctx.rng().gen_range(0..now)).unwrap(),
)
.build()
.into()
}

pub fn count(ctx: &mut BenchCtx, n: usize) -> Vec<History> {
(0..n).map(|_| Self::one(ctx)).collect()
}
}
18 changes: 18 additions & 0 deletions crates/atuin-client/benches/ordering.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::_util::context::BenchCtx;
use crate::history::BenchHistory;
use atuin_client::ordering::reorder_fuzzy;
use atuin_client::settings::SearchMode;

/// reorder_fuzzy is invoked on every keystroke during interactive fuzzy search, so keeping the
/// performance in check is important.
///
/// The interactive search hardcodes a limit of 200 deduplicated entries (`engines/db.rs`).
#[divan::bench(args = [10, 200, 1_000], min_time = 1)]
fn reorder_fuzzy_bench(bencher: divan::Bencher, n: usize) {
bencher
.with_inputs(|| {
let mut ctx = BenchCtx::new();
BenchHistory::count(&mut ctx, n)
})
.bench_values(|histories| reorder_fuzzy(SearchMode::Fuzzy, "curl", histories));
}
22 changes: 14 additions & 8 deletions crates/atuin-client/benches/record/sqlite_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ use atuin_client::record::sqlite_store::SqliteStore;
use atuin_client::record::store::Store;
use atuin_common::record::{EncryptedData, Host, HostId, Record};
use atuin_common::utils::uuid_v7;
use rand::{Rng, distributions::Alphanumeric};
use rand::Rng;
use rand::distributions::Alphanumeric;
use tempfile::TempDir;

struct BenchRecordBuilder;
use crate::_util::context::BenchCtx;

impl BenchRecordBuilder {
struct BenchRecord;

impl BenchRecord {
/// Controls how large the record payload is. Roughly, this is between 200 and 400 bytes for
/// a typical history record.
///
Expand All @@ -27,16 +30,18 @@ impl BenchRecordBuilder {
/// Rough size of the PASETO PIE-wrapped key.
const KEY_SIZE: usize = 150;

fn chain(n: usize) -> Vec<Record<EncryptedData>> {
fn chain(ctx: &mut BenchCtx, n: usize) -> Vec<Record<EncryptedData>> {
let host = Host::new(HostId(uuid_v7()));
let version: String = "v1".into();
let tag = uuid_v7().simple().to_string();
let data: String = rand::thread_rng()
let data: String = ctx
.rng()
.sample_iter(&Alphanumeric)
.take(Self::PAYLOAD_SIZE)
.map(char::from)
.collect();
let key: String = rand::thread_rng()
let key: String = ctx
.rng()
.sample_iter(&Alphanumeric)
.take(Self::KEY_SIZE)
.map(char::from)
Expand Down Expand Up @@ -85,14 +90,15 @@ impl BenchSqliteStore {
/// The parameters are:
/// - 1 proves out the case of adding one shell entry via `push_record` (history/store.rs).
/// - 100 is the page size used by `sync_remote` (record/sync.rs).
#[divan::bench(args = [1, 10, 100], sample_count = 500, min_time = 5)]
#[divan::bench(args = [1, 10, 100], sample_count = 500, min_time = 1)]
fn push_batch(bencher: divan::Bencher, n: usize) {
let rt = tokio::runtime::Runtime::new().unwrap();

bencher
.with_inputs(|| {
let mut ctx = BenchCtx::new();
let db = rt.block_on(BenchSqliteStore::new());
let records = BenchRecordBuilder::chain(n);
let records = BenchRecord::chain(&mut ctx, n);
(db, records)
})
.bench_values(|(db, records)| {
Expand Down
6 changes: 3 additions & 3 deletions crates/atuin-client/src/ordering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ pub fn reorder_fuzzy(mode: SearchMode, query: &str, res: Vec<History>) -> Vec<Hi
fn reorder<F, A>(query: &str, f: F, res: Vec<A>) -> Vec<A>
where
F: Fn(&A) -> &String,
A: Clone,
{
let mut r = res.clone();
let mut r = res;
let len = r.len();
let qvec = &query.chars().collect();
r.sort_by_cached_key(|h| {
// TODO for fzf search we should sum up scores for each matched term
Expand All @@ -24,7 +24,7 @@ where
// we don't want to return a None, as the comparison behaviour would put the worst matches
// at the front. therefore, we'll return a set of indices that are one larger than the longest
// possible legitimate match. This is meaningless except as a comparison.
None => (0, res.len()),
None => (0, len),
};
1 + to - from
});
Expand Down
Loading