milestone 21: abp list exceptions and a regex rule kind

This commit is contained in:
2026-08-13 19:14:47 +02:00
parent b340521716
commit 2ab7c1f1de
51 changed files with 4016 additions and 465 deletions
+40 -7
View File
@@ -10,8 +10,9 @@
//! What each suite measures:
//! - `filter`: `matcher.normalize` + `Snapshot.evaluate` per op — the handler's
//! filtering work — against a snapshot built from `--domains` generated exact
//! entries plus a small wildcard body. Query mix cycles hit, miss and
//! parent-walk. Target p95 < 1 ms; VmRSS < 100 MiB with the list loaded.
//! entries, a small wildcard body and `bench_regex_rules` operator regex
//! rules. Query mix cycles hit, miss and parent-walk. Target p95 < 1 ms;
//! VmRSS < 100 MiB with the list loaded.
//! - `cache`: `buildKey` + `DnsCache.get` + `packet.setId` — the handler's
//! cache-hit path, TTL aging included — on a 10k-entry cache prefilled with a
//! realistic response. Query mix alternates hit and miss. Target p95 < 5 ms.
@@ -38,6 +39,11 @@ const rss_target_bytes: usize = 100 * 1024 * 1024;
const cache_entries: u32 = 10_000;
/// Operator regex rules the filter snapshot carries. A household writes a
/// handful; 32 is the pessimistic end of plausible, and `max_regex_per_group`
/// allows eight times as many.
const bench_regex_rules: usize = 32;
/// Byte-for-byte copy of `response` in tests/fuzz/corpus.zig (a copy on
/// purpose, same as the corpus itself: a bench input that changes whenever a
/// test fixture is edited is a benchmark that silently shifts). A CNAME to
@@ -147,6 +153,23 @@ fn runFilter(io: std.Io, gpa: Allocator, opts: Options, w: *Writer) !u32 {
try body.appendSlice(gpa, text);
}
// None of these matches the generated query mix, which is the expensive
// case rather than the cheap one: the regex levels sit below every hash and
// wildcard level, so a query that no regex matches is the query that runs
// all of them to their end. Every op in this suite pays that.
var patterns: std.ArrayList([]u8) = .empty;
defer {
for (patterns.items) |pattern| gpa.free(pattern);
patterns.deinit(gpa);
}
var regex_rules: [bench_regex_rules]model.Rule = undefined;
for (&regex_rules, 0..) |*row, i| {
const pattern = try std.fmt.allocPrint(gpa, "^r{d}-[0-9]+\\.(ads|track)\\.invalid$", .{i});
errdefer gpa.free(pattern);
try patterns.append(gpa, pattern);
row.* = .{ .group = "default", .pattern = pattern, .kind = .regex, .action = .block };
}
const sources = [_]model.BlocklistSource{.{ .url = "bench://list", .name = "bench" }};
const links = [_]model.GroupSource{.{ .group = "default", .source_url = "bench://list" }};
var snapshot = try matcher.Snapshot.build(gpa, .{
@@ -155,7 +178,7 @@ fn runFilter(io: std.Io, gpa: Allocator, opts: Options, w: *Writer) !u32 {
.group_sources = &links,
.sources = &sources,
.source_ids = &.{1},
.rules = &.{},
.rules = &regex_rules,
.clients = &.{},
.prefixes = &.{},
.compiled = &.{.{ .list_body = body.items, .wild_body = wild_body }},
@@ -205,9 +228,10 @@ fn runFilter(io: std.Io, gpa: Allocator, opts: Options, w: *Writer) !u32 {
const pct = percentiles(samples);
const rss = vmRssBytes(io);
try printRow(w, "filter", opts.iters, pct);
try w.print(" blocked {d}/{d}, Snapshot.memoryBytes {d:.1} MiB, VmRSS {d:.1} MiB\n", .{
blocked, opts.iters, mib(snapshot.memoryBytes()), mib(rss),
});
try w.print(
" blocked {d}/{d}, {d} regex rules, Snapshot.memoryBytes {d:.1} MiB, VmRSS {d:.1} MiB\n",
.{ blocked, opts.iters, regex_rules.len, mib(snapshot.memoryBytes()), mib(rss) },
);
var exceeded: u32 = 0;
exceeded += try printTarget(w, "p95 < 1ms", pct.p95 < filter_p95_target_ns);
@@ -287,11 +311,20 @@ fn runCompile(io: std.Io, gpa: Allocator, opts: Options, w: *Writer) !void {
var reader = std.Io.Reader.fixed(body.items);
var list_buf: [4096]u8 = undefined;
var wild_buf: [4096]u8 = undefined;
var allow_buf: [4096]u8 = undefined;
var list_out: Writer.Discarding = .init(&list_buf);
var wild_out: Writer.Discarding = .init(&wild_buf);
var allow_out: Writer.Discarding = .init(&allow_buf);
const t0 = std.Io.Clock.awake.now(io);
const result = compiler.compile(gpa, &reader, .hosts, &list_out.writer, &wild_out.writer) catch |err| {
const result = compiler.compile(
gpa,
&reader,
.hosts,
&list_out.writer,
&wild_out.writer,
&allow_out.writer,
) catch |err| {
std.process.fatal("compiler.compile failed: {t}", .{err});
};
const t1 = std.Io.Clock.awake.now(io);