package checker import ( "encoding/json" "fmt" "strings" "time" sdk "git.happydns.org/checker-sdk-go/checker" ) // Implements sdk.CheckerMetricsReporter. func (p *authoritativeConsistencyProvider) ExtractMetrics(ctx sdk.ReportContext, collectedAt time.Time) ([]sdk.CheckMetric, error) { var data ObservationData if err := json.Unmarshal(ctx.Data(), &data); err != nil { return nil, fmt.Errorf("checker: decoding observation: %w", err) } var out []sdk.CheckMetric for name, r := range data.Results { labels := map[string]string{"zone": data.Zone, "ns": name} up := float64(0) if r.UDPReachable && r.Authoritative { up = 1 } out = append(out, sdk.CheckMetric{ Name: "authoritative_consistency_ns_up", Value: up, Labels: labels, Timestamp: collectedAt, }) tcp := float64(0) if r.TCPReachable { tcp = 1 } out = append(out, sdk.CheckMetric{ Name: "authoritative_consistency_ns_tcp", Value: tcp, Labels: labels, Timestamp: collectedAt, }) if r.LatencyMs > 0 { out = append(out, sdk.CheckMetric{ Name: "authoritative_consistency_ns_latency_ms", Value: float64(r.LatencyMs), Unit: "ms", Labels: labels, Timestamp: collectedAt, }) } if r.Serial > 0 { out = append(out, sdk.CheckMetric{ Name: "authoritative_consistency_ns_serial", Value: float64(r.Serial), Labels: labels, Timestamp: collectedAt, }) } resolvable := float64(0) if len(r.Addresses) > 0 { resolvable = 1 } out = append(out, sdk.CheckMetric{ Name: "authoritative_consistency_ns_resolvable", Value: resolvable, Labels: labels, Timestamp: collectedAt, }) // EDNS support is only meaningful once the server has actually answered. if r.UDPReachable { edns := float64(0) if r.EDNSSupported { edns = 1 } out = append(out, sdk.CheckMetric{ Name: "authoritative_consistency_ns_edns", Value: edns, Labels: labels, Timestamp: collectedAt, }) } } zoneLabels := map[string]string{"zone": data.Zone} uniqueSerials := map[uint32]struct{}{} var minSerial, maxSerial uint32 serialSeen := false for _, r := range data.Results { if r == nil || !r.Authoritative || r.SOA == nil { continue } uniqueSerials[r.Serial] = struct{}{} if !serialSeen { minSerial, maxSerial = r.Serial, r.Serial serialSeen = true continue } if r.Serial < minSerial { minSerial = r.Serial } if r.Serial > maxSerial { maxSerial = r.Serial } } out = append(out, sdk.CheckMetric{ Name: "authoritative_consistency_unique_serials", Value: float64(len(uniqueSerials)), Labels: zoneLabels, Timestamp: collectedAt, }) if data.HasSOA && data.DeclaredSerial > 0 { out = append(out, sdk.CheckMetric{ Name: "authoritative_consistency_serial", Value: float64(data.DeclaredSerial), Labels: zoneLabels, Timestamp: collectedAt, }) } if serialSeen { out = append(out, sdk.CheckMetric{ Name: "authoritative_consistency_serial_spread", Value: float64(maxSerial - minSerial), Labels: zoneLabels, Timestamp: collectedAt, }) } out = append(out, sdk.CheckMetric{ Name: "authoritative_consistency_ns_declared", Value: float64(len(data.DeclaredNS)), Labels: zoneLabels, Timestamp: collectedAt, }) reachable := 0 for _, r := range data.Results { if r != nil && r.UDPReachable && r.Authoritative { reachable++ } } out = append(out, sdk.CheckMetric{ Name: "authoritative_consistency_ns_reachable", Value: float64(reachable), Labels: zoneLabels, Timestamp: collectedAt, }) if data.ParentQueryError == "" && len(data.ParentNS) > 0 && len(data.DeclaredNS) > 0 { missing, extra := diffStringSets(data.DeclaredNS, data.ParentNS) match := float64(1) if len(missing) > 0 || len(extra) > 0 { match = 0 } out = append(out, sdk.CheckMetric{ Name: "authoritative_consistency_parent_delegation_match", Value: match, Labels: zoneLabels, Timestamp: collectedAt, }) } rrsetGroups := map[string]struct{}{} for _, r := range data.Results { if r == nil || !r.Authoritative || len(r.NSRRset) == 0 { continue } rrsetGroups[strings.Join(r.NSRRset, "|")] = struct{}{} } if len(rrsetGroups) > 0 { v := float64(1) if len(rrsetGroups) > 1 { v = 0 } out = append(out, sdk.CheckMetric{ Name: "authoritative_consistency_ns_rrset_consistent", Value: v, Labels: zoneLabels, Timestamp: collectedAt, }) } if data.HasSOA && serialSeen { v := float64(1) if len(collectSOAFieldsDrift(&data)) > 0 { v = 0 } out = append(out, sdk.CheckMetric{ Name: "authoritative_consistency_soa_fields_consistent", Value: v, Labels: zoneLabels, Timestamp: collectedAt, }) } return out, nil }