sockstats: export as client metrics

Though not fine-grained enough to be useful for detailed analysis, we
might as well export that we gather as client metrics too, since we have
an upload/analysis pipeline for them.

clientmetric.Metric.Add is an atomic add, so it's pretty cheap to also
do per-packet.

Updates tailscale/corp#9230
Updates #3363

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
pull/7523/head
Mihai Parparita 1 year ago committed by Mihai Parparita
parent 1e72de6b72
commit ea81bffdeb

@ -7,19 +7,24 @@ package sockstats
import (
"context"
"fmt"
"log"
"net"
"strings"
"sync"
"sync/atomic"
"syscall"
"tailscale.com/net/interfaces"
"tailscale.com/util/clientmetric"
)
type sockStatCounters struct {
txBytes, rxBytes atomic.Uint64
rxBytesByInterface, txBytesByInterface map[int]*atomic.Uint64
txBytesMetric, rxBytesMetric *clientmetric.Metric
// Validate counts for TCP sockets by using the TCP_CONNECTION_INFO
// getsockopt. We get current counts, as well as save final values when
// sockets are closed.
@ -28,8 +33,9 @@ type sockStatCounters struct {
}
var sockStats = struct {
// mu protects fields in this group. It should not be held in the per-read/
// write callbacks.
// mu protects fields in this group (but not the fields within
// sockStatCounters). It should not be held in the per-read/write
// callbacks.
mu sync.Mutex
countersByLabel map[Label]*sockStatCounters
knownInterfaces map[int]string // interface index -> name
@ -37,11 +43,18 @@ var sockStats = struct {
// Separate atomic since the current interface is accessed in the per-read/
// write callbacks.
currentInterface atomic.Uint32
currentInterface atomic.Uint32
currentInterfaceCellular atomic.Bool
txBytesMetric, rxBytesMetric, txBytesCellularMetric, rxBytesCellularMetric *clientmetric.Metric
}{
countersByLabel: make(map[Label]*sockStatCounters),
knownInterfaces: make(map[int]string),
usedInterfaces: make(map[int]int),
countersByLabel: make(map[Label]*sockStatCounters),
knownInterfaces: make(map[int]string),
usedInterfaces: make(map[int]int),
txBytesMetric: clientmetric.NewCounter("sockstats_tx_bytes"),
rxBytesMetric: clientmetric.NewCounter("sockstats_rx_bytes"),
txBytesCellularMetric: clientmetric.NewCounter("sockstats_tx_bytes_cellular"),
rxBytesCellularMetric: clientmetric.NewCounter("sockstats_rx_bytes_cellular"),
}
func withSockStats(ctx context.Context, label Label) context.Context {
@ -52,6 +65,8 @@ func withSockStats(ctx context.Context, label Label) context.Context {
counters = &sockStatCounters{
rxBytesByInterface: make(map[int]*atomic.Uint64),
txBytesByInterface: make(map[int]*atomic.Uint64),
txBytesMetric: clientmetric.NewCounter(fmt.Sprintf("sockstats_tx_bytes_%s", label)),
rxBytesMetric: clientmetric.NewCounter(fmt.Sprintf("sockstats_rx_bytes_%s", label)),
}
// We might be called before setLinkMonitor has been called (and we've
@ -92,19 +107,29 @@ func withSockStats(ctx context.Context, label Label) context.Context {
didRead := func(n int) {
counters.rxBytes.Add(uint64(n))
counters.rxBytesMetric.Add(int64(n))
sockStats.rxBytesMetric.Add(int64(n))
if currentInterface := int(sockStats.currentInterface.Load()); currentInterface != 0 {
if a := counters.rxBytesByInterface[currentInterface]; a != nil {
a.Add(uint64(n))
}
}
if sockStats.currentInterfaceCellular.Load() {
sockStats.rxBytesCellularMetric.Add(int64(n))
}
}
didWrite := func(n int) {
counters.txBytes.Add(uint64(n))
counters.txBytesMetric.Add(int64(n))
sockStats.txBytesMetric.Add(int64(n))
if currentInterface := int(sockStats.currentInterface.Load()); currentInterface != 0 {
if a := counters.txBytesByInterface[currentInterface]; a != nil {
a.Add(uint64(n))
}
}
if sockStats.currentInterfaceCellular.Load() {
sockStats.txBytesCellularMetric.Add(int64(n))
}
}
willOverwrite := func(trace *net.SockTrace) {
log.Printf("sockstats: trace %q was overwritten by another", label)
@ -178,6 +203,7 @@ func setLinkMonitor(lm LinkMonitor) {
if ifName := state.DefaultRouteInterface; ifName != "" {
ifIndex := state.Interface[ifName].Index
sockStats.currentInterface.Store(uint32(ifIndex))
sockStats.currentInterfaceCellular.Store(isLikelyCellularInterface(ifName))
sockStats.usedInterfaces[ifIndex] = 1
}
@ -194,10 +220,18 @@ func setLinkMonitor(lm LinkMonitor) {
if _, ok := sockStats.knownInterfaces[ifIndex]; ok {
sockStats.currentInterface.Store(uint32(ifIndex))
sockStats.usedInterfaces[ifIndex] = 1
sockStats.currentInterfaceCellular.Store(isLikelyCellularInterface(ifName))
} else {
sockStats.currentInterface.Store(0)
sockStats.currentInterfaceCellular.Store(false)
}
}
}
})
}
func isLikelyCellularInterface(ifName string) bool {
return strings.HasPrefix(ifName, "rmnet") || // Android
strings.HasPrefix(ifName, "ww") || // systemd naming scheme for WWAN
strings.HasPrefix(ifName, "pdp") // iOS
}

Loading…
Cancel
Save