132 lines
4.4 KiB
Rust
132 lines
4.4 KiB
Rust
// Benchmarks for the recursive resolver.
|
|
//
|
|
// Run with:
|
|
//
|
|
// cargo bench -p hickory-resolver --features recursor --bench recursor
|
|
//
|
|
// Each benchmark creates a small in-process mock DNS hierarchy using
|
|
// `test_support::MockProvider`, so no real network access is required.
|
|
|
|
use std::{
|
|
hint::black_box,
|
|
net::{IpAddr, Ipv4Addr},
|
|
sync::Arc,
|
|
time::Instant,
|
|
};
|
|
|
|
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
|
|
use hickory_resolver::{
|
|
proto::{
|
|
op::Query,
|
|
rr::{Name, RecordType},
|
|
},
|
|
recursor::{Recursor, RecursorOptions},
|
|
};
|
|
use test_support::{MockNetworkHandler, MockProvider, MockRecord, MockResponseSection};
|
|
use tokio::runtime::Runtime;
|
|
|
|
/// Cold-cache resolution: a fresh `Recursor` with empty NS and response caches
|
|
/// is created for every iteration. Measures the full cost of zone-cut discovery
|
|
/// plus the final answer lookup.
|
|
fn bench_cold_resolve(c: &mut Criterion) {
|
|
let rt = Runtime::new().unwrap();
|
|
|
|
c.bench_function("recursor/cold_resolve", |b| {
|
|
b.to_async(&rt).iter_batched(
|
|
make_recursor,
|
|
|(recursor, query_name)| async move {
|
|
black_box(
|
|
recursor
|
|
.resolve(
|
|
Query::query(query_name, RecordType::A),
|
|
Instant::now(),
|
|
false,
|
|
)
|
|
.await
|
|
.unwrap(),
|
|
)
|
|
},
|
|
BatchSize::PerIteration,
|
|
);
|
|
});
|
|
}
|
|
|
|
/// Warm-cache resolution: a single `Recursor` is shared across all iterations.
|
|
/// Criterion's warmup phase populates the caches; subsequent iterations measure
|
|
/// pure cache-hit latency. This is the theoretical lower bound for a cached name.
|
|
fn bench_warm_resolve(c: &mut Criterion) {
|
|
let rt = Runtime::new().unwrap();
|
|
|
|
let (recursor, query_name) = make_recursor();
|
|
let recursor = Arc::new(recursor);
|
|
|
|
c.bench_function("recursor/warm_resolve", |b| {
|
|
b.to_async(&rt).iter(|| {
|
|
let recursor = recursor.clone();
|
|
let query_name = query_name.clone();
|
|
async move {
|
|
black_box(
|
|
recursor
|
|
.resolve(
|
|
Query::query(query_name, RecordType::A),
|
|
Instant::now(),
|
|
false,
|
|
)
|
|
.await
|
|
.unwrap(),
|
|
)
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
criterion_group!(benches, bench_cold_resolve, bench_warm_resolve);
|
|
criterion_main!(benches);
|
|
|
|
/// Build a two-level mock DNS hierarchy and return a `Recursor` with its query name:
|
|
///
|
|
/// . (ROOT_IP)
|
|
/// └── testing. (TLD_IP)
|
|
/// └── hickory-dns.testing. (LEAF_IP)
|
|
/// └── host.hickory-dns.testing. A -> LEAF_IP
|
|
fn make_recursor() -> (Recursor<MockProvider>, Name) {
|
|
let query_name = Name::from_ascii("host.hickory-dns.testing.").unwrap();
|
|
let tld_zone = Name::from_ascii("testing.").unwrap();
|
|
let tld_ns = Name::from_ascii("testing.testing.").unwrap();
|
|
let leaf_zone = Name::from_ascii("hickory-dns.testing.").unwrap();
|
|
let leaf_ns = Name::from_ascii("ns.hickory-dns.testing.").unwrap();
|
|
|
|
let provider = MockProvider::new(MockNetworkHandler::new(vec![
|
|
// Root → TLD delegation
|
|
MockRecord::ns(ROOT_IP, &tld_zone, &tld_ns),
|
|
MockRecord::a(ROOT_IP, &tld_ns, TLD_IP)
|
|
.with_query_name(&tld_zone)
|
|
.with_query_type(RecordType::NS)
|
|
.with_section(MockResponseSection::Additional),
|
|
// TLD → leaf delegation
|
|
MockRecord::ns(TLD_IP, &leaf_zone, &leaf_ns),
|
|
MockRecord::a(TLD_IP, &leaf_ns, LEAF_IP)
|
|
.with_query_name(&leaf_zone)
|
|
.with_query_type(RecordType::NS)
|
|
.with_section(MockResponseSection::Additional),
|
|
// Leaf answer
|
|
MockRecord::a(LEAF_IP, &query_name, LEAF_IP),
|
|
]));
|
|
|
|
let recursor = Recursor::with_options(
|
|
&[ROOT_IP],
|
|
RecursorOptions {
|
|
deny_server: Vec::new(),
|
|
..RecursorOptions::default()
|
|
},
|
|
provider,
|
|
)
|
|
.unwrap();
|
|
|
|
(recursor, query_name)
|
|
}
|
|
|
|
const ROOT_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 0, 1, 1));
|
|
const TLD_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 0, 2, 1));
|
|
const LEAF_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 0, 3, 1));
|