diff --git a/control/controlclient/auto.go b/control/controlclient/auto.go
index bab7c7be3..93598d15a 100644
--- a/control/controlclient/auto.go
+++ b/control/controlclient/auto.go
@@ -119,10 +119,10 @@ func NewNoStart(opts Options) (_ *Auto, err error) {
statusFunc: opts.Status,
}
c.authCtx, c.authCancel = context.WithCancel(context.Background())
- c.authCtx = sockstats.WithSockStats(c.authCtx, "controlclient.Auto:auth")
+ c.authCtx = sockstats.WithSockStats(c.authCtx, sockstats.LabelControlClientAuto)
c.mapCtx, c.mapCancel = context.WithCancel(context.Background())
- c.mapCtx = sockstats.WithSockStats(c.mapCtx, "controlclient.Auto:map")
+ c.mapCtx = sockstats.WithSockStats(c.mapCtx, sockstats.LabelControlClientAuto)
c.unregisterHealthWatch = health.RegisterWatcher(direct.ReportHealthChange)
return c, nil
@@ -211,7 +211,7 @@ func (c *Auto) cancelAuth() {
}
if !c.closed {
c.authCtx, c.authCancel = context.WithCancel(context.Background())
- c.authCtx = sockstats.WithSockStats(c.authCtx, "controlclient.Auto:auth")
+ c.authCtx = sockstats.WithSockStats(c.authCtx, sockstats.LabelControlClientAuto)
}
c.mu.Unlock()
}
@@ -222,7 +222,7 @@ func (c *Auto) cancelMapLocked() {
}
if !c.closed {
c.mapCtx, c.mapCancel = context.WithCancel(context.Background())
- c.mapCtx = sockstats.WithSockStats(c.mapCtx, "controlclient.Auto:map")
+ c.mapCtx = sockstats.WithSockStats(c.mapCtx, sockstats.LabelControlClientAuto)
}
}
diff --git a/control/controlhttp/client.go b/control/controlhttp/client.go
index bdb02013b..897da19fc 100644
--- a/control/controlhttp/client.go
+++ b/control/controlhttp/client.go
@@ -273,7 +273,7 @@ func (a *Dialer) dialHost(ctx context.Context, addr netip.Addr) (*ClientConn, er
ctx, cancel := context.WithCancel(ctx)
defer cancel()
- ctx = sockstats.WithSockStats(ctx, "controlclient.Dialer")
+ ctx = sockstats.WithSockStats(ctx, sockstats.LabelControlClientDialer)
// u80 and u443 are the URLs we'll try to hit over HTTP or HTTPS,
// respectively, in order to do the HTTP upgrade to a net.Conn over which
diff --git a/derp/derphttp/derphttp_client.go b/derp/derphttp/derphttp_client.go
index 160ae8bd3..c8e5ea2b4 100644
--- a/derp/derphttp/derphttp_client.go
+++ b/derp/derphttp/derphttp_client.go
@@ -616,7 +616,7 @@ type res struct {
ctx, cancel := context.WithTimeout(ctx, dialNodeTimeout)
defer cancel()
- ctx = sockstats.WithSockStats(ctx, "derphttp.Client")
+ ctx = sockstats.WithSockStats(ctx, sockstats.LabelDERPHTTPClient)
nwait := 0
startDial := func(dstPrimary, proto string) {
diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go
index 16c2f496e..e6152ba01 100644
--- a/ipn/ipnlocal/peerapi.go
+++ b/ipn/ipnlocal/peerapi.go
@@ -879,11 +879,13 @@ func (h *peerAPIHandler) handleServeSockStats(w http.ResponseWriter, r *http.Req
fmt.Fprintln(w, "")
fmt.Fprintln(w, "
")
- labels := make([]string, 0, len(stats.Stats))
+ labels := make([]sockstats.Label, 0, len(stats.Stats))
for label := range stats.Stats {
labels = append(labels, label)
}
- sort.Strings(labels)
+ slices.SortFunc(labels, func(a, b sockstats.Label) bool {
+ return a.String() < b.String()
+ })
txTotal := int64(0)
rxTotal := int64(0)
@@ -893,7 +895,7 @@ func (h *peerAPIHandler) handleServeSockStats(w http.ResponseWriter, r *http.Req
for _, label := range labels {
stat := stats.Stats[label]
fmt.Fprintln(w, "")
- fmt.Fprintf(w, "%s | ", html.EscapeString(label))
+ fmt.Fprintf(w, "%s | ", html.EscapeString(label.String()))
fmt.Fprintf(w, "%d | ", stat.TxBytes)
fmt.Fprintf(w, "%d | ", stat.RxBytes)
diff --git a/logtail/logtail.go b/logtail/logtail.go
index c3a4242f4..1e2755d46 100644
--- a/logtail/logtail.go
+++ b/logtail/logtail.go
@@ -428,7 +428,7 @@ func (l *Logger) awaitInternetUp(ctx context.Context) {
// origlen of -1 indicates that the body is not compressed.
func (l *Logger) upload(ctx context.Context, body []byte, origlen int) (uploaded bool, err error) {
const maxUploadTime = 45 * time.Second
- ctx = sockstats.WithSockStats(ctx, "logtail.Logger")
+ ctx = sockstats.WithSockStats(ctx, sockstats.LabelLogtailLogger)
ctx, cancel := context.WithTimeout(ctx, maxUploadTime)
defer cancel()
diff --git a/net/dns/resolver/forwarder.go b/net/dns/resolver/forwarder.go
index 7c0ff42ec..a52108c0c 100644
--- a/net/dns/resolver/forwarder.go
+++ b/net/dns/resolver/forwarder.go
@@ -407,7 +407,7 @@ func (f *forwarder) getKnownDoHClientForProvider(urlBase string) (c *http.Client
const dohType = "application/dns-message"
func (f *forwarder) sendDoH(ctx context.Context, urlBase string, c *http.Client, packet []byte) ([]byte, error) {
- ctx = sockstats.WithSockStats(ctx, "dns.forwarder:doh")
+ ctx = sockstats.WithSockStats(ctx, sockstats.LabelDNSForwarderDoH)
metricDNSFwdDoH.Add(1)
req, err := http.NewRequestWithContext(ctx, "POST", urlBase, bytes.NewReader(packet))
if err != nil {
@@ -487,7 +487,7 @@ func (f *forwarder) sendUDP(ctx context.Context, fq *forwardQuery, rr resolverAn
return nil, fmt.Errorf("unrecognized resolver type %q", rr.name.Addr)
}
metricDNSFwdUDP.Add(1)
- ctx = sockstats.WithSockStats(ctx, "dns.forwarder:udp")
+ ctx = sockstats.WithSockStats(ctx, sockstats.LabelDNSForwarderUDP)
ln, err := f.packetListener(ipp.Addr())
if err != nil {
diff --git a/net/netcheck/netcheck.go b/net/netcheck/netcheck.go
index 2b8f76133..1897fa372 100644
--- a/net/netcheck/netcheck.go
+++ b/net/netcheck/netcheck.go
@@ -784,7 +784,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (_ *Report,
ctx, cancel := context.WithTimeout(ctx, overallProbeTimeout)
defer cancel()
- ctx = sockstats.WithSockStats(ctx, "netcheck.Client")
+ ctx = sockstats.WithSockStats(ctx, sockstats.LabelNetcheckClient)
if dm == nil {
return nil, errors.New("netcheck: GetReport: DERP map is nil")
diff --git a/net/portmapper/portmapper.go b/net/portmapper/portmapper.go
index 8c1f55b6e..187868356 100644
--- a/net/portmapper/portmapper.go
+++ b/net/portmapper/portmapper.go
@@ -249,7 +249,7 @@ func (c *Client) upnpPort() uint16 {
}
func (c *Client) listenPacket(ctx context.Context, network, addr string) (nettype.PacketConn, error) {
- ctx = sockstats.WithSockStats(ctx, "portmapper.Client")
+ ctx = sockstats.WithSockStats(ctx, sockstats.LabelPortmapperClient)
// When running under testing conditions, we bind the IGD server
// to localhost, and may be running in an environment where our
diff --git a/net/sockstats/label_string.go b/net/sockstats/label_string.go
new file mode 100644
index 000000000..83baaf785
--- /dev/null
+++ b/net/sockstats/label_string.go
@@ -0,0 +1,32 @@
+// Code generated by "stringer -type Label -trimprefix Label"; DO NOT EDIT.
+
+package sockstats
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[LabelControlClientAuto-0]
+ _ = x[LabelControlClientDialer-1]
+ _ = x[LabelDERPHTTPClient-2]
+ _ = x[LabelLogtailLogger-3]
+ _ = x[LabelDNSForwarderDoH-4]
+ _ = x[LabelDNSForwarderUDP-5]
+ _ = x[LabelNetcheckClient-6]
+ _ = x[LabelPortmapperClient-7]
+ _ = x[LabelMagicsockConnUDP4-8]
+ _ = x[LabelMagicsockConnUDP6-9]
+}
+
+const _Label_name = "ControlClientAutoControlClientDialerDERPHTTPClientLogtailLoggerDNSForwarderDoHDNSForwarderUDPNetcheckClientPortmapperClientMagicsockConnUDP4MagicsockConnUDP6"
+
+var _Label_index = [...]uint8{0, 17, 36, 50, 63, 78, 93, 107, 123, 140, 157}
+
+func (i Label) String() string {
+ if i >= Label(len(_Label_index)-1) {
+ return "Label(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _Label_name[_Label_index[i]:_Label_index[i+1]]
+}
diff --git a/net/sockstats/sockstats.go b/net/sockstats/sockstats.go
index e8e1ff735..f92da4b04 100644
--- a/net/sockstats/sockstats.go
+++ b/net/sockstats/sockstats.go
@@ -15,10 +15,32 @@
)
type SockStats struct {
- Stats map[string]SockStat
+ Stats map[Label]SockStat
Interfaces []string
}
+// Label is an identifier for a socket that stats are collected for. A finite
+// set of values that may be used to label a socket to encourage grouping and
+// to make storage more efficient.
+type Label uint8
+
+//go:generate go run golang.org/x/tools/cmd/stringer -type Label -trimprefix Label
+
+// Labels are named after the package and function/struct that uses the socket.
+// Values may be persisted and thus existing entries should not be re-numbered.
+const (
+ LabelControlClientAuto Label = 0 // control/controlclient/auto.go
+ LabelControlClientDialer Label = 1 // control/controlhttp/client.go
+ LabelDERPHTTPClient Label = 2 // derp/derphttp/derphttp_client.go
+ LabelLogtailLogger Label = 3 // logtail/logtail.go
+ LabelDNSForwarderDoH Label = 4 // net/dns/resolver/forwarder.go
+ LabelDNSForwarderUDP Label = 5 // net/dns/resolver/forwarder.go
+ LabelNetcheckClient Label = 6 // net/netcheck/netcheck.go
+ LabelPortmapperClient Label = 7 // net/portmapper/portmapper.go
+ LabelMagicsockConnUDP4 Label = 8 // wgengine/magicsock/magicsock.go
+ LabelMagicsockConnUDP6 Label = 9 // wgengine/magicsock/magicsock.go
+)
+
type SockStat struct {
TxBytes int64
RxBytes int64
@@ -26,7 +48,7 @@ type SockStat struct {
RxBytesByInterface map[string]int64
}
-func WithSockStats(ctx context.Context, label string) context.Context {
+func WithSockStats(ctx context.Context, label Label) context.Context {
return withSockStats(ctx, label)
}
diff --git a/net/sockstats/sockstats_noop.go b/net/sockstats/sockstats_noop.go
index 518c73443..b6d80e221 100644
--- a/net/sockstats/sockstats_noop.go
+++ b/net/sockstats/sockstats_noop.go
@@ -9,7 +9,7 @@
"context"
)
-func withSockStats(ctx context.Context, label string) context.Context {
+func withSockStats(ctx context.Context, label Label) context.Context {
return ctx
}
diff --git a/net/sockstats/sockstats_tsgo.go b/net/sockstats/sockstats_tsgo.go
index ab28c78a6..90c038cbb 100644
--- a/net/sockstats/sockstats_tsgo.go
+++ b/net/sockstats/sockstats_tsgo.go
@@ -24,7 +24,7 @@ type sockStatCounters struct {
// mu protects fields in this group. It should not be held in the per-read/
// write callbacks.
mu sync.Mutex
- countersByLabel map[string]*sockStatCounters
+ countersByLabel map[Label]*sockStatCounters
knownInterfaces map[int]string // interface index -> name
usedInterfaces map[int]int // set of interface indexes
@@ -32,12 +32,12 @@ type sockStatCounters struct {
// write callbacks.
currentInterface atomic.Uint32
}{
- countersByLabel: make(map[string]*sockStatCounters),
+ countersByLabel: make(map[Label]*sockStatCounters),
knownInterfaces: make(map[int]string),
usedInterfaces: make(map[int]int),
}
-func withSockStats(ctx context.Context, label string) context.Context {
+func withSockStats(ctx context.Context, label Label) context.Context {
sockStats.mu.Lock()
defer sockStats.mu.Unlock()
counters, ok := sockStats.countersByLabel[label]
@@ -85,7 +85,7 @@ func get() *SockStats {
defer sockStats.mu.Unlock()
r := &SockStats{
- Stats: make(map[string]SockStat),
+ Stats: make(map[Label]SockStat),
Interfaces: make([]string, 0, len(sockStats.usedInterfaces)),
}
for iface := range sockStats.usedInterfaces {
diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go
index 71914645d..3e78e20ef 100644
--- a/wgengine/magicsock/magicsock.go
+++ b/wgengine/magicsock/magicsock.go
@@ -3050,7 +3050,12 @@ func (c *Conn) ReSTUN(why string) {
// listenPacket opens a packet listener.
// The network must be "udp4" or "udp6".
func (c *Conn) listenPacket(network string, port uint16) (nettype.PacketConn, error) {
- ctx := sockstats.WithSockStats(context.Background(), fmt.Sprintf("magicsock.Conn:%s", network)) // unused without DNS name to resolve
+ ctx := context.Background() // unused without DNS name to resolve
+ if network == "udp4" {
+ ctx = sockstats.WithSockStats(ctx, sockstats.LabelMagicsockConnUDP4)
+ } else {
+ ctx = sockstats.WithSockStats(ctx, sockstats.LabelMagicsockConnUDP6)
+ }
addr := net.JoinHostPort("", fmt.Sprint(port))
if c.testOnlyPacketListener != nil {
return nettype.MakePacketListenerWithNetIP(c.testOnlyPacketListener).ListenPacket(ctx, network, addr)