Answers, result codes, record types, zones, blocking, rate limiting and the top lists are also counted per view, from named's response log. The Statistics page has a toggle per view with All and None, remembered per browser; jarl stats --view and ?views= on the API. named's own counters stay for the whole server, and the page says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
104 lines
3.6 KiB
Go
104 lines
3.6 KiB
Go
package stats
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Figures for chosen views: from the response log, per view, and added
|
|
// up for several.
|
|
func TestViewFilter(t *testing.T) {
|
|
ctx := context.Background()
|
|
loc := time.FixedZone("test", 0)
|
|
dir := t.TempDir()
|
|
logFile := filepath.Join(dir, "responses.log")
|
|
store, err := OpenStore(filepath.Join(dir, "stats.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer store.Close()
|
|
named := newFakeNamed(t)
|
|
clk := &clock{time.Date(2026, 10, 5, 10, 0, 30, 0, loc)}
|
|
opts := Options{QueryLog: true, LogFile: logFile, StatisticsChannel: "unused", TopN: 10,
|
|
RetainMinute: 7 * 24 * time.Hour, RetainHour: 90 * 24 * time.Hour, RetainDay: 730 * 24 * time.Hour, Location: loc}
|
|
zones := func() map[string][]string {
|
|
return map[string][]string{"internal": {"adbraun.lol"}, "external": {"adbraun.lol"}}
|
|
}
|
|
c := NewCollector(store, opts, zones, slog.New(slog.NewTextHandler(io.Discard, nil)))
|
|
c.poller.URL = named.srv.URL
|
|
c.now = clk.now
|
|
c.start(ctx)
|
|
at := time.Date(2026, 10, 5, 10, 0, 40, 0, loc)
|
|
lines := []string{
|
|
logLine(at, "10.10.0.37", "internal", "nas.adbraun.lol", "A", "NOERROR"),
|
|
logLine(at, "10.10.0.37", "internal", "nas.adbraun.lol", "AAAA", "NOERROR"),
|
|
logLine(at, "10.10.0.42", "internal", "nope.adbraun.lol", "A", "NXDOMAIN"),
|
|
logLine(at, "203.0.113.9", "external", "www.adbraun.lol", "A", "NOERROR"),
|
|
logLine(at, "203.0.113.9", "external", "x.example", "A", "REFUSED"),
|
|
logLine(at, "192.0.2.5", "vps", "debian.org", "AAAA", "NOERROR"),
|
|
}
|
|
f, _ := os.Create(logFile)
|
|
for _, l := range lines {
|
|
f.WriteString(l)
|
|
}
|
|
f.Close()
|
|
c.readLog()
|
|
named.add(6, 1)
|
|
clk.t = time.Date(2026, 10, 5, 10, 1, 2, 0, loc)
|
|
c.flush(ctx, false)
|
|
clk.t = time.Date(2026, 10, 5, 11, 0, 2, 0, loc)
|
|
c.flush(ctx, false)
|
|
|
|
all := &Querier{Store: store, Opts: opts, Now: clk.now}
|
|
s, err := all.Summary(ctx, 24*time.Hour, 5)
|
|
if err != nil || s.Queries != 6 || s.FilterViews != nil {
|
|
t.Fatalf("all views: %+v %v", s, err)
|
|
}
|
|
in := all.ForViews([]string{"internal"})
|
|
s, err = in.Summary(ctx, 24*time.Hour, 5)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s.Queries != 3 || len(s.Views) != 1 || s.Views[0].Key != "internal" || s.ViewsSince == nil {
|
|
t.Errorf("internal: queries %d views %v since %v", s.Queries, s.Views, s.ViewsSince)
|
|
}
|
|
if len(s.RCodes) != 2 || s.RCodes[0] != (Entry{Key: "NOERROR", Count: 2}) || len(s.QTypes) != 2 {
|
|
t.Errorf("internal rcodes %v qtypes %v", s.RCodes, s.QTypes)
|
|
}
|
|
if s.Tops[KindName][0] != (Entry{Key: "nas.adbraun.lol", Count: 2}) || len(s.Tops[KindNXDomain]) != 1 || len(s.Answers) != 0 {
|
|
t.Errorf("internal tops %v answers %v", s.Tops, s.Answers)
|
|
}
|
|
// Linked views, say: two together.
|
|
two := all.ForViews([]string{"internal", "external"})
|
|
s, _ = two.Summary(ctx, 24*time.Hour, 5)
|
|
if s.Queries != 5 || len(s.Views) != 2 {
|
|
t.Errorf("internal+external: %d %v", s.Queries, s.Views)
|
|
}
|
|
if es, _ := two.Top(ctx, KindClient, 24*time.Hour, 5); len(es) != 3 || es[2].Key != "10.10.0.42" {
|
|
t.Errorf("clients of two views: %v", es)
|
|
}
|
|
if es, _ := two.Breakdown(ctx, "rcode", 24*time.Hour); len(es) != 3 {
|
|
t.Errorf("rcodes of two views: %v", es)
|
|
}
|
|
if _, err := two.Breakdown(ctx, "answer", 24*time.Hour); !errors.Is(err, ErrNotPerView) {
|
|
t.Errorf("answers per view: %v", err)
|
|
}
|
|
tl, err := all.ForViews([]string{"vps"}).Timeline(ctx, 2*time.Hour, "1m", []string{"queries", "rcode:*"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var total int64
|
|
for _, v := range tl.Series[0].Values {
|
|
total += v
|
|
}
|
|
if total != 1 || len(tl.Series) != 2 || tl.Series[1].Key != "rcode:NOERROR" {
|
|
t.Errorf("vps timeline: %d %+v", total, tl.Series)
|
|
}
|
|
}
|