159 lines
3.5 KiB
Go
159 lines
3.5 KiB
Go
package resolver
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
type DNSErrorKind string
|
|
|
|
const (
|
|
DNSErrorNXDomain DNSErrorKind = "nxdomain"
|
|
DNSErrorTimeout DNSErrorKind = "timeout"
|
|
DNSErrorTemporary DNSErrorKind = "temporary"
|
|
DNSErrorOther DNSErrorKind = "other"
|
|
)
|
|
|
|
type DNSUpstreamMetrics struct {
|
|
Attempts int
|
|
OK int
|
|
NXDomain int
|
|
Timeout int
|
|
Temporary int
|
|
Other int
|
|
Skipped int
|
|
}
|
|
|
|
type DNSMetrics struct {
|
|
Attempts int
|
|
OK int
|
|
NXDomain int
|
|
Timeout int
|
|
Temporary int
|
|
Other int
|
|
Skipped int
|
|
|
|
PerUpstream map[string]*DNSUpstreamMetrics
|
|
}
|
|
|
|
func (m *DNSMetrics) EnsureUpstream(upstream string) *DNSUpstreamMetrics {
|
|
if m.PerUpstream == nil {
|
|
m.PerUpstream = map[string]*DNSUpstreamMetrics{}
|
|
}
|
|
if us, ok := m.PerUpstream[upstream]; ok {
|
|
return us
|
|
}
|
|
us := &DNSUpstreamMetrics{}
|
|
m.PerUpstream[upstream] = us
|
|
return us
|
|
}
|
|
|
|
func (m *DNSMetrics) AddSuccess(upstream string) {
|
|
m.Attempts++
|
|
m.OK++
|
|
us := m.EnsureUpstream(upstream)
|
|
us.Attempts++
|
|
us.OK++
|
|
}
|
|
|
|
func (m *DNSMetrics) AddError(upstream string, kind DNSErrorKind) {
|
|
m.Attempts++
|
|
us := m.EnsureUpstream(upstream)
|
|
us.Attempts++
|
|
switch kind {
|
|
case DNSErrorNXDomain:
|
|
m.NXDomain++
|
|
us.NXDomain++
|
|
case DNSErrorTimeout:
|
|
m.Timeout++
|
|
us.Timeout++
|
|
case DNSErrorTemporary:
|
|
m.Temporary++
|
|
us.Temporary++
|
|
default:
|
|
m.Other++
|
|
us.Other++
|
|
}
|
|
}
|
|
|
|
func (m *DNSMetrics) AddCooldownSkip(upstream string) {
|
|
m.Skipped++
|
|
us := m.EnsureUpstream(upstream)
|
|
us.Skipped++
|
|
}
|
|
|
|
func (m *DNSMetrics) Merge(other DNSMetrics) {
|
|
m.Attempts += other.Attempts
|
|
m.OK += other.OK
|
|
m.NXDomain += other.NXDomain
|
|
m.Timeout += other.Timeout
|
|
m.Temporary += other.Temporary
|
|
m.Other += other.Other
|
|
m.Skipped += other.Skipped
|
|
|
|
for upstream, src := range other.PerUpstream {
|
|
dst := m.EnsureUpstream(upstream)
|
|
dst.Attempts += src.Attempts
|
|
dst.OK += src.OK
|
|
dst.NXDomain += src.NXDomain
|
|
dst.Timeout += src.Timeout
|
|
dst.Temporary += src.Temporary
|
|
dst.Other += src.Other
|
|
dst.Skipped += src.Skipped
|
|
}
|
|
}
|
|
|
|
func (m DNSMetrics) TotalErrors() int {
|
|
return m.NXDomain + m.Timeout + m.Temporary + m.Other
|
|
}
|
|
|
|
func (m DNSMetrics) FormatPerUpstream() string {
|
|
if len(m.PerUpstream) == 0 {
|
|
return ""
|
|
}
|
|
keys := make([]string, 0, len(m.PerUpstream))
|
|
for k := range m.PerUpstream {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
parts := make([]string, 0, len(keys))
|
|
for _, k := range keys {
|
|
v := m.PerUpstream[k]
|
|
parts = append(parts, fmt.Sprintf("%s{attempts=%d ok=%d nxdomain=%d timeout=%d temporary=%d other=%d skipped=%d}", k, v.Attempts, v.OK, v.NXDomain, v.Timeout, v.Temporary, v.Other, v.Skipped))
|
|
}
|
|
return strings.Join(parts, "; ")
|
|
}
|
|
|
|
func (m DNSMetrics) FormatResolverHealth() string {
|
|
if len(m.PerUpstream) == 0 {
|
|
return ""
|
|
}
|
|
keys := make([]string, 0, len(m.PerUpstream))
|
|
for k := range m.PerUpstream {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
parts := make([]string, 0, len(keys))
|
|
for _, k := range keys {
|
|
v := m.PerUpstream[k]
|
|
if v == nil || v.Attempts <= 0 {
|
|
continue
|
|
}
|
|
okRate := float64(v.OK) / float64(v.Attempts)
|
|
timeoutRate := float64(v.Timeout) / float64(v.Attempts)
|
|
score := okRate*100.0 - timeoutRate*50.0
|
|
state := "bad"
|
|
switch {
|
|
case score >= 70 && timeoutRate <= 0.05:
|
|
state = "good"
|
|
case score >= 35:
|
|
state = "degraded"
|
|
default:
|
|
state = "bad"
|
|
}
|
|
parts = append(parts, fmt.Sprintf("%s{score=%.1f state=%s attempts=%d ok=%d timeout=%d nxdomain=%d temporary=%d other=%d skipped=%d}", k, score, state, v.Attempts, v.OK, v.Timeout, v.NXDomain, v.Temporary, v.Other, v.Skipped))
|
|
}
|
|
return strings.Join(parts, "; ")
|
|
}
|