sockstats: remove per-interface stats from Get

They're not needed for the sockstats logger, and they're somewhat
expensive to return (since they involve the creation of a map per
label). We now have a separate GetInterfaces() method that returns
them instead (which we can still use in the PeerAPI debug endpoint).

If changing sockstatlog to sample at 10,000 Hz (instead of the default
of 10Hz), the CPU usage would go up to 59% on a iPhone XS. Removing the
per-interface stats drops it to 20% (a no-op implementation of Get that
returns a fixed value is 16%).

Updates tailscale/corp#9230
Updates #3363

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
pull/7570/head
Mihai Parparita 1 year ago committed by Mihai Parparita
parent 9ebab961c9
commit 97b6d3e917

@ -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, "<!DOCTYPE html><h1>Socket Stats</h1>")
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, "<th>Label</th>")
fmt.Fprintln(w, "<th>Tx</th>")
fmt.Fprintln(w, "<th>Rx</th>")
for _, iface := range stats.Interfaces {
for _, iface := range interfaceStats.Interfaces {
fmt.Fprintf(w, "<th>Tx (%s)</th>", html.EscapeString(iface))
fmt.Fprintf(w, "<th>Rx (%s)</th>", 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, "<td align=right>%d</td>", stat.TxBytesByInterface[iface])
fmt.Fprintf(w, "<td align=right>%d</td>", 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, "<td align=right>%d</td>", interfaceStat.TxBytesByInterface[iface])
fmt.Fprintf(w, "<td align=right>%d</td>", 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, "<th>Total</th>")
fmt.Fprintf(w, "<th>%d</th>", txTotal)
fmt.Fprintf(w, "<th>%d</th>", rxTotal)
for _, iface := range stats.Interfaces {
for _, iface := range interfaceStats.Interfaces {
fmt.Fprintf(w, "<th>%d</th>", txTotalByInterface[iface])
fmt.Fprintf(w, "<th>%d</th>", rxTotalByInterface[iface])
}

@ -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},

@ -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

@ -19,6 +19,10 @@ func get() *SockStats {
return nil
}
func getInterfaces() *InterfaceSockStats {
return nil
}
func getValidation() *ValidationSockStats {
return nil
}

@ -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]

Loading…
Cancel
Save