From bec58b85b1c51575a575b8dcc35244e58d24b6b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Papp?= Date: Wed, 11 Feb 2026 15:18:45 +0100 Subject: [PATCH] Add sync duration tracking to client metrics Introduce `RecordSyncDuration` for measuring sync message processing time. Update all metrics implementations (VictoriaMetrics, no-op) to support the new method. Refactor `ClientMetrics` to use `AgentInfo` for static agent data. --- client/internal/engine.go | 9 +++++++-- client/internal/metrics/metrics.go | 18 ++++++++++++++++-- client/internal/metrics/noop.go | 5 +++++ client/internal/metrics/victoria.go | 29 +++++++++++++++++++---------- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/client/internal/engine.go b/client/internal/engine.go index 0fae4e616..968f635ec 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -279,7 +279,10 @@ func NewEngine( connSemaphore: semaphoregroup.NewSemaphoreGroup(connInitLimit), probeStunTurn: relay.NewStunTurnProbe(relay.DefaultCacheTTL), jobExecutor: jobexec.NewExecutor(), - clientMetrics: metrics.NewClientMetrics(deploymentType, version.NetbirdVersion(), true), + clientMetrics: metrics.NewClientMetrics(metrics.AgentInfo{ + DeploymentType: deploymentType, + Version: version.NetbirdVersion(), + }, true), } log.Infof("I am: %s", config.WgPrivateKey.PublicKey().String()) @@ -842,7 +845,9 @@ func (e *Engine) handleAutoUpdateVersion(autoUpdateSettings *mgmProto.AutoUpdate func (e *Engine) handleSync(update *mgmProto.SyncResponse) error { started := time.Now() defer func() { - log.Infof("sync finished in %s", time.Since(started)) + duration := time.Since(started) + log.Infof("sync finished in %s", duration) + e.clientMetrics.RecordSyncDuration(e.ctx, duration) }() e.syncMsgMux.Lock() defer e.syncMsgMux.Unlock() diff --git a/client/internal/metrics/metrics.go b/client/internal/metrics/metrics.go index 7699f5b02..a8fd456a1 100644 --- a/client/internal/metrics/metrics.go +++ b/client/internal/metrics/metrics.go @@ -6,6 +6,12 @@ import ( "time" ) +// AgentInfo holds static information about the agent +type AgentInfo struct { + DeploymentType DeploymentType + Version string +} + // metricsImplementation defines the internal interface for metrics implementations type metricsImplementation interface { // RecordConnectionStages records connection stage metrics from timestamps @@ -16,6 +22,9 @@ type metricsImplementation interface { timestamps ConnectionStageTimestamps, ) + // RecordSyncDuration records how long it took to process a sync message + RecordSyncDuration(ctx context.Context, duration time.Duration) + // Export exports metrics in Prometheus format Export(w io.Writer) error } @@ -36,12 +45,12 @@ type ConnectionStageTimestamps struct { // NewClientMetrics creates a new ClientMetrics instance // If enabled is true, uses an OpenTelemetry implementation // If enabled is false, uses a no-op implementation -func NewClientMetrics(deploymentType DeploymentType, version string, enabled bool) *ClientMetrics { +func NewClientMetrics(agentInfo AgentInfo, enabled bool) *ClientMetrics { var impl metricsImplementation if !enabled { impl = &noopMetrics{} } else { - impl = newVictoriaMetrics(deploymentType, version) + impl = newVictoriaMetrics(agentInfo) } return &ClientMetrics{impl: impl} } @@ -56,6 +65,11 @@ func (c *ClientMetrics) RecordConnectionStages( c.impl.RecordConnectionStages(ctx, connectionType, isReconnection, timestamps) } +// RecordSyncDuration records the duration of sync message processing +func (c *ClientMetrics) RecordSyncDuration(ctx context.Context, duration time.Duration) { + c.impl.RecordSyncDuration(ctx, duration) +} + // Export exports metrics to the writer func (c *ClientMetrics) Export(w io.Writer) error { return c.impl.Export(w) diff --git a/client/internal/metrics/noop.go b/client/internal/metrics/noop.go index bf8aa4320..fb1f6126e 100644 --- a/client/internal/metrics/noop.go +++ b/client/internal/metrics/noop.go @@ -3,6 +3,7 @@ package metrics import ( "context" "io" + "time" ) // noopMetrics is a no-op implementation of metricsImplementation @@ -17,6 +18,10 @@ func (s *noopMetrics) RecordConnectionStages( // No-op } +func (s *noopMetrics) RecordSyncDuration(_ context.Context, _ time.Duration) { + // No-op +} + func (s *noopMetrics) Export(_ io.Writer) error { return nil } diff --git a/client/internal/metrics/victoria.go b/client/internal/metrics/victoria.go index 10ee713da..32706a1f9 100644 --- a/client/internal/metrics/victoria.go +++ b/client/internal/metrics/victoria.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "time" "github.com/VictoriaMetrics/metrics" log "github.com/sirupsen/logrus" @@ -11,19 +12,17 @@ import ( // victoriaMetrics is the VictoriaMetrics implementation of ClientMetrics type victoriaMetrics struct { - // Static attributes applied to all metrics - deploymentType DeploymentType - version string + // Static agent information applied to all metrics + agentInfo AgentInfo // Metrics set for managing all metrics set *metrics.Set } -func newVictoriaMetrics(deploymentType DeploymentType, version string) metricsImplementation { +func newVictoriaMetrics(agentInfo AgentInfo) metricsImplementation { return &victoriaMetrics{ - deploymentType: deploymentType, - version: version, - set: metrics.NewSet(), + agentInfo: agentInfo, + set: metrics.NewSet(), } } @@ -87,7 +86,7 @@ func (m *victoriaMetrics) RecordConnectionStages( ).Update(totalDuration) log.Tracef("peer connection metrics [%s, %s, %s]: creation→semaphore: %.3fs, semaphore→signaling: %.3fs, signaling→connection: %.3fs, connection→handshake: %.3fs, total: %.3fs", - m.deploymentType.String(), connTypeStr, attemptType, + m.agentInfo.DeploymentType.String(), connTypeStr, attemptType, creationToSemaphore, semaphoreToSignaling, signalingToConnection, connectionToHandshake, totalDuration) } @@ -96,13 +95,23 @@ func (m *victoriaMetrics) RecordConnectionStages( func (m *victoriaMetrics) getMetricName(baseName, connectionType, attemptType string) string { return fmt.Sprintf(`%s{deployment_type=%q,connection_type=%q,attempt_type=%q,version=%q}`, baseName, - m.deploymentType.String(), + m.agentInfo.DeploymentType.String(), connectionType, attemptType, - m.version, + m.agentInfo.Version, ) } +// RecordSyncDuration records the duration of sync message processing +func (m *victoriaMetrics) RecordSyncDuration(ctx context.Context, duration time.Duration) { + metricName := fmt.Sprintf(`netbird_sync_duration_seconds{deployment_type=%q,version=%q}`, + m.agentInfo.DeploymentType.String(), + m.agentInfo.Version, + ) + + m.set.GetOrCreateHistogram(metricName).Update(duration.Seconds()) +} + // Export writes metrics in Prometheus text format func (m *victoriaMetrics) Export(w io.Writer) error { if m.set == nil {