#![allow(
clippy::expect_used,
clippy::missing_docs_in_private_items,
clippy::unreachable,
missing_docs
)]
use std::{hint::black_box, sync::Arc};
use criterion::{Criterion, criterion_group, criterion_main};
use rmcp::model::{CallToolResult, Content};
use rmcp_server_kit::tool_hooks::{
AfterHook, BeforeHook, HookDisposition, HookOutcome, ToolCallContext, ToolHooks,
};
use tokio::runtime::Builder;
fn make_ctx() -> ToolCallContext {
ToolCallContext::for_tool("bench")
}
fn make_result() -> CallToolResult {
CallToolResult::success(vec![Content::text("ok".to_owned())])
}
fn bench_hook_latency_bare(c: &mut Criterion) {
let rt = Builder::new_current_thread()
.enable_all()
.build()
.expect("build tokio runtime");
c.bench_function("hook_latency_bare", |b| {
b.iter(|| {
rt.block_on(async {
let r = async { make_result() }.await;
black_box(r);
});
});
});
}
fn bench_hook_latency_hooked(c: &mut Criterion) {
let rt = Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.expect("build tokio runtime");
let before: BeforeHook = Arc::new(|_ctx| Box::pin(async { HookOutcome::Continue }));
let after: AfterHook = Arc::new(|_ctx, _disp, _bytes| Box::pin(async {}));
let hooks = Arc::new(
ToolHooks::new()
.with_max_result_bytes(64 * 1024)
.with_before(before)
.with_after(after),
);
c.bench_function("hook_latency_hooked", |b| {
b.iter(|| {
rt.block_on(async {
let ctx = make_ctx();
if let Some(before) = hooks.before.as_ref() {
let outcome = before(&ctx).await;
if !matches!(outcome, HookOutcome::Continue) {
unreachable!("bench expects Continue");
}
}
let r = async { make_result() }.await;
if let Some(after) = hooks.after.as_ref() {
let after = Arc::clone(after);
let ctx_clone = ctx.clone();
tokio::spawn(async move {
let fut = after(&ctx_clone, HookDisposition::InnerExecuted, 64);
fut.await;
});
}
black_box(r);
});
});
});
}
criterion_group!(benches, bench_hook_latency_bare, bench_hook_latency_hooked);
criterion_main!(benches);