diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go index ad2f3ea51..196d0db5b 100644 --- a/ipn/ipnlocal/peerapi.go +++ b/ipn/ipnlocal/peerapi.go @@ -865,7 +865,7 @@ func (h *peerAPIHandler) handleServeSockStats(w http.ResponseWriter, r *http.Req w.Header().Set("Content-Type", "text/html; charset=utf-8") fmt.Fprintln(w, "

Socket Stats

") - stats, validation := sockstats.GetWithValidation() + stats, interfaceStats, validation := sockstats.Get(), sockstats.GetInterfaces(), sockstats.GetValidation() if stats == nil { fmt.Fprintln(w, "No socket stats available") return @@ -876,7 +876,7 @@ func (h *peerAPIHandler) handleServeSockStats(w http.ResponseWriter, r *http.Req fmt.Fprintln(w, "Label") fmt.Fprintln(w, "Tx") fmt.Fprintln(w, "Rx") - for _, iface := range stats.Interfaces { + for _, iface := range interfaceStats.Interfaces { fmt.Fprintf(w, "Tx (%s)", html.EscapeString(iface)) fmt.Fprintf(w, "Rx (%s)", html.EscapeString(iface)) } @@ -907,11 +907,13 @@ func (h *peerAPIHandler) handleServeSockStats(w http.ResponseWriter, r *http.Req txTotal += stat.TxBytes rxTotal += stat.RxBytes - for _, iface := range stats.Interfaces { - fmt.Fprintf(w, "%d", stat.TxBytesByInterface[iface]) - fmt.Fprintf(w, "%d", stat.RxBytesByInterface[iface]) - txTotalByInterface[iface] += stat.TxBytesByInterface[iface] - rxTotalByInterface[iface] += stat.RxBytesByInterface[iface] + if interfaceStat, ok := interfaceStats.Stats[label]; ok { + for _, iface := range interfaceStats.Interfaces { + fmt.Fprintf(w, "%d", interfaceStat.TxBytesByInterface[iface]) + fmt.Fprintf(w, "%d", interfaceStat.RxBytesByInterface[iface]) + txTotalByInterface[iface] += interfaceStat.TxBytesByInterface[iface] + rxTotalByInterface[iface] += interfaceStat.RxBytesByInterface[iface] + } } if validationStat, ok := validation.Stats[label]; ok && (validationStat.RxBytes > 0 || validationStat.TxBytes > 0) { @@ -932,7 +934,7 @@ func (h *peerAPIHandler) handleServeSockStats(w http.ResponseWriter, r *http.Req fmt.Fprintln(w, "Total") fmt.Fprintf(w, "%d", txTotal) fmt.Fprintf(w, "%d", rxTotal) - for _, iface := range stats.Interfaces { + for _, iface := range interfaceStats.Interfaces { fmt.Fprintf(w, "%d", txTotalByInterface[iface]) fmt.Fprintf(w, "%d", rxTotalByInterface[iface]) } diff --git a/log/sockstatlog/logger_test.go b/log/sockstatlog/logger_test.go index 9dec3fdd4..2d3e412a2 100644 --- a/log/sockstatlog/logger_test.go +++ b/log/sockstatlog/logger_test.go @@ -34,9 +34,6 @@ func TestDelta(t *testing.T) { Stats: map[sockstats.Label]sockstats.SockStat{ sockstats.LabelDERPHTTPClient: { TxBytes: 10, - TxBytesByInterface: map[string]uint64{ - "en0": 10, - }, }, }, }, @@ -44,9 +41,6 @@ func TestDelta(t *testing.T) { Stats: map[sockstats.Label]sockstats.SockStat{ sockstats.LabelDERPHTTPClient: { TxBytes: 10, - TxBytesByInterface: map[string]uint64{ - "en0": 10, - }, }, }, }, @@ -59,12 +53,8 @@ func TestDelta(t *testing.T) { Stats: map[sockstats.Label]sockstats.SockStat{ sockstats.LabelDERPHTTPClient: { TxBytes: 10, - TxBytesByInterface: map[string]uint64{ - "en0": 10, - }, }, }, - Interfaces: []string{"en0"}, }, wantStats: map[sockstats.Label]deltaStat{ sockstats.LabelDERPHTTPClient: {10, 0}, @@ -77,30 +67,16 @@ func TestDelta(t *testing.T) { sockstats.LabelDERPHTTPClient: { TxBytes: 10, RxBytes: 10, - TxBytesByInterface: map[string]uint64{ - "en0": 10, - }, - RxBytesByInterface: map[string]uint64{ - "en0": 10, - }, }, }, - Interfaces: []string{"en0"}, }, b: &sockstats.SockStats{ Stats: map[sockstats.Label]sockstats.SockStat{ sockstats.LabelDERPHTTPClient: { TxBytes: 10, RxBytes: 30, - TxBytesByInterface: map[string]uint64{ - "en0": 10, - }, - RxBytesByInterface: map[string]uint64{ - "en0": 30, - }, }, }, - Interfaces: []string{"en0"}, }, wantStats: map[sockstats.Label]deltaStat{ sockstats.LabelDERPHTTPClient: {0, 20}, diff --git a/net/sockstats/sockstats.go b/net/sockstats/sockstats.go index 99385ed67..ebfc45962 100644 --- a/net/sockstats/sockstats.go +++ b/net/sockstats/sockstats.go @@ -15,23 +15,17 @@ import ( ) // SockStats contains statistics for sockets instrumented with the -// WithSockStats() function, along with the interfaces that we have -// per-interface statistics for. +// WithSockStats() function type SockStats struct { Stats map[Label]SockStat - Interfaces []string CurrentInterfaceCellular bool } // SockStat contains the sent and received bytes for a socket instrumented with -// the WithSockStats() function. The bytes are also broken down by interface, -// though this may be a subset of the total if interfaces were added after the -// instrumented socket was created. +// the WithSockStats() function. type SockStat struct { - TxBytes uint64 - RxBytes uint64 - TxBytesByInterface map[string]uint64 - RxBytesByInterface map[string]uint64 + TxBytes uint64 + RxBytes uint64 } // Label is an identifier for a socket that stats are collected for. A finite @@ -67,6 +61,28 @@ func Get() *SockStats { return get() } +// InterfaceSockStats contains statistics for sockets instrumented with the +// WithSockStats() function, broken down by interface. The statistics may be a +// subset of the total if interfaces were added after the instrumented socket +// was created. +type InterfaceSockStats struct { + Stats map[Label]InterfaceSockStat + Interfaces []string +} + +// InterfaceSockStat contains the per-interface sent and received bytes for a +// socket instrumented with the WithSockStats() function. +type InterfaceSockStat struct { + TxBytesByInterface map[string]uint64 + RxBytesByInterface map[string]uint64 +} + +// GetWithInterfaces is a variant of Get that returns the current socket +// statistics broken down by interface. It is slightly more expensive than Get. +func GetInterfaces() *InterfaceSockStats { + return getInterfaces() +} + // ValidationSockStats contains external validation numbers for sockets // instrumented with WithSockStats. It may be a subset of the all sockets, // depending on what externa measurement mechanisms the platform supports. @@ -81,11 +97,11 @@ type ValidationSockStat struct { RxBytes uint64 } -// GetWithValidation is a variant of GetWith that returns both the current stats -// and external validation numbers for the stats. It is more expensive than -// Get and should be used in debug interfaces only. -func GetWithValidation() (*SockStats, *ValidationSockStats) { - return get(), getValidation() +// GetValidation is a variant of Get that returns external validation numbers +// for stats. It is more expensive than Get and should be used in debug +// interfaces only. +func GetValidation() *ValidationSockStats { + return getValidation() } // LinkMonitor is the interface for the parts of wgengine/mointor's Mon that we diff --git a/net/sockstats/sockstats_noop.go b/net/sockstats/sockstats_noop.go index 5e2b79ace..9a1ae1b3b 100644 --- a/net/sockstats/sockstats_noop.go +++ b/net/sockstats/sockstats_noop.go @@ -19,6 +19,10 @@ func get() *SockStats { return nil } +func getInterfaces() *InterfaceSockStats { + return nil +} + func getValidation() *ValidationSockStats { return nil } diff --git a/net/sockstats/sockstats_tsgo.go b/net/sockstats/sockstats_tsgo.go index 56069ec91..7a699fab1 100644 --- a/net/sockstats/sockstats_tsgo.go +++ b/net/sockstats/sockstats_tsgo.go @@ -157,20 +157,37 @@ func get() *SockStats { defer sockStats.mu.Unlock() r := &SockStats{ - Stats: make(map[Label]SockStat), - Interfaces: make([]string, 0, len(sockStats.usedInterfaces)), + Stats: make(map[Label]SockStat, len(sockStats.countersByLabel)), CurrentInterfaceCellular: sockStats.currentInterfaceCellular.Load(), } + + for label, counters := range sockStats.countersByLabel { + r.Stats[label] = SockStat{ + TxBytes: counters.txBytes.Load(), + RxBytes: counters.rxBytes.Load(), + } + } + + return r +} + +func getInterfaces() *InterfaceSockStats { + sockStats.mu.Lock() + defer sockStats.mu.Unlock() + + interfaceCount := len(sockStats.usedInterfaces) + r := &InterfaceSockStats{ + Stats: make(map[Label]InterfaceSockStat, len(sockStats.countersByLabel)), + Interfaces: make([]string, 0, interfaceCount), + } for iface := range sockStats.usedInterfaces { r.Interfaces = append(r.Interfaces, sockStats.knownInterfaces[iface]) } for label, counters := range sockStats.countersByLabel { - s := SockStat{ - TxBytes: counters.txBytes.Load(), - RxBytes: counters.rxBytes.Load(), - TxBytesByInterface: make(map[string]uint64), - RxBytesByInterface: make(map[string]uint64), + s := InterfaceSockStat{ + TxBytesByInterface: make(map[string]uint64, interfaceCount), + RxBytesByInterface: make(map[string]uint64, interfaceCount), } for iface, a := range counters.rxBytesByInterface { ifName := sockStats.knownInterfaces[iface]