package peer import ( "sync" "time" "github.com/netbirdio/netbird/client/internal/metrics" ) type MetricsStages struct { isReconnectionAttempt bool // Track if current attempt is a reconnection stageTimestamps metrics.ConnectionStageTimestamps mu sync.Mutex } // RecordSignalingReceived records when the first signal is received from the remote peer. // Used as the base for all subsequent stage durations to avoid inflating metrics when // the remote peer was offline. func (s *MetricsStages) RecordSignalingReceived() { s.mu.Lock() defer s.mu.Unlock() if s.stageTimestamps.SignalingReceived.IsZero() { s.stageTimestamps.SignalingReceived = time.Now() } } func (s *MetricsStages) RecordConnectionReady(when time.Time) { s.mu.Lock() defer s.mu.Unlock() if s.stageTimestamps.ConnectionReady.IsZero() { s.stageTimestamps.ConnectionReady = when } } func (s *MetricsStages) RecordWGHandshakeSuccess(handshakeTime time.Time) { s.mu.Lock() defer s.mu.Unlock() if !s.stageTimestamps.ConnectionReady.IsZero() && s.stageTimestamps.WgHandshakeSuccess.IsZero() { // WireGuard only reports handshake times with second precision, but ConnectionReady // is captured with microsecond precision. If handshake appears before ConnectionReady // due to truncation (e.g., handshake at 6.042s truncated to 6.000s), normalize to // ConnectionReady to avoid negative duration metrics. if handshakeTime.Before(s.stageTimestamps.ConnectionReady) { s.stageTimestamps.WgHandshakeSuccess = s.stageTimestamps.ConnectionReady } else { s.stageTimestamps.WgHandshakeSuccess = handshakeTime } } } // Disconnected sets the mode to reconnection. It is called only when both ICE and Relay have been disconnected at the same time. func (s *MetricsStages) Disconnected() { s.mu.Lock() defer s.mu.Unlock() // Reset all timestamps for reconnection s.stageTimestamps = metrics.ConnectionStageTimestamps{} s.isReconnectionAttempt = true } func (s *MetricsStages) IsReconnection() bool { s.mu.Lock() defer s.mu.Unlock() return s.isReconnectionAttempt } func (s *MetricsStages) GetTimestamps() metrics.ConnectionStageTimestamps { s.mu.Lock() defer s.mu.Unlock() return s.stageTimestamps }