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) } }