checker-resolver-propagation/checker/metrics.go

137 lines
3.5 KiB
Go

package checker
import (
"encoding/json"
"fmt"
"time"
sdk "git.happydns.org/checker-sdk-go/checker"
)
// ExtractMetrics implements sdk.CheckerMetricsReporter. It consumes the raw
// observation, derives consensus on the fly, and emits time-series for
// dashboards. Severity counters are computed from the rule states carried
// in the ReportContext rather than re-derived from raw data.
func (p *resolverPropagationProvider) ExtractMetrics(ctx sdk.ReportContext, collectedAt time.Time) ([]sdk.CheckMetric, error) {
var data ResolverPropagationData
if err := json.Unmarshal(ctx.Data(), &data); err != nil {
return nil, fmt.Errorf("resolver-propagation: decoding observation: %w", err)
}
deriveView(&data)
var out []sdk.CheckMetric
zone := data.Zone
rollups := []struct {
name string
val float64
}{
{"resolver_propagation_resolvers_total", float64(data.Stats.TotalResolvers)},
{"resolver_propagation_resolvers_reachable", float64(data.Stats.ReachableResolvers)},
{"resolver_propagation_unfiltered_agreeing", float64(data.Stats.UnfilteredAgreeing)},
{"resolver_propagation_regions_covered", float64(data.Stats.CountriesCovered)},
{"resolver_propagation_run_duration_ms", float64(data.RunDurationMs)},
}
for _, r := range rollups {
out = append(out, sdk.CheckMetric{
Name: r.name,
Value: r.val,
Labels: map[string]string{"zone": zone},
Timestamp: collectedAt,
})
}
if data.DeclaredSerial != 0 {
out = append(out, sdk.CheckMetric{
Name: "resolver_propagation_declared_serial",
Value: float64(data.DeclaredSerial),
Labels: map[string]string{"zone": zone},
Timestamp: collectedAt,
})
}
soaKey := rrsetKey(zone, "SOA")
var staleResolvers int
for id, rv := range data.Resolvers {
if rv.Filtered {
continue
}
p := rv.Probes[soaKey]
if p == nil || p.Error != "" || p.Rcode != "NOERROR" {
continue
}
s := extractSerial(p.Records)
if s == 0 {
continue
}
out = append(out, sdk.CheckMetric{
Name: "resolver_propagation_observed_serial",
Value: float64(s),
Labels: map[string]string{
"zone": zone,
"resolver": id,
},
Timestamp: collectedAt,
})
if data.DeclaredSerial != 0 && s < data.DeclaredSerial {
staleResolvers++
}
}
if data.DeclaredSerial != 0 {
out = append(out, sdk.CheckMetric{
Name: "resolver_propagation_serial_drift_resolvers",
Value: float64(staleResolvers),
Labels: map[string]string{"zone": zone},
Timestamp: collectedAt,
})
}
for id, rv := range data.Resolvers {
labels := map[string]string{
"zone": zone,
"resolver": id,
"ip": rv.IP,
"region": rv.Region,
"transport": string(rv.Transport),
}
up := float64(0)
if rv.Reachable {
up = 1
}
out = append(out, sdk.CheckMetric{
Name: "resolver_propagation_resolver_up",
Value: up, Labels: labels, Timestamp: collectedAt,
})
var total, n int64
for _, p := range rv.Probes {
if p.Error != "" {
continue
}
total += p.LatencyMs
n++
}
if n > 0 {
out = append(out, sdk.CheckMetric{
Name: "resolver_propagation_resolver_latency_ms",
Value: float64(total) / float64(n), Unit: "ms",
Labels: labels, Timestamp: collectedAt,
})
}
}
for key, v := range data.RRsets {
out = append(out, sdk.CheckMetric{
Name: "resolver_propagation_rrset_signatures",
Value: float64(len(v.Groups)),
Labels: map[string]string{
"zone": zone,
"rrset": key,
},
Timestamp: collectedAt,
})
}
return out, nil
}