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:
1 parent
068c10ea1c
commit
ded196ec8d
40 files changed
+2545
-188
No files matched your search
@@ -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()
|
||||
|
||||
Reference in new issue
Block a user