mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-21 12:59:08 +02:00
[client] Add local Prometheus metrics endpoint (#6689)
This commit is contained in:
@@ -45,30 +45,13 @@ func (m *influxDBMetrics) RecordConnectionStages(
|
||||
isReconnection bool,
|
||||
timestamps ConnectionStageTimestamps,
|
||||
) {
|
||||
var signalingReceivedToConnection, connectionToWgHandshake, totalDuration float64
|
||||
|
||||
if !timestamps.SignalingReceived.IsZero() && !timestamps.ConnectionReady.IsZero() {
|
||||
signalingReceivedToConnection = timestamps.ConnectionReady.Sub(timestamps.SignalingReceived).Seconds()
|
||||
}
|
||||
|
||||
if !timestamps.ConnectionReady.IsZero() && !timestamps.WgHandshakeSuccess.IsZero() {
|
||||
connectionToWgHandshake = timestamps.WgHandshakeSuccess.Sub(timestamps.ConnectionReady).Seconds()
|
||||
}
|
||||
|
||||
if !timestamps.SignalingReceived.IsZero() && !timestamps.WgHandshakeSuccess.IsZero() {
|
||||
totalDuration = timestamps.WgHandshakeSuccess.Sub(timestamps.SignalingReceived).Seconds()
|
||||
}
|
||||
|
||||
attemptType := "initial"
|
||||
if isReconnection {
|
||||
attemptType = "reconnection"
|
||||
}
|
||||
signalingReceivedToConnection, connectionToWgHandshake, totalDuration := timestamps.Durations()
|
||||
|
||||
connTypeStr := connectionType.String()
|
||||
tags := fmt.Sprintf("deployment_type=%s,connection_type=%s,attempt_type=%s,version=%s,os=%s,arch=%s,peer_id=%s,connection_pair_id=%s",
|
||||
agentInfo.DeploymentType.String(),
|
||||
connTypeStr,
|
||||
attemptType,
|
||||
attemptType(isReconnection),
|
||||
agentInfo.Version,
|
||||
agentInfo.OS,
|
||||
agentInfo.Arch,
|
||||
@@ -94,7 +77,7 @@ func (m *influxDBMetrics) RecordConnectionStages(
|
||||
m.trimLocked()
|
||||
|
||||
log.Tracef("peer connection metrics [%s, %s, %s]: signalingReceived→connection: %.3fs, connection→wg_handshake: %.3fs, total: %.3fs",
|
||||
agentInfo.DeploymentType.String(), connTypeStr, attemptType, signalingReceivedToConnection, connectionToWgHandshake, totalDuration)
|
||||
agentInfo.DeploymentType.String(), connTypeStr, attemptType(isReconnection), signalingReceivedToConnection, connectionToWgHandshake, totalDuration)
|
||||
}
|
||||
|
||||
func (m *influxDBMetrics) RecordSyncDuration(_ context.Context, agentInfo AgentInfo, duration time.Duration) {
|
||||
|
||||
@@ -89,6 +89,21 @@ type ConnectionStageTimestamps struct {
|
||||
WgHandshakeSuccess time.Time
|
||||
}
|
||||
|
||||
// Durations returns the stage durations in seconds. A duration is zero when
|
||||
// either of its timestamps is missing.
|
||||
func (c ConnectionStageTimestamps) Durations() (signalingToConnection, connectionToWgHandshake, total float64) {
|
||||
if !c.SignalingReceived.IsZero() && !c.ConnectionReady.IsZero() {
|
||||
signalingToConnection = c.ConnectionReady.Sub(c.SignalingReceived).Seconds()
|
||||
}
|
||||
if !c.ConnectionReady.IsZero() && !c.WgHandshakeSuccess.IsZero() {
|
||||
connectionToWgHandshake = c.WgHandshakeSuccess.Sub(c.ConnectionReady).Seconds()
|
||||
}
|
||||
if !c.SignalingReceived.IsZero() && !c.WgHandshakeSuccess.IsZero() {
|
||||
total = c.WgHandshakeSuccess.Sub(c.SignalingReceived).Seconds()
|
||||
}
|
||||
return signalingToConnection, connectionToWgHandshake, total
|
||||
}
|
||||
|
||||
// String returns a human-readable representation of the connection stage timestamps
|
||||
func (c ConnectionStageTimestamps) String() string {
|
||||
return fmt.Sprintf("ConnectionStageTimestamps{SignalingReceived=%v, ConnectionReady=%v, WgHandshakeSuccess=%v}",
|
||||
@@ -279,3 +294,11 @@ func (c *ClientMetrics) stopPushLocked() {
|
||||
c.wg.Wait()
|
||||
c.push.Store(nil)
|
||||
}
|
||||
|
||||
// attemptType returns the metric label for an initial vs reconnection attempt.
|
||||
func attemptType(isReconnection bool) string {
|
||||
if isReconnection {
|
||||
return "reconnection"
|
||||
}
|
||||
return "initial"
|
||||
}
|
||||
|
||||
@@ -2,10 +2,24 @@
|
||||
|
||||
package metrics
|
||||
|
||||
import "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
// NewClientMetrics creates a new ClientMetrics instance
|
||||
func NewClientMetrics(agentInfo AgentInfo) *ClientMetrics {
|
||||
return &ClientMetrics{
|
||||
impl: newInfluxDBMetrics(),
|
||||
impl: newPrometheusMetrics(newInfluxDBMetrics()),
|
||||
agentInfo: agentInfo,
|
||||
}
|
||||
}
|
||||
|
||||
// PrometheusGatherer returns the registry with the mirrored Prometheus
|
||||
// metrics, or nil when unavailable.
|
||||
func (c *ClientMetrics) PrometheusGatherer() prometheus.Gatherer {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
if pm, ok := c.impl.(*prometheusMetrics); ok {
|
||||
return pm.Gatherer()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
//go:build !js
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// prometheusMetrics mirrors recorded client metrics into a Prometheus
|
||||
// registry for the local /metrics endpoint, then delegates to the wrapped
|
||||
// implementation. Export and Reset pass through untouched: Prometheus
|
||||
// metrics are cumulative and pull-based.
|
||||
type prometheusMetrics struct {
|
||||
next metricsImplementation
|
||||
registry *prometheus.Registry
|
||||
|
||||
connectionStages *prometheus.HistogramVec
|
||||
syncDuration prometheus.Histogram
|
||||
syncPhaseDuration *prometheus.HistogramVec
|
||||
loginDuration *prometheus.HistogramVec
|
||||
}
|
||||
|
||||
func newPrometheusMetrics(next metricsImplementation) *prometheusMetrics {
|
||||
connectionBuckets := []float64{.05, .1, .25, .5, 1, 2.5, 5, 10, 30, 60}
|
||||
|
||||
m := &prometheusMetrics{
|
||||
next: next,
|
||||
registry: prometheus.NewRegistry(),
|
||||
connectionStages: prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "netbird_peer_connection_stage_duration_seconds",
|
||||
Help: "Duration of peer connection establishment stages.",
|
||||
Buckets: connectionBuckets,
|
||||
}, []string{"stage", "connection_type", "attempt_type"}),
|
||||
syncDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||
Name: "netbird_sync_duration_seconds",
|
||||
Help: "Duration of management sync message processing.",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
}),
|
||||
syncPhaseDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "netbird_sync_phase_duration_seconds",
|
||||
Help: "Duration of individual sync processing phases.",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
}, []string{"phase"}),
|
||||
loginDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "netbird_login_duration_seconds",
|
||||
Help: "Duration of logins to the management service.",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
}, []string{"success"}),
|
||||
}
|
||||
|
||||
m.registry.MustRegister(m.connectionStages, m.syncDuration, m.syncPhaseDuration, m.loginDuration)
|
||||
return m
|
||||
}
|
||||
|
||||
// Gatherer returns the registry holding the mirrored metrics.
|
||||
func (m *prometheusMetrics) Gatherer() prometheus.Gatherer {
|
||||
return m.registry
|
||||
}
|
||||
|
||||
// RecordConnectionStages implements metricsImplementation.
|
||||
func (m *prometheusMetrics) RecordConnectionStages(
|
||||
ctx context.Context,
|
||||
agentInfo AgentInfo,
|
||||
connectionPairID string,
|
||||
connectionType ConnectionType,
|
||||
isReconnection bool,
|
||||
timestamps ConnectionStageTimestamps,
|
||||
) {
|
||||
attempt := attemptType(isReconnection)
|
||||
connType := connectionType.String()
|
||||
|
||||
signalingToConnection, connectionToWgHandshake, total := timestamps.Durations()
|
||||
if signalingToConnection > 0 {
|
||||
m.connectionStages.WithLabelValues("signaling_to_connection", connType, attempt).Observe(signalingToConnection)
|
||||
}
|
||||
if connectionToWgHandshake > 0 {
|
||||
m.connectionStages.WithLabelValues("connection_to_wg_handshake", connType, attempt).Observe(connectionToWgHandshake)
|
||||
}
|
||||
if total > 0 {
|
||||
m.connectionStages.WithLabelValues("total", connType, attempt).Observe(total)
|
||||
}
|
||||
|
||||
m.next.RecordConnectionStages(ctx, agentInfo, connectionPairID, connectionType, isReconnection, timestamps)
|
||||
}
|
||||
|
||||
// RecordSyncDuration implements metricsImplementation.
|
||||
func (m *prometheusMetrics) RecordSyncDuration(ctx context.Context, agentInfo AgentInfo, duration time.Duration) {
|
||||
m.syncDuration.Observe(duration.Seconds())
|
||||
m.next.RecordSyncDuration(ctx, agentInfo, duration)
|
||||
}
|
||||
|
||||
// RecordSyncPhase implements metricsImplementation.
|
||||
func (m *prometheusMetrics) RecordSyncPhase(ctx context.Context, agentInfo AgentInfo, phase string, duration time.Duration) {
|
||||
m.syncPhaseDuration.WithLabelValues(phase).Observe(duration.Seconds())
|
||||
m.next.RecordSyncPhase(ctx, agentInfo, phase, duration)
|
||||
}
|
||||
|
||||
// RecordLoginDuration implements metricsImplementation.
|
||||
func (m *prometheusMetrics) RecordLoginDuration(ctx context.Context, agentInfo AgentInfo, duration time.Duration, success bool) {
|
||||
m.loginDuration.WithLabelValues(strconv.FormatBool(success)).Observe(duration.Seconds())
|
||||
m.next.RecordLoginDuration(ctx, agentInfo, duration, success)
|
||||
}
|
||||
|
||||
// Export implements metricsImplementation by delegating to the wrapped
|
||||
// implementation; Prometheus metrics are pulled via the registry instead.
|
||||
func (m *prometheusMetrics) Export(w io.Writer) error {
|
||||
return m.next.Export(w)
|
||||
}
|
||||
|
||||
// Reset implements metricsImplementation by delegating to the wrapped
|
||||
// implementation; Prometheus metrics must not be cleared on push.
|
||||
func (m *prometheusMetrics) Reset() {
|
||||
m.next.Reset()
|
||||
}
|
||||
Reference in New Issue
Block a user