net/udprelay: add tailscaled_peer_relay_endpoints gauge (#18265)

New gauge reflects endpoints state via labels:
- open, when both peers are connected and ready to talk, and
- connecting. when at least one peer hasn't connected yet.

Corresponding client metrics are logged as
- udprelay_endpoints_connecting
- udprelay_endpoints_open

Updates tailscale/corp#30820

Change-Id: Idb1baa90a38c97847e14f9b2390093262ad0ea23

Signed-off-by: Alex Valiushko <alexvaliushko@tailscale.com>
This commit is contained in:
Alex Valiushko
2026-01-21 21:55:37 -08:00
committed by GitHub
parent 6dc0bd834c
commit 4b7585df77
4 changed files with 258 additions and 24 deletions
+56 -1
View File
@@ -4,6 +4,7 @@
package udprelay
import (
"fmt"
"slices"
"testing"
@@ -11,7 +12,7 @@ import (
"tailscale.com/util/usermetric"
)
func TestMetrics(t *testing.T) {
func TestMetricsLifecycle(t *testing.T) {
c := qt.New(t)
deregisterMetrics()
r := &usermetric.Registry{}
@@ -22,6 +23,7 @@ func TestMetrics(t *testing.T) {
want := []string{
"tailscaled_peer_relay_forwarded_packets_total",
"tailscaled_peer_relay_forwarded_bytes_total",
"tailscaled_peer_relay_endpoints",
}
slices.Sort(have)
slices.Sort(want)
@@ -51,4 +53,57 @@ func TestMetrics(t *testing.T) {
c.Assert(m.forwarded66Packets.Value(), qt.Equals, int64(4))
c.Assert(cMetricForwarded66Bytes.Value(), qt.Equals, int64(4))
c.Assert(cMetricForwarded66Packets.Value(), qt.Equals, int64(4))
// Validate client metrics deregistration.
m.updateEndpoint(endpointClosed, endpointOpen)
deregisterMetrics()
c.Check(cMetricForwarded44Bytes.Value(), qt.Equals, int64(0))
c.Check(cMetricForwarded44Packets.Value(), qt.Equals, int64(0))
c.Check(cMetricForwarded46Bytes.Value(), qt.Equals, int64(0))
c.Check(cMetricForwarded46Packets.Value(), qt.Equals, int64(0))
c.Check(cMetricForwarded64Bytes.Value(), qt.Equals, int64(0))
c.Check(cMetricForwarded64Packets.Value(), qt.Equals, int64(0))
c.Check(cMetricForwarded66Bytes.Value(), qt.Equals, int64(0))
c.Check(cMetricForwarded66Packets.Value(), qt.Equals, int64(0))
for k := range cMetricEndpoints {
c.Check(cMetricEndpoints[k].Value(), qt.Equals, int64(0))
}
}
func TestMetricsEndpointTransitions(t *testing.T) {
c := qt.New(t)
var states = []endpointState{
endpointClosed,
endpointConnecting,
endpointOpen,
}
for _, a := range states {
for _, b := range states {
t.Run(fmt.Sprintf("%s-%s", a, b), func(t *testing.T) {
deregisterMetrics()
r := &usermetric.Registry{}
m := registerMetrics(r)
m.updateEndpoint(a, b)
var wantA, wantB int64
switch {
case a == b:
wantA, wantB = 0, 0
case a == endpointClosed:
wantA, wantB = 0, 1
case b == endpointClosed:
wantA, wantB = -1, 0
default:
wantA, wantB = -1, 1
}
if a != endpointClosed {
c.Check(m.endpoints[a].Value(), qt.Equals, wantA)
c.Check(cMetricEndpoints[a].Value(), qt.Equals, wantA)
}
if b != endpointClosed {
c.Check(m.endpoints[b].Value(), qt.Equals, wantB)
c.Check(cMetricEndpoints[b].Value(), qt.Equals, wantB)
}
})
}
}
}