mirror of
https://github.com/tailscale/tailscale.git
synced 2025-03-14 01:11:01 +00:00
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>
This commit is contained in:
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…
x
Reference in New Issue
Block a user