mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-21 12:28:39 +00:00
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>
This commit is contained in:
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")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
fmt.Fprintln(w, "<!DOCTYPE html><h1>Socket Stats</h1>")
|
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 {
|
if stats == nil {
|
||||||
fmt.Fprintln(w, "No socket stats available")
|
fmt.Fprintln(w, "No socket stats available")
|
||||||
return
|
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>Label</th>")
|
||||||
fmt.Fprintln(w, "<th>Tx</th>")
|
fmt.Fprintln(w, "<th>Tx</th>")
|
||||||
fmt.Fprintln(w, "<th>Rx</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>Tx (%s)</th>", html.EscapeString(iface))
|
||||||
fmt.Fprintf(w, "<th>Rx (%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
|
txTotal += stat.TxBytes
|
||||||
rxTotal += stat.RxBytes
|
rxTotal += stat.RxBytes
|
||||||
|
|
||||||
for _, iface := range stats.Interfaces {
|
if interfaceStat, ok := interfaceStats.Stats[label]; ok {
|
||||||
fmt.Fprintf(w, "<td align=right>%d</td>", stat.TxBytesByInterface[iface])
|
for _, iface := range interfaceStats.Interfaces {
|
||||||
fmt.Fprintf(w, "<td align=right>%d</td>", stat.RxBytesByInterface[iface])
|
fmt.Fprintf(w, "<td align=right>%d</td>", interfaceStat.TxBytesByInterface[iface])
|
||||||
txTotalByInterface[iface] += stat.TxBytesByInterface[iface]
|
fmt.Fprintf(w, "<td align=right>%d</td>", interfaceStat.RxBytesByInterface[iface])
|
||||||
rxTotalByInterface[iface] += stat.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) {
|
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.Fprintln(w, "<th>Total</th>")
|
||||||
fmt.Fprintf(w, "<th>%d</th>", txTotal)
|
fmt.Fprintf(w, "<th>%d</th>", txTotal)
|
||||||
fmt.Fprintf(w, "<th>%d</th>", rxTotal)
|
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>", txTotalByInterface[iface])
|
||||||
fmt.Fprintf(w, "<th>%d</th>", rxTotalByInterface[iface])
|
fmt.Fprintf(w, "<th>%d</th>", rxTotalByInterface[iface])
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,6 @@ func TestDelta(t *testing.T) {
|
|||||||
Stats: map[sockstats.Label]sockstats.SockStat{
|
Stats: map[sockstats.Label]sockstats.SockStat{
|
||||||
sockstats.LabelDERPHTTPClient: {
|
sockstats.LabelDERPHTTPClient: {
|
||||||
TxBytes: 10,
|
TxBytes: 10,
|
||||||
TxBytesByInterface: map[string]uint64{
|
|
||||||
"en0": 10,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -44,9 +41,6 @@ func TestDelta(t *testing.T) {
|
|||||||
Stats: map[sockstats.Label]sockstats.SockStat{
|
Stats: map[sockstats.Label]sockstats.SockStat{
|
||||||
sockstats.LabelDERPHTTPClient: {
|
sockstats.LabelDERPHTTPClient: {
|
||||||
TxBytes: 10,
|
TxBytes: 10,
|
||||||
TxBytesByInterface: map[string]uint64{
|
|
||||||
"en0": 10,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -59,13 +53,9 @@ func TestDelta(t *testing.T) {
|
|||||||
Stats: map[sockstats.Label]sockstats.SockStat{
|
Stats: map[sockstats.Label]sockstats.SockStat{
|
||||||
sockstats.LabelDERPHTTPClient: {
|
sockstats.LabelDERPHTTPClient: {
|
||||||
TxBytes: 10,
|
TxBytes: 10,
|
||||||
TxBytesByInterface: map[string]uint64{
|
|
||||||
"en0": 10,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Interfaces: []string{"en0"},
|
|
||||||
},
|
|
||||||
wantStats: map[sockstats.Label]deltaStat{
|
wantStats: map[sockstats.Label]deltaStat{
|
||||||
sockstats.LabelDERPHTTPClient: {10, 0},
|
sockstats.LabelDERPHTTPClient: {10, 0},
|
||||||
},
|
},
|
||||||
@ -77,31 +67,17 @@ func TestDelta(t *testing.T) {
|
|||||||
sockstats.LabelDERPHTTPClient: {
|
sockstats.LabelDERPHTTPClient: {
|
||||||
TxBytes: 10,
|
TxBytes: 10,
|
||||||
RxBytes: 10,
|
RxBytes: 10,
|
||||||
TxBytesByInterface: map[string]uint64{
|
|
||||||
"en0": 10,
|
|
||||||
},
|
|
||||||
RxBytesByInterface: map[string]uint64{
|
|
||||||
"en0": 10,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Interfaces: []string{"en0"},
|
|
||||||
},
|
|
||||||
b: &sockstats.SockStats{
|
b: &sockstats.SockStats{
|
||||||
Stats: map[sockstats.Label]sockstats.SockStat{
|
Stats: map[sockstats.Label]sockstats.SockStat{
|
||||||
sockstats.LabelDERPHTTPClient: {
|
sockstats.LabelDERPHTTPClient: {
|
||||||
TxBytes: 10,
|
TxBytes: 10,
|
||||||
RxBytes: 30,
|
RxBytes: 30,
|
||||||
TxBytesByInterface: map[string]uint64{
|
|
||||||
"en0": 10,
|
|
||||||
},
|
|
||||||
RxBytesByInterface: map[string]uint64{
|
|
||||||
"en0": 30,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Interfaces: []string{"en0"},
|
|
||||||
},
|
|
||||||
wantStats: map[sockstats.Label]deltaStat{
|
wantStats: map[sockstats.Label]deltaStat{
|
||||||
sockstats.LabelDERPHTTPClient: {0, 20},
|
sockstats.LabelDERPHTTPClient: {0, 20},
|
||||||
},
|
},
|
||||||
|
@ -15,23 +15,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// SockStats contains statistics for sockets instrumented with the
|
// SockStats contains statistics for sockets instrumented with the
|
||||||
// WithSockStats() function, along with the interfaces that we have
|
// WithSockStats() function
|
||||||
// per-interface statistics for.
|
|
||||||
type SockStats struct {
|
type SockStats struct {
|
||||||
Stats map[Label]SockStat
|
Stats map[Label]SockStat
|
||||||
Interfaces []string
|
|
||||||
CurrentInterfaceCellular bool
|
CurrentInterfaceCellular bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// SockStat contains the sent and received bytes for a socket instrumented with
|
// SockStat contains the sent and received bytes for a socket instrumented with
|
||||||
// the WithSockStats() function. The bytes are also broken down by interface,
|
// the WithSockStats() function.
|
||||||
// though this may be a subset of the total if interfaces were added after the
|
|
||||||
// instrumented socket was created.
|
|
||||||
type SockStat struct {
|
type SockStat struct {
|
||||||
TxBytes uint64
|
TxBytes uint64
|
||||||
RxBytes uint64
|
RxBytes uint64
|
||||||
TxBytesByInterface map[string]uint64
|
|
||||||
RxBytesByInterface map[string]uint64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Label is an identifier for a socket that stats are collected for. A finite
|
// Label is an identifier for a socket that stats are collected for. A finite
|
||||||
@ -67,6 +61,28 @@ func Get() *SockStats {
|
|||||||
return get()
|
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
|
// ValidationSockStats contains external validation numbers for sockets
|
||||||
// instrumented with WithSockStats. It may be a subset of the all sockets,
|
// instrumented with WithSockStats. It may be a subset of the all sockets,
|
||||||
// depending on what externa measurement mechanisms the platform supports.
|
// depending on what externa measurement mechanisms the platform supports.
|
||||||
@ -81,11 +97,11 @@ type ValidationSockStat struct {
|
|||||||
RxBytes uint64
|
RxBytes uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWithValidation is a variant of GetWith that returns both the current stats
|
// GetValidation is a variant of Get that returns external validation numbers
|
||||||
// and external validation numbers for the stats. It is more expensive than
|
// for stats. It is more expensive than Get and should be used in debug
|
||||||
// Get and should be used in debug interfaces only.
|
// interfaces only.
|
||||||
func GetWithValidation() (*SockStats, *ValidationSockStats) {
|
func GetValidation() *ValidationSockStats {
|
||||||
return get(), getValidation()
|
return getValidation()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LinkMonitor is the interface for the parts of wgengine/mointor's Mon that we
|
// LinkMonitor is the interface for the parts of wgengine/mointor's Mon that we
|
||||||
|
@ -19,6 +19,10 @@ func get() *SockStats {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getInterfaces() *InterfaceSockStats {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func getValidation() *ValidationSockStats {
|
func getValidation() *ValidationSockStats {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -157,20 +157,37 @@ func get() *SockStats {
|
|||||||
defer sockStats.mu.Unlock()
|
defer sockStats.mu.Unlock()
|
||||||
|
|
||||||
r := &SockStats{
|
r := &SockStats{
|
||||||
Stats: make(map[Label]SockStat),
|
Stats: make(map[Label]SockStat, len(sockStats.countersByLabel)),
|
||||||
Interfaces: make([]string, 0, len(sockStats.usedInterfaces)),
|
|
||||||
CurrentInterfaceCellular: sockStats.currentInterfaceCellular.Load(),
|
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 {
|
for iface := range sockStats.usedInterfaces {
|
||||||
r.Interfaces = append(r.Interfaces, sockStats.knownInterfaces[iface])
|
r.Interfaces = append(r.Interfaces, sockStats.knownInterfaces[iface])
|
||||||
}
|
}
|
||||||
|
|
||||||
for label, counters := range sockStats.countersByLabel {
|
for label, counters := range sockStats.countersByLabel {
|
||||||
s := SockStat{
|
s := InterfaceSockStat{
|
||||||
TxBytes: counters.txBytes.Load(),
|
TxBytesByInterface: make(map[string]uint64, interfaceCount),
|
||||||
RxBytes: counters.rxBytes.Load(),
|
RxBytesByInterface: make(map[string]uint64, interfaceCount),
|
||||||
TxBytesByInterface: make(map[string]uint64),
|
|
||||||
RxBytesByInterface: make(map[string]uint64),
|
|
||||||
}
|
}
|
||||||
for iface, a := range counters.rxBytesByInterface {
|
for iface, a := range counters.rxBytesByInterface {
|
||||||
ifName := sockStats.knownInterfaces[iface]
|
ifName := sockStats.knownInterfaces[iface]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user