karapace/crates/karapace-core/benches/engine_benchmarks.rs
Marco Allegretti f535020600 feat: karapace-core — engine orchestration, lifecycle state machine, drift control
- Engine: init → resolve → lock → build → enter/exec → freeze → archive → destroy
- Cached store_root_str avoiding repeated to_string_lossy() allocations
- WAL-protected build, enter, exec, destroy, commit, restore, GC operations
- Overlay drift detection: diff/commit/export via upper_dir scanning
- Deterministic snapshot commit with composite identity hashing
- Atomic restore via staging directory swap
- StoreLock: compile-time enforcement via type parameter on gc()
- Signal handler: SIGINT/SIGTERM graceful shutdown with AtomicBool
- Push/pull delegation to karapace-remote backend
- Crash recovery: stale .running marker cleanup on Engine::new()
- Integration tests, E2E tests, crash injection tests, ENOSPC simulation
- Criterion benchmarks: build, rebuild, commit, restore, GC, verify
2026-02-22 18:37:02 +01:00

202 lines
7 KiB
Rust

use criterion::{criterion_group, criterion_main, Criterion};
use std::fs;
use std::path::Path;
fn create_test_manifest(dir: &Path) -> std::path::PathBuf {
let manifest_path = dir.join("karapace.toml");
fs::write(
&manifest_path,
r#"
manifest_version = 1
[base]
image = "rolling"
[system]
packages = ["git", "clang"]
[runtime]
backend = "mock"
"#,
)
.unwrap();
manifest_path
}
fn bench_build(c: &mut Criterion) {
c.bench_function("engine_build_mock_2pkg", |b| {
b.iter_with_setup(
|| {
let store_dir = tempfile::tempdir().unwrap();
let project_dir = tempfile::tempdir().unwrap();
let manifest = create_test_manifest(project_dir.path());
let engine = karapace_core::Engine::new(store_dir.path());
(store_dir, project_dir, manifest, engine)
},
|(_sd, _pd, manifest, engine)| {
engine.build(&manifest).unwrap();
},
);
});
}
fn bench_rebuild_unchanged(c: &mut Criterion) {
c.bench_function("engine_rebuild_unchanged", |b| {
b.iter_with_setup(
|| {
let store_dir = tempfile::tempdir().unwrap();
let project_dir = tempfile::tempdir().unwrap();
let manifest = create_test_manifest(project_dir.path());
let engine = karapace_core::Engine::new(store_dir.path());
let result = engine.build(&manifest).unwrap();
(store_dir, project_dir, manifest, engine, result)
},
|(_sd, _pd, manifest, engine, _result)| {
engine.build(&manifest).unwrap();
},
);
});
}
fn bench_commit(c: &mut Criterion) {
c.bench_function("engine_commit_100files", |b| {
b.iter_with_setup(
|| {
let store_dir = tempfile::tempdir().unwrap();
let project_dir = tempfile::tempdir().unwrap();
let manifest = create_test_manifest(project_dir.path());
let engine = karapace_core::Engine::new(store_dir.path());
let result = engine.build(&manifest).unwrap();
let env_id = result.identity.env_id.to_string();
// Create 100 files in the upper directory to simulate drift
let upper = store_dir.path().join("env").join(&env_id).join("upper");
fs::create_dir_all(&upper).unwrap();
for i in 0..100 {
fs::write(
upper.join(format!("file_{i:03}.txt")),
format!("content {i}"),
)
.unwrap();
}
(store_dir, project_dir, engine, env_id)
},
|(_sd, _pd, engine, env_id)| {
engine.commit(&env_id).unwrap();
},
);
});
}
fn bench_restore(c: &mut Criterion) {
c.bench_function("engine_restore_snapshot", |b| {
b.iter_with_setup(
|| {
let store_dir = tempfile::tempdir().unwrap();
let project_dir = tempfile::tempdir().unwrap();
let manifest = create_test_manifest(project_dir.path());
let engine = karapace_core::Engine::new(store_dir.path());
let result = engine.build(&manifest).unwrap();
let env_id = result.identity.env_id.to_string();
// Create files and commit a snapshot
let upper = store_dir.path().join("env").join(&env_id).join("upper");
fs::create_dir_all(&upper).unwrap();
for i in 0..50 {
fs::write(
upper.join(format!("file_{i:03}.txt")),
format!("content {i}"),
)
.unwrap();
}
let snapshot_hash = engine.commit(&env_id).unwrap();
(store_dir, project_dir, engine, env_id, snapshot_hash)
},
|(_sd, _pd, engine, env_id, snapshot_hash)| {
engine.restore(&env_id, &snapshot_hash).unwrap();
},
);
});
}
fn bench_gc(c: &mut Criterion) {
c.bench_function("gc_50envs", |b| {
b.iter_with_setup(
|| {
let store_dir = tempfile::tempdir().unwrap();
let layout = karapace_store::StoreLayout::new(store_dir.path());
layout.initialize().unwrap();
let meta_store = karapace_store::MetadataStore::new(layout.clone());
let obj_store = karapace_store::ObjectStore::new(layout.clone());
// Create 50 environments: 25 live (ref_count=1), 25 dead (ref_count=0)
for i in 0..50 {
let obj_hash = obj_store.put(format!("obj-{i}").as_bytes()).unwrap();
let meta = karapace_store::EnvMetadata {
env_id: format!("env_{i:04}").into(),
short_id: format!("env_{i:04}").into(),
name: None,
state: karapace_store::EnvState::Built,
manifest_hash: obj_hash.into(),
base_layer: "".into(),
dependency_layers: vec![],
policy_layer: None,
created_at: "2026-01-01T00:00:00Z".to_owned(),
updated_at: "2026-01-01T00:00:00Z".to_owned(),
ref_count: u32::from(i < 25),
checksum: None,
};
meta_store.put(&meta).unwrap();
}
// Create 200 orphan objects
for i in 0..200 {
obj_store
.put(format!("orphan-object-{i}").as_bytes())
.unwrap();
}
(store_dir, layout)
},
|(_sd, layout)| {
let gc = karapace_store::GarbageCollector::new(layout);
gc.collect(false).unwrap();
},
);
});
}
fn bench_verify_store(c: &mut Criterion) {
c.bench_function("verify_store_200objects", |b| {
b.iter_with_setup(
|| {
let store_dir = tempfile::tempdir().unwrap();
let layout = karapace_store::StoreLayout::new(store_dir.path());
layout.initialize().unwrap();
let obj_store = karapace_store::ObjectStore::new(layout.clone());
// Create 200 objects
for i in 0..200 {
obj_store
.put(format!("verify-object-{i}").as_bytes())
.unwrap();
}
(store_dir, layout)
},
|(_sd, layout)| {
karapace_store::verify_store_integrity(&layout).unwrap();
},
);
});
}
criterion_group!(
benches,
bench_build,
bench_rebuild_unchanged,
bench_commit,
bench_restore,
bench_gc,
bench_verify_store,
);
criterion_main!(benches);