After a reconfig that changed named's views, the cache and resolver counters (summed over views) went down, which was taken for a restart: every counter then counted its total since named started for one minute. Only a new boot time means a restart now; a counter that goes down counts 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
62 lines
2.5 KiB
Go
62 lines
2.5 KiB
Go
package stats
|
|
|
|
import "testing"
|
|
|
|
// A trimmed /json/v1/server answer from named 9.20.24.
|
|
const sampleJSON = `{"json-stats-version":"1.8","boot-time":"2026-10-07T19:46:51.322Z",
|
|
"opcodes":{"QUERY":10,"IQUERY":0,"NOTIFY":0},
|
|
"rcodes":{"NOERROR":7,"NXDOMAIN":2,"SERVFAIL":1,"RESERVED11":0,"17":0},
|
|
"qtypes":{"A":6,"AAAA":3,"MX":1},
|
|
"nsstats":{"Requestv4":10,"QryAuthAns":4,"QryNoauthAns":6,"QryRecursion":2,"QryUDP":9,"QryTCP":1},
|
|
"views":{"internal":{"resolver":{"stats":{"QryRTT100":3,"QryRTT500":1,"ValOk":2},"cachestats":{"QueryHits":4,"QueryMisses":2,"CacheHits":345}}},
|
|
"_bind":{"resolver":{"stats":{},"cachestats":{}}}}}`
|
|
|
|
func TestFlattenAndDelta(t *testing.T) {
|
|
s1, err := Flatten([]byte(sampleJSON))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := map[string]int64{"queries": 10, "rcode:NOERROR": 7, "rcode:NXDOMAIN": 2, "rcode:SERVFAIL": 1, "qtype:A": 6, "qtype:AAAA": 3, "qtype:MX": 1,
|
|
"answer:authoritative": 4, "answer:recursive": 6, "recursion": 2, "transport:udp": 9, "transport:tcp": 1,
|
|
"rtt:10-100ms": 3, "rtt:100-500ms": 1, "dnssec:valid": 2, "cache:hits": 4, "cache:misses": 2}
|
|
for k, v := range want {
|
|
if s1.Values[k] != v {
|
|
t.Errorf("%s = %d, want %d", k, s1.Values[k], v)
|
|
}
|
|
}
|
|
if len(s1.Values) != len(want) {
|
|
t.Errorf("unexpected keys: %v", s1.Values)
|
|
}
|
|
// Normal change.
|
|
s2 := &Snapshot{BootTime: s1.BootTime, Values: map[string]int64{}}
|
|
for k, v := range s1.Values {
|
|
s2.Values[k] = v
|
|
}
|
|
s2.Values["queries"] += 5
|
|
s2.Values["qtype:A"] += 5
|
|
d := Delta(s1, s2)
|
|
if d["queries"] != 5 || d["qtype:A"] != 5 || d["qtype:AAAA"] != 0 || len(d) != 2 {
|
|
t.Errorf("delta = %v", d)
|
|
}
|
|
// Quiet minute: queries is still recorded (as 0) so coverage is visible.
|
|
if d := Delta(s2, s2); len(d) != 1 || d["queries"] != 0 {
|
|
t.Errorf("quiet delta = %v", d)
|
|
}
|
|
// named restarted: counters start again from zero.
|
|
s3 := &Snapshot{BootTime: "2026-10-07T20:00:00.000Z", Values: map[string]int64{"queries": 3, "qtype:A": 3}}
|
|
if d := Delta(s2, s3); d["queries"] != 3 || d["qtype:A"] != 3 {
|
|
t.Errorf("restart delta = %v", d)
|
|
}
|
|
// A counter going down without a restart (views changed in a
|
|
// reconfig) counts 0, and doesn't make the others count from zero.
|
|
s4 := &Snapshot{BootTime: s2.BootTime, Values: map[string]int64{}}
|
|
for k, v := range s2.Values {
|
|
s4.Values[k] = v
|
|
}
|
|
s4.Values["queries"] += 4
|
|
s4.Values["cache:hits"] = 1 // was 4: a view's cache counters are gone
|
|
if d := Delta(s2, s4); d["queries"] != 4 || d["cache:hits"] != 0 || len(d) != 1 {
|
|
t.Errorf("delta with a counter going down = %v", d)
|
|
}
|
|
}
|