diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go
index e14bb3e6c..ad2f3ea51 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 := sockstats.Get()
+ stats, validation := sockstats.GetWithValidation()
if stats == nil {
fmt.Fprintln(w, "No socket stats available")
return
@@ -914,12 +914,12 @@ func (h *peerAPIHandler) handleServeSockStats(w http.ResponseWriter, r *http.Req
rxTotalByInterface[iface] += stat.RxBytesByInterface[iface]
}
- if stat.ValidationRxBytes > 0 || stat.ValidationTxBytes > 0 {
+ if validationStat, ok := validation.Stats[label]; ok && (validationStat.RxBytes > 0 || validationStat.TxBytes > 0) {
fmt.Fprintf(w, "Tx=%d (%+d) Rx=%d (%+d) | ",
- stat.ValidationTxBytes,
- int64(stat.ValidationTxBytes)-int64(stat.TxBytes),
- stat.ValidationRxBytes,
- int64(stat.ValidationRxBytes)-int64(stat.RxBytes))
+ validationStat.TxBytes,
+ int64(validationStat.TxBytes)-int64(stat.TxBytes),
+ validationStat.RxBytes,
+ int64(validationStat.RxBytes)-int64(stat.RxBytes))
} else {
fmt.Fprintln(w, " | ")
}
diff --git a/net/sockstats/sockstats.go b/net/sockstats/sockstats.go
index 3384b8206..a06bfbae9 100644
--- a/net/sockstats/sockstats.go
+++ b/net/sockstats/sockstats.go
@@ -14,11 +14,25 @@ import (
"tailscale.com/net/interfaces"
)
+// SockStats contains statistics for sockets instrumented with the
+// WithSockStats() function, along with the interfaces that we have
+// per-interface statistics for.
type SockStats struct {
Stats map[Label]SockStat
Interfaces []string
}
+// 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.
+type SockStat struct {
+ TxBytes 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
// set of values that may be used to label a socket to encourage grouping and
// to make storage more efficient.
@@ -41,25 +55,38 @@ const (
LabelMagicsockConnUDP6 Label = 9 // wgengine/magicsock/magicsock.go
)
-type SockStat struct {
- TxBytes uint64
- RxBytes uint64
- TxBytesByInterface map[string]uint64
- RxBytesByInterface map[string]uint64
-
- // NOCOMMIT
- ValidationTxBytes uint64
- ValidationRxBytes uint64
-}
-
+// WithSockStats instruments a context so that sockets created with it will
+// have their statistics collected.
func WithSockStats(ctx context.Context, label Label) context.Context {
return withSockStats(ctx, label)
}
+// Get returns the current socket statistics.
func Get() *SockStats {
return get()
}
+// 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.
+type ValidationSockStats struct {
+ Stats map[Label]ValidationSockStat
+}
+
+// ValidationSockStat contains the validation bytes for a socket instrumented
+// with WithSockStats.
+type ValidationSockStat struct {
+ TxBytes uint64
+ 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()
+}
+
// LinkMonitor is the interface for the parts of wgengine/mointor's Mon that we
// need, to avoid the dependency.
type LinkMonitor interface {
@@ -67,6 +94,8 @@ type LinkMonitor interface {
RegisterChangeCallback(interfaces.ChangeFunc) (unregister func())
}
+// SetLinkMonitor configures the sockstats package to monitor the active
+// interface, so that per-interface stats can be collected.
func SetLinkMonitor(lm LinkMonitor) {
setLinkMonitor(lm)
}
diff --git a/net/sockstats/sockstats_noop.go b/net/sockstats/sockstats_noop.go
index b6d80e221..a62941c42 100644
--- a/net/sockstats/sockstats_noop.go
+++ b/net/sockstats/sockstats_noop.go
@@ -17,5 +17,9 @@ func get() *SockStats {
return nil
}
+func getValidation() *ValidationSockStats {
+ return nil
+}
+
func setLinkMonitor(lm LinkMonitor) {
}
diff --git a/net/sockstats/sockstats_tsgo.go b/net/sockstats/sockstats_tsgo.go
index cce192d37..2605e0eb1 100644
--- a/net/sockstats/sockstats_tsgo.go
+++ b/net/sockstats/sockstats_tsgo.go
@@ -96,6 +96,7 @@ func withSockStats(ctx context.Context, label Label) context.Context {
tx, rx := tcpConnStats(c)
counters.validationTxBytes.Add(tx)
counters.validationRxBytes.Add(rx)
+ counters.validationConn.Store(nil)
}
// Don't bother adding these hooks if we can't get stats that they end up
@@ -167,14 +168,6 @@ func get() *SockStats {
RxBytes: counters.rxBytes.Load(),
TxBytesByInterface: make(map[string]uint64),
RxBytesByInterface: make(map[string]uint64),
-
- ValidationTxBytes: counters.validationTxBytes.Load(),
- ValidationRxBytes: counters.validationRxBytes.Load(),
- }
- if c := counters.validationConn.Load(); c != nil && tcpConnStats != nil {
- tx, rx := tcpConnStats(*c)
- s.ValidationTxBytes += tx
- s.ValidationRxBytes += rx
}
for iface, a := range counters.rxBytesByInterface {
ifName := sockStats.knownInterfaces[iface]
@@ -190,6 +183,30 @@ func get() *SockStats {
return r
}
+func getValidation() *ValidationSockStats {
+ sockStats.mu.Lock()
+ defer sockStats.mu.Unlock()
+
+ r := &ValidationSockStats{
+ Stats: make(map[Label]ValidationSockStat),
+ }
+
+ for label, counters := range sockStats.countersByLabel {
+ s := ValidationSockStat{
+ TxBytes: counters.validationTxBytes.Load(),
+ RxBytes: counters.validationRxBytes.Load(),
+ }
+ if c := counters.validationConn.Load(); c != nil && tcpConnStats != nil {
+ tx, rx := tcpConnStats(*c)
+ s.TxBytes += tx
+ s.RxBytes += rx
+ }
+ r.Stats[label] = s
+ }
+
+ return r
+}
+
func setLinkMonitor(lm LinkMonitor) {
sockStats.mu.Lock()
defer sockStats.mu.Unlock()