From adcd8e3ec51ab56f1f2309f18d47a7682498f5ae Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Wed, 23 Sep 2026 08:51:31 +0200 Subject: [PATCH] Report FBU metrics as untracked for service-mode proxied sessions instead of as zero --- client/internal/engine_vnc.go | 1 + client/internal/metrics/influxdb.go | 28 +++++++++++++++++----------- client/internal/metrics/metrics.go | 3 +++ client/vnc/server/metrics_conn.go | 22 ++++++++++++++++++++++ client/vnc/server/server_darwin.go | 2 +- client/vnc/server/server_windows.go | 2 +- 6 files changed, 45 insertions(+), 13 deletions(-) diff --git a/client/internal/engine_vnc.go b/client/internal/engine_vnc.go index 6ea099866..1d3ead524 100644 --- a/client/internal/engine_vnc.go +++ b/client/internal/engine_vnc.go @@ -153,6 +153,7 @@ func (e *Engine) startVNCServer(authConfig *sshauth.Config) error { MaxFBURects: t.MaxFBURects, MaxWriteBytes: t.MaxWriteBytes, WriteNanos: t.WriteNanos, + FBUsTracked: t.FBUsTracked, }) } } diff --git a/client/internal/metrics/influxdb.go b/client/internal/metrics/influxdb.go index 3e3c6a82b..cbb5f6152 100644 --- a/client/internal/metrics/influxdb.go +++ b/client/internal/metrics/influxdb.go @@ -115,20 +115,26 @@ func (m *influxDBMetrics) RecordVNCSessionTick(_ context.Context, agentInfo Agen m.mu.Lock() defer m.mu.Unlock() + fields := map[string]float64{ + "period_seconds": tick.Period.Seconds(), + "bytes_out": float64(tick.BytesOut), + "writes": float64(tick.Writes), + "max_write_bytes": float64(tick.MaxWriteBytes), + "write_time_seconds": float64(tick.WriteNanos) / 1e9, + } + // Left out rather than written as zero when they were not observed, so a + // service-mode session does not read as one that sent no updates. + if tick.FBUsTracked { + fields["fbus"] = float64(tick.FBUs) + fields["max_fbu_bytes"] = float64(tick.MaxFBUBytes) + fields["max_fbu_rects"] = float64(tick.MaxFBURects) + } + m.samples = append(m.samples, influxSample{ measurement: "netbird_vnc_traffic", tags: tags, - fields: map[string]float64{ - "period_seconds": tick.Period.Seconds(), - "bytes_out": float64(tick.BytesOut), - "writes": float64(tick.Writes), - "fbus": float64(tick.FBUs), - "max_fbu_bytes": float64(tick.MaxFBUBytes), - "max_fbu_rects": float64(tick.MaxFBURects), - "max_write_bytes": float64(tick.MaxWriteBytes), - "write_time_seconds": float64(tick.WriteNanos) / 1e9, - }, - timestamp: time.Now(), + fields: fields, + timestamp: time.Now(), }) m.trimLocked() } diff --git a/client/internal/metrics/metrics.go b/client/internal/metrics/metrics.go index 5deb48815..4b2b12a53 100644 --- a/client/internal/metrics/metrics.go +++ b/client/internal/metrics/metrics.go @@ -100,6 +100,9 @@ type VNCSessionTick struct { MaxFBURects uint64 MaxWriteBytes uint64 WriteNanos uint64 + // FBUsTracked is false when the FBU fields could not be observed (a + // service-mode proxy). They are then unknown rather than zero. + FBUsTracked bool } // ConnectionStageTimestamps holds timestamps for each connection stage diff --git a/client/vnc/server/metrics_conn.go b/client/vnc/server/metrics_conn.go index 6b5b414ca..b4b747a0e 100644 --- a/client/vnc/server/metrics_conn.go +++ b/client/vnc/server/metrics_conn.go @@ -23,6 +23,11 @@ type SessionTick struct { MaxFBURects uint64 MaxWriteBytes uint64 WriteNanos uint64 + // FBUsTracked is false when the connection carried framebuffer updates it + // could not see the boundaries of, which is the case for a service-mode + // proxy relaying bytes to a per-session agent. FBUs, MaxFBUBytes and + // MaxFBURects then mean "unknown", not "none". + FBUsTracked bool } // sessionTickInterval is how often metricsConn emits a SessionTick. One @@ -40,6 +45,9 @@ type metricsConn struct { net.Conn recorder func(SessionTick) + // framed is true when the session writing through this connection marks + // its FramebufferUpdate boundaries with beginFBU/endFBU. + framed bool bytesOut atomic.Uint64 writes atomic.Uint64 @@ -72,9 +80,22 @@ type metricsConn struct { } func newMetricsConn(c net.Conn, recorder func(SessionTick)) net.Conn { + return newMetricsConnFramed(c, recorder, true) +} + +// newProxyMetricsConn wraps a connection whose bytes are relayed to a session +// running elsewhere, the service-mode agent. Nothing on this side sees where +// one FramebufferUpdate ends and the next begins, so its ticks carry byte and +// write counts only and report the FBU fields as untracked. +func newProxyMetricsConn(c net.Conn, recorder func(SessionTick)) net.Conn { + return newMetricsConnFramed(c, recorder, false) +} + +func newMetricsConnFramed(c net.Conn, recorder func(SessionTick), framed bool) net.Conn { m := &metricsConn{ Conn: c, recorder: recorder, + framed: framed, tickStart: time.Now(), done: make(chan struct{}), } @@ -137,6 +158,7 @@ func (m *metricsConn) flushTick(final bool) { MaxFBURects: maxRects, MaxWriteBytes: maxPkt, WriteNanos: dns, + FBUsTracked: m.framed, }) } diff --git a/client/vnc/server/server_darwin.go b/client/vnc/server/server_darwin.go index b8184e15b..e96350430 100644 --- a/client/vnc/server/server_darwin.go +++ b/client/vnc/server/server_darwin.go @@ -57,7 +57,7 @@ func (s *Server) serviceAcceptLoop(ln net.Listener) { continue } enableTCPKeepAlive(conn, s.log) - metered := newMetricsConn(conn, s.sessionRecorder) + metered := newProxyMetricsConn(conn, s.sessionRecorder) s.retrackConn(conn, metered) if !s.beginHandler() { s.releaseConnSlot() diff --git a/client/vnc/server/server_windows.go b/client/vnc/server/server_windows.go index 89ba26925..101bf7b31 100644 --- a/client/vnc/server/server_windows.go +++ b/client/vnc/server/server_windows.go @@ -416,7 +416,7 @@ func (s *Server) serviceAcceptLoop(ln net.Listener) { continue } enableTCPKeepAlive(conn, s.log) - metered := newMetricsConn(conn, s.sessionRecorder) + metered := newProxyMetricsConn(conn, s.sessionRecorder) s.retrackConn(conn, metered) if !s.beginHandler() { s.releaseConnSlot()