Registry and Analytics: run, subnet, direction and protocol filters, successes-by-target chart

Registry (/registry):
- filters by run (slice by the address's cycle in that run), subnet
  (drop-down of configured subnets), direction (egress/ingress) and
  protocol (icmp, tcp, ssh, https, tls); status in scope is computed over
  the narrowed checks
- chart "successful checks per target (egress) / site (ingress)" when both
  direction and protocol are chosen; a row opens the list of addresses
  (dialog, CSV)
- API: direction/protocol parameters and run in GET /admin/registry,
  GET /admin/registry/breakdown and /breakdown/list
- subnet filter passes ids as one JSON parameter (SQLite variable limit)

Analytics (/analytics):
- subnet filter recomputes the whole page over the addresses of the run
  inside the subnet; only their checks are read; cache per run and subnet
- direction and protocol focus the page; with both set the registry chart
  is shown
- subnet parameter in GET /admin/analytics/runs/{id} and lists (JSON, CSV)

Docs: plans and summaries in docs/changes, README, API, USAGE.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ayurishchevandClaude Sonnet 5.5 committed 2026-10-06 14:23:48 +03:00
1 parent 068c10ea1c
commit ded196ec8d
40 files changed
+2545 -188

No files matched your search

+55 -19
View File
@@ -351,30 +351,36 @@ func TestMigration0009Indexes(t *testing.T) {
}
}
// TestRegistryLevelsQueryUsesIndex guards against a full scan of checks: both
// the per-address MAX(cycle_id) and the join back must go through
// idx_checks_registry_cycle.
// TestRegistryLevelsQueryUsesIndex guards against a full scan of checks: the
// per-address MAX(cycle_id) and the join back must go through
// idx_checks_registry_cycle, the run's slice through idx_checks_run.
func TestRegistryLevelsQueryUsesIndex(t *testing.T) {
d, ctx := newTestDB(t)
rows, err := d.QueryContext(ctx, "EXPLAIN QUERY PLAN "+registryLevelsQuery(3), 1, 2, 3)
if err != nil {
t.Fatal(err)
}
defer rows.Close()
var plan string
for rows.Next() {
var id, parent, unused int
var detail string
if err := rows.Scan(&id, &parent, &unused, &detail); err != nil {
for sl, index := range map[registrySlice]string{
{}: "idx_checks_registry_cycle",
{RunID: 1, Level: LevelIngress, Family: "tls"}: "idx_checks_run",
} {
q, args := registryLevelsQuery([]any{1, 2, 3}, sl)
rows, err := d.QueryContext(ctx, "EXPLAIN QUERY PLAN "+q, args...)
if err != nil {
t.Fatal(err)
}
plan += detail + "\n"
if strings.HasPrefix(detail, "SCAN") && strings.Contains(detail, "checks") {
t.Errorf("full scan of checks in plan:\n%s", plan)
var plan string
for rows.Next() {
var id, parent, unused int
var detail string
if err := rows.Scan(&id, &parent, &unused, &detail); err != nil {
t.Fatal(err)
}
plan += detail + "\n"
if strings.HasPrefix(detail, "SCAN") && strings.Contains(detail, "checks") {
t.Errorf("%+v: full scan of checks in plan:\n%s", sl, plan)
}
}
rows.Close()
if !strings.Contains(plan, index) {
t.Errorf("%+v: expected %s in plan:\n%s", sl, index, plan)
}
}
if !strings.Contains(plan, "idx_checks_registry_cycle") {
t.Errorf("expected idx_checks_registry_cycle in plan:\n%s", plan)
}
}
@@ -417,6 +423,17 @@ func TestScaleSmoke6440(t *testing.T) {
t.Fatalf("upsert check: %v", err)
}
}
// The run's result of the address (as aggregation would record it).
if _, err := d.ExecContext(ctx, `
INSERT INTO run_results (run_id, registry_id, ip_address, cycle_id, verdict, aggregated_at)
SELECT run_id, registry_id, ip_address, cycle_id, 'partial', ? FROM ip_queue WHERE id=?
`, timeToDB(Now()), ip.ID); err != nil {
t.Fatalf("run result: %v", err)
}
}
var runID int64
if err := d.QueryRowContext(ctx, `SELECT run_id FROM run_results LIMIT 1`).Scan(&runID); err != nil {
t.Fatalf("run id: %v", err)
}
start = time.Now()
@@ -430,6 +447,25 @@ func TestScaleSmoke6440(t *testing.T) {
if _, total, err = d.ListRegistryPage(ctx, RegistryFilter{LastResult: ResultPass}, 100, 0); err != nil || total != 0 {
t.Fatalf("registry last_result filter: total=%d err=%v", total, err)
}
// All filters at once, the subnet covering the whole registry.
page, total, err = d.ListRegistryPage(ctx, RegistryFilter{
RunID: runID, Subnet: "10.0.0.0/8", Level: LevelIngress, Family: "tcp", LastResult: ResultPartial,
}, 50, 0)
if err != nil || total != 100 || len(page) != 50 || page[0].Egress.Total != 0 || page[0].Ingress.Total != 18 {
t.Fatalf("registry slice: total=%d len=%d err=%v", total, len(page), err)
}
// The chart and the list behind one of its rows: 100 addresses x 6 checks per site.
bf := RegistryFilter{RunID: runID, Subnet: "10.0.0.0/8", Level: LevelIngress, Family: "tcp", LastResult: ResultPartial}
bd, err := d.RegistryBreakdown(ctx, bf)
if err != nil || bd.Addresses != 100 || len(bd.Rows) != 3 || bd.Rows[0].Total != 600 || bd.Rows[0].OK != 500 {
t.Fatalf("breakdown: %+v err=%v", bd, err)
}
if l, err := d.RegistryBreakdownList(ctx, bf, bd.Rows[0].Key); err != nil || len(l) != 600 {
t.Fatalf("breakdown list: len=%d err=%v", len(l), err)
}
if bd, err = d.RegistryBreakdown(ctx, RegistryFilter{Level: LevelEgress, Family: "https"}); err != nil || bd.Addresses != 100 || len(bd.Rows) != 6 {
t.Fatalf("breakdown, newest cycle: %+v err=%v", bd, err)
}
registryDur := time.Since(start)
start = time.Now()