Files
monlet/agent/internal/metrics/metrics.go
2026-05-26 16:41:27 +04:00

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
}
}