Files
wg-portal/internal/adapters/metrics.go
T

157 lines
5.0 KiB
Go
Raw Normal View History

2024-09-29 22:10:50 +02:00
package adapters
import (
"context"
2025-02-28 08:29:40 +01:00
"errors"
"log/slog"
2024-09-29 22:10:50 +02:00
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
2025-02-28 08:29:40 +01:00
"github.com/h44z/wg-portal/internal"
"github.com/h44z/wg-portal/internal/config"
"github.com/h44z/wg-portal/internal/domain"
2024-09-29 22:10:50 +02:00
)
type MetricsServer struct {
*http.Server
ifaceReceivedBytesTotal *prometheus.GaugeVec
ifaceSendBytesTotal *prometheus.GaugeVec
peerIsConnected *prometheus.GaugeVec
peerLastHandshakeSeconds *prometheus.GaugeVec
peerReceivedBytesTotal *prometheus.GaugeVec
peerSendBytesTotal *prometheus.GaugeVec
}
// Wireguard metrics labels
var (
ifaceLabels = []string{"interface"}
peerLabels = []string{"interface", "addresses", "id", "name", "user"}
2024-09-29 22:10:50 +02:00
)
// NewMetricsServer returns a new prometheus server
func NewMetricsServer(cfg *config.Config) *MetricsServer {
2024-09-29 22:10:50 +02:00
reg := prometheus.NewRegistry()
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg}))
return &MetricsServer{
Server: &http.Server{
Addr: cfg.Statistics.ListeningAddress,
Handler: mux,
},
ifaceReceivedBytesTotal: promauto.With(reg).NewGaugeVec(
prometheus.GaugeOpts{
Name: "wireguard_interface_received_bytes_total",
Help: "Bytes received througth the interface.",
}, ifaceLabels,
2024-09-29 22:10:50 +02:00
),
ifaceSendBytesTotal: promauto.With(reg).NewGaugeVec(
prometheus.GaugeOpts{
Name: "wireguard_interface_sent_bytes_total",
Help: "Bytes sent through the interface.",
}, ifaceLabels,
2024-09-29 22:10:50 +02:00
),
peerIsConnected: promauto.With(reg).NewGaugeVec(
prometheus.GaugeOpts{
Name: "wireguard_peer_up",
Help: "Peer connection state (boolean: 1/0).",
}, peerLabels,
2024-09-29 22:10:50 +02:00
),
peerLastHandshakeSeconds: promauto.With(reg).NewGaugeVec(
prometheus.GaugeOpts{
Name: "wireguard_peer_last_handshake_seconds",
Help: "Seconds from the last handshake with the peer.",
}, peerLabels,
2024-09-29 22:10:50 +02:00
),
peerReceivedBytesTotal: promauto.With(reg).NewGaugeVec(
prometheus.GaugeOpts{
Name: "wireguard_peer_received_bytes_total",
Help: "Bytes received from the peer.",
}, peerLabels,
2024-09-29 22:10:50 +02:00
),
peerSendBytesTotal: promauto.With(reg).NewGaugeVec(
prometheus.GaugeOpts{
Name: "wireguard_peer_sent_bytes_total",
Help: "Bytes sent to the peer.",
}, peerLabels,
2024-09-29 22:10:50 +02:00
),
}
}
2025-02-28 16:11:55 +01:00
// Run starts the metrics server. The function blocks until the context is cancelled.
2024-09-29 22:10:50 +02:00
func (m *MetricsServer) Run(ctx context.Context) {
// Run the metrics server in a goroutine
go func() {
2025-02-28 08:29:40 +01:00
if err := m.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
slog.Error("metrics service exited", "address", m.Addr, "error", err)
2024-09-29 22:10:50 +02:00
}
}()
slog.Info("started metrics service", "address", m.Addr)
2024-09-29 22:10:50 +02:00
// Wait for the context to be done
<-ctx.Done()
// Create a context with timeout for the shutdown process
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
2025-02-28 16:11:55 +01:00
// Attempt to gracefully shut down the metrics server
2024-09-29 22:10:50 +02:00
if err := m.Shutdown(shutdownCtx); err != nil {
slog.Error("metrics service shutdown failed", "address", m.Addr, "error", err)
2024-09-29 22:10:50 +02:00
} else {
slog.Info("metrics service shutdown gracefully", "address", m.Addr)
2024-09-29 22:10:50 +02:00
}
}
// UpdateInterfaceMetrics updates the metrics for the given interface
func (m *MetricsServer) UpdateInterfaceMetrics(status domain.InterfaceStatus) {
labels := []string{string(status.InterfaceId)}
m.ifaceReceivedBytesTotal.WithLabelValues(labels...).Set(float64(status.BytesReceived))
m.ifaceSendBytesTotal.WithLabelValues(labels...).Set(float64(status.BytesTransmitted))
}
// UpdatePeerMetrics updates the metrics for the given peer
func (m *MetricsServer) UpdatePeerMetrics(peer *domain.Peer, status domain.PeerStatus) {
2024-09-29 22:10:50 +02:00
labels := []string{
string(peer.InterfaceIdentifier),
2025-02-28 16:11:55 +01:00
peer.Interface.AddressStr(),
2024-09-29 22:10:50 +02:00
string(status.PeerId),
2025-02-28 16:11:55 +01:00
peer.DisplayName,
string(peer.UserIdentifier),
2024-09-29 22:10:50 +02:00
}
if status.LastHandshake != nil {
m.peerLastHandshakeSeconds.WithLabelValues(labels...).Set(float64(status.LastHandshake.Unix()))
}
m.peerReceivedBytesTotal.WithLabelValues(labels...).Set(float64(status.BytesReceived))
m.peerSendBytesTotal.WithLabelValues(labels...).Set(float64(status.BytesTransmitted))
m.peerIsConnected.WithLabelValues(labels...).Set(internal.BoolToFloat64(status.IsConnected))
2024-09-29 22:10:50 +02:00
}
2026-09-02 03:28:10 +07:00
// DeletePeerMetrics removes all Prometheus series for a peer that was deleted
// from the WireGuard portal. GaugeVecs retain label values until explicitly
// deleted, so leaving these behind makes a deleted peer indistinguishable from
// a retained peer that is currently offline.
func (m *MetricsServer) DeletePeerMetrics(peer domain.Peer) {
labels := []string{
string(peer.InterfaceIdentifier),
peer.Interface.AddressStr(),
string(peer.Identifier),
peer.DisplayName,
string(peer.UserIdentifier),
}
m.peerLastHandshakeSeconds.DeleteLabelValues(labels...)
m.peerReceivedBytesTotal.DeleteLabelValues(labels...)
m.peerSendBytesTotal.DeleteLabelValues(labels...)
m.peerIsConnected.DeleteLabelValues(labels...)
}