milestone 28: query provenance — every logged query is exactly explainable
Gates / frontend (push) Successful in 1m36s
Gates / test (push) Successful in 1m56s
Gates / test-aarch64 (push) Successful in 7m37s
Gates / package (push) Successful in 9m12s
Gates / container (push) Successful in 13s
CI / gates (push) Successful in 19m4s
Gates / frontend (push) Successful in 1m36s
Gates / test (push) Successful in 1m56s
Gates / test-aarch64 (push) Successful in 7m37s
Gates / package (push) Successful in 9m12s
Gates / container (push) Successful in 13s
CI / gates (push) Successful in 19m4s
query rows gain qclass, rcode, group, policy action and reason, the matched rule or list entry with its source, cname and safe-search targets, route kind, forward zone, and the resolver that actually answered — the pool and local markers die. servfails are logged and name the resolver that lost; post-parse protocol refusals become rows. a detail page at /queries/:id renders the ordered explanation, and coverage watermarks distinguish an empty history from a missing one. the schema fingerprint changes: existing query history is recreated with the old file kept aside and the reset filed as a resolved diagnostic. fixes an oversized udp reply being rebuilt as noerror, which handed clients a truncated nxdomain as success.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
//! How much of the window a client asked about the query log can still answer
|
||||
//! for.
|
||||
//!
|
||||
//! Retention deletes old rows and advances a watermark in the same transaction
|
||||
//! (`queries_repo.pruneOlderThan`), so the file knows the oldest instant it is
|
||||
//! complete for. Without that fact on the wire a chart draws a pruned week as a
|
||||
//! week of silence, which is the one reading that is certainly wrong.
|
||||
//!
|
||||
//! Three endpoints carry it — `/api/queries`, `/api/stats` and
|
||||
//! `/api/stats/timeseries` — and they judge it against their own effective
|
||||
//! lower bound: the client's `since` for the query log, the period's aligned
|
||||
//! window start for the two stats endpoints.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const db = @import("../storage/db.zig");
|
||||
const queries_repo = @import("../storage/repositories/queries_repo.zig");
|
||||
|
||||
pub const Coverage = struct {
|
||||
/// True only when the whole requested window is inside what the file still
|
||||
/// holds. A request with no lower bound at all asks about all of history,
|
||||
/// which no file that has ever pruned can promise.
|
||||
complete: bool,
|
||||
/// The oldest instant the file is complete for, unix seconds.
|
||||
available_since: i64,
|
||||
};
|
||||
|
||||
pub fn of(available_since: i64, since: ?i64) Coverage {
|
||||
return .{
|
||||
.complete = if (since) |lower_bound| lower_bound >= available_since else false,
|
||||
.available_since = available_since,
|
||||
};
|
||||
}
|
||||
|
||||
/// Reads the watermark for a request that is about to answer.
|
||||
pub fn read(database: *db.Db, since: ?i64) db.Error!Coverage {
|
||||
return of(try queries_repo.availableSince(database), since);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const testing = std.testing;
|
||||
|
||||
test "a window that starts at or after the watermark is complete" {
|
||||
try testing.expect(of(1000, 1000).complete);
|
||||
try testing.expect(of(1000, 1001).complete);
|
||||
try testing.expect(!of(1000, 999).complete);
|
||||
}
|
||||
|
||||
test "an unbounded window is never complete" {
|
||||
const unbounded = of(1000, null);
|
||||
try testing.expect(!unbounded.complete);
|
||||
try testing.expectEqual(@as(i64, 1000), unbounded.available_since);
|
||||
}
|
||||
Reference in New Issue
Block a user