use std::hint::black_box;
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use destructive_command_guard::packs::REGISTRY;
use destructive_command_guard::{Config, EvaluationDecision, LayeredAllowlist};
struct EvalContext {
enabled_keywords: Vec<&'static str>,
ordered_packs: Vec<String>,
keyword_index: Option<destructive_command_guard::packs::EnabledKeywordIndex>,
compiled_overrides: destructive_command_guard::config::CompiledOverrides,
heredoc_settings: destructive_command_guard::config::HeredocSettings,
allowlists: LayeredAllowlist,
}
impl EvalContext {
fn new() -> Self {
let mut config = Config::default();
config.heredoc.enabled = Some(false);
config.packs.enabled = vec![
"core".to_string(),
"database.postgresql".to_string(),
"containers.docker".to_string(),
"kubernetes".to_string(),
];
let enabled_packs = config.enabled_pack_ids();
let ordered_packs = REGISTRY.expand_enabled_ordered(&enabled_packs);
Self {
enabled_keywords: REGISTRY.collect_enabled_keywords(&enabled_packs),
keyword_index: REGISTRY.build_enabled_keyword_index(&ordered_packs),
ordered_packs,
compiled_overrides: config.overrides.compile(),
heredoc_settings: config.heredoc_settings(),
allowlists: LayeredAllowlist::default(),
}
}
fn evaluate(&self, command: &str) -> destructive_command_guard::EvaluationResult {
destructive_command_guard::evaluate_command_with_pack_order(
command,
self.enabled_keywords.as_slice(),
self.ordered_packs.as_slice(),
self.keyword_index.as_ref(),
&self.compiled_overrides,
&self.allowlists,
&self.heredoc_settings,
)
}
}
fn bench_evaluate_command(c: &mut Criterion) {
let ctx = EvalContext::new();
let commands: &[(&str, &str, EvaluationDecision)] = &[
("safe_echo", "echo hello world", EvaluationDecision::Allow),
("safe_ls", "ls -la /tmp", EvaluationDecision::Allow),
("safe_git_status", "git status", EvaluationDecision::Allow),
(
"safe_git_log",
"git log --oneline -10",
EvaluationDecision::Allow,
),
(
"denied_rm_rf",
"rm -rf /important/data",
EvaluationDecision::Deny,
),
(
"denied_git_force_push",
"git push --force origin main",
EvaluationDecision::Deny,
),
(
"denied_drop_database",
"dropdb production",
EvaluationDecision::Deny,
),
(
"denied_docker_system_prune",
"docker system prune -af",
EvaluationDecision::Deny,
),
(
"denied_kubectl_delete_ns",
"kubectl delete namespace production",
EvaluationDecision::Deny,
),
(
"unrelated_cargo_build",
"cargo build --release",
EvaluationDecision::Allow,
),
(
"unrelated_python",
"python3 -m pytest tests/",
EvaluationDecision::Allow,
),
("unrelated_npm", "npm run build", EvaluationDecision::Allow),
];
let mut group = c.benchmark_group("evaluate_command");
group.measurement_time(std::time::Duration::from_secs(5));
for (name, cmd, expected_decision) in commands {
let result = ctx.evaluate(cmd);
assert_eq!(
&result.decision, expected_decision,
"pre-check failed for {name}: {cmd}"
);
group.bench_with_input(BenchmarkId::new("latency", name), cmd, |b, cmd| {
b.iter(|| ctx.evaluate(black_box(cmd)));
});
}
group.finish();
}
fn bench_long_command(c: &mut Criterion) {
let ctx = EvalContext::new();
let long_safe = format!("echo {}", "hello ".repeat(200));
let long_denied = format!("rm -rf /tmp/dir && {}", "echo ok && ".repeat(100));
let mut group = c.benchmark_group("long_command");
group.measurement_time(std::time::Duration::from_secs(5));
group.bench_function("long_safe_1200_chars", |b| {
b.iter(|| ctx.evaluate(black_box(&long_safe)));
});
group.bench_function("long_denied_compound", |b| {
b.iter(|| ctx.evaluate(black_box(&long_denied)));
});
group.finish();
}
fn bench_keyword_rejection(c: &mut Criterion) {
let ctx = EvalContext::new();
let no_keyword_commands = &[
"cargo test --release",
"python3 manage.py runserver",
"node server.js",
"make -j8",
"gcc -o main main.c",
];
let mut group = c.benchmark_group("keyword_rejection");
group.measurement_time(std::time::Duration::from_secs(5));
for cmd in no_keyword_commands {
let result = ctx.evaluate(cmd);
assert_eq!(result.decision, EvaluationDecision::Allow);
group.bench_with_input(
BenchmarkId::new("fast_reject", cmd.split_whitespace().next().unwrap()),
cmd,
|b, cmd| {
b.iter(|| ctx.evaluate(black_box(cmd)));
},
);
}
group.finish();
}
fn bench_throughput(c: &mut Criterion) {
let ctx = EvalContext::new();
let mixed_commands = vec![
"echo hello",
"ls -la",
"git status",
"rm -rf /data",
"git push --force origin main",
"cargo build",
"docker ps",
"kubectl get pods",
"psql -c 'SELECT 1'",
"npm install express",
];
c.bench_function("throughput_10_mixed_commands", |b| {
b.iter(|| {
for cmd in &mixed_commands {
black_box(ctx.evaluate(black_box(cmd)));
}
});
});
}
criterion_group!(
benches,
bench_evaluate_command,
bench_long_command,
bench_keyword_rejection,
bench_throughput
);
criterion_main!(benches);