mirror of
https://github.com/h44z/wg-portal.git
synced 2025-09-14 15:01:14 +00:00
feat: Metrics for Prometheus (#309)
* feat: prometheus metrics * Added Prometheus resources support to helm chart
This commit is contained in:
161
internal/adapters/metrics.go
Normal file
161
internal/adapters/metrics.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package adapters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal"
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type MetricsServer struct {
|
||||
*http.Server
|
||||
db *SqlRepo
|
||||
|
||||
ifaceInfo *prometheus.GaugeVec
|
||||
ifaceReceivedBytesTotal *prometheus.GaugeVec
|
||||
ifaceSendBytesTotal *prometheus.GaugeVec
|
||||
peerInfo *prometheus.GaugeVec
|
||||
peerIsConnected *prometheus.GaugeVec
|
||||
peerLastHandshakeSeconds *prometheus.GaugeVec
|
||||
peerReceivedBytesTotal *prometheus.GaugeVec
|
||||
peerSendBytesTotal *prometheus.GaugeVec
|
||||
}
|
||||
|
||||
// Wireguard metrics labels
|
||||
var (
|
||||
labels = []string{"interface"}
|
||||
ifaceLabels = []string{}
|
||||
peerLabels = []string{"addresses", "id", "name"}
|
||||
)
|
||||
|
||||
// NewMetricsServer returns a new prometheus server
|
||||
func NewMetricsServer(cfg *config.Config, db *SqlRepo) *MetricsServer {
|
||||
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,
|
||||
},
|
||||
db: db,
|
||||
|
||||
ifaceInfo: promauto.With(reg).NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wireguard_interface_info",
|
||||
Help: "Interface info.",
|
||||
}, append(labels, ifaceLabels...),
|
||||
),
|
||||
ifaceReceivedBytesTotal: promauto.With(reg).NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wireguard_interface_received_bytes_total",
|
||||
Help: "Bytes received througth the interface.",
|
||||
}, append(labels, ifaceLabels...),
|
||||
),
|
||||
ifaceSendBytesTotal: promauto.With(reg).NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wireguard_interface_sent_bytes_total",
|
||||
Help: "Bytes sent through the interface.",
|
||||
}, append(labels, ifaceLabels...),
|
||||
),
|
||||
|
||||
peerInfo: promauto.With(reg).NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wireguard_peer_info",
|
||||
Help: "Peer info.",
|
||||
}, append(labels, peerLabels...),
|
||||
),
|
||||
peerIsConnected: promauto.With(reg).NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wireguard_peer_up",
|
||||
Help: "Peer connection state (boolean: 1/0).",
|
||||
}, append(labels, peerLabels...),
|
||||
),
|
||||
peerLastHandshakeSeconds: promauto.With(reg).NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wireguard_peer_last_handshake_seconds",
|
||||
Help: "Seconds from the last handshake with the peer.",
|
||||
}, append(labels, peerLabels...),
|
||||
),
|
||||
peerReceivedBytesTotal: promauto.With(reg).NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wireguard_peer_received_bytes_total",
|
||||
Help: "Bytes received from the peer.",
|
||||
}, append(labels, peerLabels...),
|
||||
),
|
||||
peerSendBytesTotal: promauto.With(reg).NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wireguard_peer_sent_bytes_total",
|
||||
Help: "Bytes sent to the peer.",
|
||||
}, append(labels, peerLabels...),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// Run starts the metrics server
|
||||
func (m *MetricsServer) Run(ctx context.Context) {
|
||||
// Run the metrics server in a goroutine
|
||||
go func() {
|
||||
if err := m.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
logrus.Errorf("metrics service on %s exited: %v", m.Addr, err)
|
||||
}
|
||||
}()
|
||||
|
||||
logrus.Infof("started metrics service on %s", m.Addr)
|
||||
|
||||
// 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()
|
||||
|
||||
// Attempt to gracefully shutdown the metrics server
|
||||
if err := m.Shutdown(shutdownCtx); err != nil {
|
||||
logrus.Errorf("metrics service on %s shutdown failed: %v", m.Addr, err)
|
||||
} else {
|
||||
logrus.Infof("metrics service on %s shutdown gracefully", m.Addr)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateInterfaceMetrics updates the metrics for the given interface
|
||||
func (m *MetricsServer) UpdateInterfaceMetrics(status domain.InterfaceStatus) {
|
||||
labels := []string{string(status.InterfaceId)}
|
||||
m.ifaceInfo.WithLabelValues(labels...).Set(1)
|
||||
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(ctx context.Context, status domain.PeerStatus) {
|
||||
// Fetch peer data from the database
|
||||
peer, err := m.db.GetPeer(ctx, status.PeerId)
|
||||
if err != nil {
|
||||
logrus.Warnf("failed to fetch peer data for labels %s: %v", status.PeerId, err)
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{
|
||||
string(peer.InterfaceIdentifier),
|
||||
string(peer.Interface.AddressStr()),
|
||||
string(status.PeerId),
|
||||
string(peer.DisplayName),
|
||||
}
|
||||
|
||||
m.peerInfo.WithLabelValues(labels...).Set(1)
|
||||
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()))
|
||||
}
|
@@ -95,8 +95,6 @@ func NewServer(cfg *config.Config, endpoints ...ApiEndpointSetupFunc) (*Server,
|
||||
}
|
||||
|
||||
func (s *Server) Run(ctx context.Context, listenAddress string) {
|
||||
logrus.Infof("starting web service on %s", listenAddress)
|
||||
|
||||
// Run web service
|
||||
srv := &http.Server{
|
||||
Addr: listenAddress,
|
||||
@@ -116,6 +114,7 @@ func (s *Server) Run(ctx context.Context, listenAddress string) {
|
||||
cancelFn()
|
||||
}
|
||||
}()
|
||||
logrus.Infof("started web service on %s", listenAddress)
|
||||
|
||||
// Wait for the main context to end
|
||||
<-srvContext.Done()
|
||||
|
@@ -2,6 +2,7 @@ package wireguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
@@ -27,6 +28,7 @@ type InterfaceAndPeerDatabaseRepo interface {
|
||||
type StatisticsDatabaseRepo interface {
|
||||
GetAllInterfaces(ctx context.Context) ([]domain.Interface, error)
|
||||
GetInterfacePeers(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.Peer, error)
|
||||
GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error)
|
||||
|
||||
UpdatePeerStatus(ctx context.Context, id domain.PeerIdentifier, updateFunc func(in *domain.PeerStatus) (*domain.PeerStatus, error)) error
|
||||
UpdateInterfaceStatus(ctx context.Context, id domain.InterfaceIdentifier, updateFunc func(in *domain.InterfaceStatus) (*domain.InterfaceStatus, error)) error
|
||||
@@ -48,3 +50,8 @@ type WgQuickController interface {
|
||||
SetDNS(id domain.InterfaceIdentifier, dnsStr, dnsSearchStr string) error
|
||||
UnsetDNS(id domain.InterfaceIdentifier) error
|
||||
}
|
||||
|
||||
type MetricsServer interface {
|
||||
UpdateInterfaceMetrics(status domain.InterfaceStatus)
|
||||
UpdatePeerMetrics(ctx context.Context, status domain.PeerStatus)
|
||||
}
|
||||
|
@@ -2,12 +2,13 @@ package wireguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
"github.com/prometheus-community/pro-bing"
|
||||
"github.com/sirupsen/logrus"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
probing "github.com/prometheus-community/pro-bing"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type StatisticsCollector struct {
|
||||
@@ -18,14 +19,16 @@ type StatisticsCollector struct {
|
||||
|
||||
db StatisticsDatabaseRepo
|
||||
wg InterfaceController
|
||||
ms MetricsServer
|
||||
}
|
||||
|
||||
func NewStatisticsCollector(cfg *config.Config, db StatisticsDatabaseRepo, wg InterfaceController) (*StatisticsCollector, error) {
|
||||
func NewStatisticsCollector(cfg *config.Config, db StatisticsDatabaseRepo, wg InterfaceController, ms MetricsServer) (*StatisticsCollector, error) {
|
||||
return &StatisticsCollector{
|
||||
cfg: cfg,
|
||||
|
||||
db: db,
|
||||
wg: wg,
|
||||
ms: ms,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -70,11 +73,15 @@ func (c *StatisticsCollector) collectInterfaceData(ctx context.Context) {
|
||||
i.UpdatedAt = time.Now()
|
||||
i.BytesReceived = physicalInterface.BytesDownload
|
||||
i.BytesTransmitted = physicalInterface.BytesUpload
|
||||
|
||||
// Update prometheus metrics
|
||||
go c.ms.UpdateInterfaceMetrics(*i)
|
||||
return i, nil
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Warnf("failed to update interface status for %s: %v", in.Identifier, err)
|
||||
}
|
||||
logrus.Tracef("updated interface status for %s", in.Identifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,11 +133,15 @@ func (c *StatisticsCollector) collectPeerData(ctx context.Context) {
|
||||
p.Endpoint = peer.Endpoint
|
||||
p.LastHandshake = lastHandshake
|
||||
|
||||
// Update prometheus metrics
|
||||
go c.ms.UpdatePeerMetrics(ctx, *p)
|
||||
|
||||
return p, nil
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Warnf("failed to update interface status for %s: %v", in.Identifier, err)
|
||||
}
|
||||
logrus.Tracef("updated peer status for %s", peer.Identifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,7 +245,7 @@ func (c *StatisticsCollector) pingWorker(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (c *StatisticsCollector) isPeerPingable(ctx context.Context, peer domain.Peer) bool {
|
||||
if c.cfg.Statistics.UsePingChecks == false {
|
||||
if !c.cfg.Statistics.UsePingChecks {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@@ -48,6 +48,7 @@ type Config struct {
|
||||
CollectInterfaceData bool `yaml:"collect_interface_data"`
|
||||
CollectPeerData bool `yaml:"collect_peer_data"`
|
||||
CollectAuditData bool `yaml:"collect_audit_data"`
|
||||
ListeningAddress string `yaml:"listening_address"`
|
||||
} `yaml:"statistics"`
|
||||
|
||||
Mail MailConfig `yaml:"mail"`
|
||||
@@ -117,10 +118,11 @@ func defaultConfig() *Config {
|
||||
cfg.Statistics.PingCheckWorkers = 10
|
||||
cfg.Statistics.PingUnprivileged = false
|
||||
cfg.Statistics.PingCheckInterval = 1 * time.Minute
|
||||
cfg.Statistics.DataCollectionInterval = 10 * time.Second
|
||||
cfg.Statistics.DataCollectionInterval = 1 * time.Minute
|
||||
cfg.Statistics.CollectInterfaceData = true
|
||||
cfg.Statistics.CollectPeerData = true
|
||||
cfg.Statistics.CollectAuditData = true
|
||||
cfg.Statistics.ListeningAddress = ":8787"
|
||||
|
||||
cfg.Mail = MailConfig{
|
||||
Host: "127.0.0.1",
|
||||
|
@@ -126,3 +126,10 @@ func TruncateString(s string, max int) string {
|
||||
}
|
||||
return s[:max]
|
||||
}
|
||||
|
||||
func BoolToFloat64(b bool) float64 {
|
||||
if b {
|
||||
return 1.0
|
||||
}
|
||||
return 0.0
|
||||
}
|
||||
|
Reference in New Issue
Block a user