diff --git a/checker/evaluate.go b/checker/evaluate.go index f64b2f1..8cef1aa 100644 --- a/checker/evaluate.go +++ b/checker/evaluate.go @@ -23,6 +23,7 @@ package checker import ( "fmt" + "strings" "time" ) @@ -52,43 +53,48 @@ type Metric struct { Timestamp time.Time `json:"timestamp"` } -// EvaluateResult holds the evaluation outcome for a single target. +// EvaluateResult holds the evaluation outcome. type EvaluateResult struct { - Address string `json:"address"` Status int `json:"status"` Message string `json:"message"` Code string `json:"code"` } const ( - StatusUnknown = 0 - StatusOK = 1 - StatusWarn = 3 - StatusCrit = 4 + StatusOK = 1 + StatusWarn = 3 + StatusCrit = 4 ) -// Evaluate checks the ping data against the given thresholds and returns one -// result per target. -func Evaluate(data *PingData, warningRTT, criticalRTT, warningPacketLoss, criticalPacketLoss float64) []EvaluateResult { +// Evaluate checks the ping data against the given thresholds. +// StatusUnknown indicates the check could not be performed. +const StatusUnknown = 0 + +func Evaluate(data *PingData, warningRTT, criticalRTT, warningPacketLoss, criticalPacketLoss float64) EvaluateResult { if len(data.Targets) == 0 { - return nil + return EvaluateResult{ + Status: StatusUnknown, + Message: "No targets to ping", + Code: "ping_no_targets", + } } - results := make([]EvaluateResult, 0, len(data.Targets)) + overallStatus := StatusOK + var summaryParts []string + for _, target := range data.Targets { - status := StatusOK if target.PacketLoss >= criticalPacketLoss || target.RTTAvg >= criticalRTT { - status = StatusCrit - } else if target.PacketLoss >= warningPacketLoss || target.RTTAvg >= warningRTT { - status = StatusWarn + overallStatus = StatusCrit + } else if (target.PacketLoss >= warningPacketLoss || target.RTTAvg >= warningRTT) && overallStatus < StatusWarn { + overallStatus = StatusWarn } - results = append(results, EvaluateResult{ - Address: target.Address, - Status: status, - Message: fmt.Sprintf("%.1fms avg, %.0f%% loss", target.RTTAvg, target.PacketLoss), - Code: "ping_result", - }) + summaryParts = append(summaryParts, fmt.Sprintf("%s: %.1fms avg, %.0f%% loss", target.Address, target.RTTAvg, target.PacketLoss)) + } + + return EvaluateResult{ + Status: overallStatus, + Message: strings.Join(summaryParts, " | "), + Code: "ping_result", } - return results } diff --git a/checker/interactive.go b/checker/interactive.go deleted file mode 100644 index 784db24..0000000 --- a/checker/interactive.go +++ /dev/null @@ -1,109 +0,0 @@ -// This file is part of the happyDomain (R) project. -// Copyright (c) 2020-2026 happyDomain -// Authors: Pierre-Olivier Mercier, et al. -// -// This program is offered under a commercial and under the AGPL license. -// For commercial licensing, contact us at . -// -// For AGPL licensing: -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package checker - -import ( - "errors" - "net/http" - "strconv" - "strings" - - sdk "git.happydns.org/checker-sdk-go/checker" -) - -// RenderForm implements sdk.CheckerInteractive. It exposes a minimal form -// letting a human submit one or more ping targets (hostnames or IPs) along -// with the usual threshold knobs. -func (p *pingProvider) RenderForm() []sdk.CheckerOptionField { - return []sdk.CheckerOptionField{ - { - Id: "addresses", - Type: "string", - Label: "Targets", - Placeholder: "example.com, 192.0.2.1", - Description: "Comma- or newline-separated list of hostnames or IP addresses.", - Required: true, - }, - { - Id: "count", - Type: "uint", - Label: "Number of pings to send", - Default: float64(5), - }, - { - Id: "warningRTT", - Type: "number", - Label: "Warning RTT threshold (ms)", - Default: float64(100), - }, - { - Id: "criticalRTT", - Type: "number", - Label: "Critical RTT threshold (ms)", - Default: float64(500), - }, - { - Id: "warningPacketLoss", - Type: "number", - Label: "Warning packet loss threshold (%)", - Default: float64(10), - }, - { - Id: "criticalPacketLoss", - Type: "number", - Label: "Critical packet loss threshold (%)", - Default: float64(50), - }, - } -} - -// ParseForm implements sdk.CheckerInteractive. It converts the HTML form -// inputs into a CheckerOptions that Collect can consume directly — pinging -// resolves hostnames on its own, so no extra lookups are needed here. -func (p *pingProvider) ParseForm(r *http.Request) (sdk.CheckerOptions, error) { - raw := strings.TrimSpace(r.FormValue("addresses")) - if raw == "" { - return nil, errors.New("at least one target is required") - } - - var addresses []string - for _, part := range strings.FieldsFunc(raw, func(c rune) bool { - return c == ',' || c == '\n' || c == '\r' || c == ' ' || c == '\t' || c == ';' - }) { - if part = strings.TrimSpace(part); part != "" { - addresses = append(addresses, part) - } - } - if len(addresses) == 0 { - return nil, errors.New("at least one target is required") - } - - opts := sdk.CheckerOptions{"addresses": addresses} - for _, k := range []string{"count", "warningRTT", "criticalRTT", "warningPacketLoss", "criticalPacketLoss"} { - if v := strings.TrimSpace(r.FormValue(k)); v != "" { - if n, err := strconv.ParseFloat(v, 64); err == nil { - opts[k] = n - } - } - } - return opts, nil -} diff --git a/checker/provider.go b/checker/provider.go index 3b7a411..ab6b7e8 100644 --- a/checker/provider.go +++ b/checker/provider.go @@ -53,9 +53,9 @@ func (p *pingProvider) Definition() *happydns.CheckerDefinition { } // ExtractMetrics implements happydns.CheckerMetricsReporter. -func (p *pingProvider) ExtractMetrics(ctx happydns.ReportContext, collectedAt time.Time) ([]happydns.CheckMetric, error) { +func (p *pingProvider) ExtractMetrics(raw json.RawMessage, collectedAt time.Time) ([]happydns.CheckMetric, error) { var data PingData - if err := json.Unmarshal(ctx.Data(), &data); err != nil { + if err := json.Unmarshal(raw, &data); err != nil { return nil, err } diff --git a/checker/rule.go b/checker/rule.go index f54d76a..5932cb1 100644 --- a/checker/rule.go +++ b/checker/rule.go @@ -106,14 +106,14 @@ func (r *pingRule) ValidateOptions(opts sdk.CheckerOptions) error { return nil } -func (r *pingRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState { +func (r *pingRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) sdk.CheckState { var data PingData if err := obs.Get(ctx, ObservationKeyPing, &data); err != nil { - return []sdk.CheckState{{ + return sdk.CheckState{ Status: sdk.StatusError, Message: fmt.Sprintf("Failed to get ping data: %v", err), Code: "ping_error", - }} + } } warningRTT := sdk.GetFloatOption(opts, "warningRTT", 100) @@ -121,44 +121,26 @@ func (r *pingRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts warningPacketLoss := sdk.GetFloatOption(opts, "warningPacketLoss", 10) criticalPacketLoss := sdk.GetFloatOption(opts, "criticalPacketLoss", 50) - results := Evaluate(&data, warningRTT, criticalRTT, warningPacketLoss, criticalPacketLoss) - if len(results) == 0 { - return []sdk.CheckState{{ - Status: sdk.StatusInfo, - Message: "No targets to ping", - Code: "ping_no_targets", - }} + result := Evaluate(&data, warningRTT, criticalRTT, warningPacketLoss, criticalPacketLoss) + + var status sdk.Status + switch result.Status { + case StatusOK: + status = sdk.StatusOK + case StatusWarn: + status = sdk.StatusWarn + case StatusCrit: + status = sdk.StatusCrit + default: + status = sdk.StatusUnknown } - targetByAddr := make(map[string]PingTargetResult, len(data.Targets)) - for _, t := range data.Targets { - targetByAddr[t.Address] = t + return sdk.CheckState{ + Status: status, + Message: result.Message, + Code: result.Code, + Meta: map[string]any{ + "targets": data.Targets, + }, } - - out := make([]sdk.CheckState, 0, len(results)) - for _, r := range results { - var status sdk.Status - switch r.Status { - case StatusOK: - status = sdk.StatusOK - case StatusWarn: - status = sdk.StatusWarn - case StatusCrit: - status = sdk.StatusCrit - default: - status = sdk.StatusUnknown - } - - state := sdk.CheckState{ - Status: status, - Subject: r.Address, - Message: r.Message, - Code: r.Code, - } - if t, ok := targetByAddr[r.Address]; ok { - state.Meta = map[string]any{"target": t} - } - out = append(out, state) - } - return out } diff --git a/go.mod b/go.mod index 8e1e592..02a7c39 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module git.happydns.org/checker-ping go 1.25.0 require ( - git.happydns.org/checker-sdk-go v1.2.0 git.happydns.org/happyDomain v0.7.0 github.com/prometheus-community/pro-bing v0.8.0 ) require ( + git.happydns.org/checker-sdk-go v0.0.1 github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect diff --git a/go.sum b/go.sum index 630d66c..4a5626b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.happydns.org/checker-sdk-go v1.2.0 h1:v4MpKAz0W3PwP+bxx3pya8w893sVH5xTD1of1cc0TV8= -git.happydns.org/checker-sdk-go v1.2.0/go.mod h1:aNAcfYFfbhvH9kJhE0Njp5GX0dQbxdRB0rJ0KvSC5nI= +git.happydns.org/checker-sdk-go v0.0.1 h1:4RxCJr73HWKxjOyU/6NJMO8lXJmH0gMLA68EzTqLbQI= +git.happydns.org/checker-sdk-go v0.0.1/go.mod h1:aNAcfYFfbhvH9kJhE0Njp5GX0dQbxdRB0rJ0KvSC5nI= git.happydns.org/happyDomain v0.7.0 h1:NV82/NbcSeRm0+IUZqaK3Vu9Ovl5+vv4AigUJZMdwws= git.happydns.org/happyDomain v0.7.0/go.mod h1:5tgkmqFE65kK359rY49V++49wgZ0gco+Gh9X6tbL+bY= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=