153 lines
5.1 KiB
Go
153 lines
5.1 KiB
Go
package metrics
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
)
|
|
|
|
type Metrics struct {
|
|
Registry *prometheus.Registry
|
|
|
|
CheckRuns *prometheus.CounterVec
|
|
CheckSkipped *prometheus.CounterVec
|
|
CheckDuration *prometheus.HistogramVec
|
|
CheckStatus *prometheus.GaugeVec
|
|
CheckExitCode *prometheus.GaugeVec
|
|
CheckLastRun *prometheus.GaugeVec
|
|
CheckLastSuccess *prometheus.GaugeVec
|
|
CheckInterval *prometheus.GaugeVec
|
|
ChecksConfigured prometheus.Gauge
|
|
SpoolDepth prometheus.Gauge
|
|
SpoolBytes prometheus.Gauge
|
|
SendAttempts *prometheus.CounterVec
|
|
SendFailures *prometheus.CounterVec
|
|
EventsAccepted prometheus.Counter
|
|
EventsDedup prometheus.Counter
|
|
LastHeartbeat prometheus.Gauge
|
|
BuildInfo *prometheus.GaugeVec
|
|
}
|
|
|
|
// StatusCode maps a CheckResultEvent status string to a numeric gauge value.
|
|
func StatusCode(status string) float64 {
|
|
switch status {
|
|
case "ok":
|
|
return 0
|
|
case "warning":
|
|
return 1
|
|
case "critical":
|
|
return 2
|
|
default:
|
|
return 3
|
|
}
|
|
}
|
|
|
|
func New() *Metrics {
|
|
reg := prometheus.NewRegistry()
|
|
m := &Metrics{Registry: reg}
|
|
m.CheckRuns = prometheus.NewCounterVec(prometheus.CounterOpts{
|
|
Name: "monlet_agent_check_runs_total",
|
|
Help: "Number of check runs by id and status.",
|
|
}, []string{"check_id", "status"})
|
|
m.CheckSkipped = prometheus.NewCounterVec(prometheus.CounterOpts{
|
|
Name: "monlet_agent_check_skipped_total",
|
|
Help: "Number of ticks skipped due to overlap.",
|
|
}, []string{"check_id"})
|
|
m.CheckDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
|
Name: "monlet_agent_check_duration_seconds",
|
|
Help: "Check execution duration.",
|
|
Buckets: prometheus.DefBuckets,
|
|
}, []string{"check_id"})
|
|
m.SpoolDepth = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "monlet_agent_spool_events",
|
|
Help: "Current spool depth in events.",
|
|
})
|
|
m.SpoolBytes = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "monlet_agent_spool_bytes",
|
|
Help: "Current spool size in bytes.",
|
|
})
|
|
m.SendAttempts = prometheus.NewCounterVec(prometheus.CounterOpts{
|
|
Name: "monlet_agent_send_attempts_total",
|
|
Help: "Outbound send attempts by kind.",
|
|
}, []string{"kind"})
|
|
m.SendFailures = prometheus.NewCounterVec(prometheus.CounterOpts{
|
|
Name: "monlet_agent_send_failures_total",
|
|
Help: "Outbound send failures by kind.",
|
|
}, []string{"kind"})
|
|
m.EventsAccepted = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "monlet_agent_events_accepted_total",
|
|
Help: "Events accepted by the server.",
|
|
})
|
|
m.EventsDedup = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "monlet_agent_events_deduplicated_total",
|
|
Help: "Events the server reported as duplicates.",
|
|
})
|
|
m.CheckStatus = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
|
Name: "monlet_agent_check_status",
|
|
Help: "Last observed status per check: 0=ok,1=warning,2=critical,3=unknown.",
|
|
}, []string{"check_id"})
|
|
m.CheckExitCode = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
|
Name: "monlet_agent_check_exit_code",
|
|
Help: "Last exit code per check.",
|
|
}, []string{"check_id"})
|
|
m.CheckLastRun = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
|
Name: "monlet_agent_check_last_run_timestamp_seconds",
|
|
Help: "Unix timestamp of the last completed run per check.",
|
|
}, []string{"check_id"})
|
|
m.CheckLastSuccess = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
|
Name: "monlet_agent_check_last_success_timestamp_seconds",
|
|
Help: "Unix timestamp of the last ok run per check.",
|
|
}, []string{"check_id"})
|
|
m.CheckInterval = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
|
Name: "monlet_agent_check_interval_seconds",
|
|
Help: "Configured interval per check.",
|
|
}, []string{"check_id"})
|
|
m.ChecksConfigured = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "monlet_agent_checks_configured",
|
|
Help: "Number of checks configured.",
|
|
})
|
|
m.LastHeartbeat = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "monlet_agent_last_heartbeat_timestamp_seconds",
|
|
Help: "Unix timestamp of the last successful heartbeat ack.",
|
|
})
|
|
m.BuildInfo = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
|
Name: "monlet_agent_build_info",
|
|
Help: "Constant 1 with build labels.",
|
|
}, []string{"version", "mode"})
|
|
reg.MustRegister(m.CheckRuns, m.CheckSkipped, m.CheckDuration,
|
|
m.CheckStatus, m.CheckExitCode, m.CheckLastRun, m.CheckLastSuccess,
|
|
m.CheckInterval, m.ChecksConfigured,
|
|
m.SpoolDepth, m.SpoolBytes,
|
|
m.SendAttempts, m.SendFailures,
|
|
m.EventsAccepted, m.EventsDedup,
|
|
m.LastHeartbeat, m.BuildInfo)
|
|
return m
|
|
}
|
|
|
|
// Serve blocks until ctx is done; the listener is closed on shutdown.
|
|
func (m *Metrics) Serve(ctx context.Context, addr string) error {
|
|
mux := http.NewServeMux()
|
|
mux.Handle("/metrics", promhttp.HandlerFor(m.Registry, promhttp.HandlerOpts{}))
|
|
srv := &http.Server{Addr: addr, Handler: mux, ReadHeaderTimeout: 5 * time.Second}
|
|
|
|
errCh := make(chan error, 1)
|
|
go func() { errCh <- srv.ListenAndServe() }()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
shutCtx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancel()
|
|
_ = srv.Shutdown(shutCtx)
|
|
return nil
|
|
case err := <-errCh:
|
|
if errors.Is(err, http.ErrServerClosed) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
}
|