platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
158
selective-vpn-api/app/resolver/dns_metrics.go
Normal file
158
selective-vpn-api/app/resolver/dns_metrics.go
Normal file
@@ -0,0 +1,158 @@
|
||||
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, "; ")
|
||||
}
|
||||
Reference in New Issue
Block a user