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 }