Update dependencies
This commit is contained in:
35
vendor/tailscale.com/net/sockstats/label_string.go
generated
vendored
Normal file
35
vendor/tailscale.com/net/sockstats/label_string.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
// Code generated by "stringer -type Label -trimprefix Label"; DO NOT EDIT.
|
||||
|
||||
package sockstats
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[LabelControlClientAuto-0]
|
||||
_ = x[LabelControlClientDialer-1]
|
||||
_ = x[LabelDERPHTTPClient-2]
|
||||
_ = x[LabelLogtailLogger-3]
|
||||
_ = x[LabelDNSForwarderDoH-4]
|
||||
_ = x[LabelDNSForwarderUDP-5]
|
||||
_ = x[LabelNetcheckClient-6]
|
||||
_ = x[LabelPortmapperClient-7]
|
||||
_ = x[LabelMagicsockConnUDP4-8]
|
||||
_ = x[LabelMagicsockConnUDP6-9]
|
||||
_ = x[LabelNetlogLogger-10]
|
||||
_ = x[LabelSockstatlogLogger-11]
|
||||
_ = x[LabelDNSForwarderTCP-12]
|
||||
}
|
||||
|
||||
const _Label_name = "ControlClientAutoControlClientDialerDERPHTTPClientLogtailLoggerDNSForwarderDoHDNSForwarderUDPNetcheckClientPortmapperClientMagicsockConnUDP4MagicsockConnUDP6NetlogLoggerSockstatlogLoggerDNSForwarderTCP"
|
||||
|
||||
var _Label_index = [...]uint8{0, 17, 36, 50, 63, 78, 93, 107, 123, 140, 157, 169, 186, 201}
|
||||
|
||||
func (i Label) String() string {
|
||||
if i >= Label(len(_Label_index)-1) {
|
||||
return "Label(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Label_name[_Label_index[i]:_Label_index[i+1]]
|
||||
}
|
||||
121
vendor/tailscale.com/net/sockstats/sockstats.go
generated
vendored
Normal file
121
vendor/tailscale.com/net/sockstats/sockstats.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package sockstats collects statistics about network sockets used by
|
||||
// the Tailscale client. The context where sockets are used must be
|
||||
// instrumented with the WithSockStats() function.
|
||||
//
|
||||
// Only available on POSIX platforms when built with Tailscale's fork of Go.
|
||||
package sockstats
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
// SockStats contains statistics for sockets instrumented with the
|
||||
// WithSockStats() function
|
||||
type SockStats struct {
|
||||
Stats map[Label]SockStat
|
||||
CurrentInterfaceCellular bool
|
||||
}
|
||||
|
||||
// SockStat contains the sent and received bytes for a socket instrumented with
|
||||
// the WithSockStats() function.
|
||||
type SockStat struct {
|
||||
TxBytes uint64
|
||||
RxBytes 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.
|
||||
type Label uint8
|
||||
|
||||
//go:generate go run golang.org/x/tools/cmd/stringer -type Label -trimprefix Label
|
||||
|
||||
// Labels are named after the package and function/struct that uses the socket.
|
||||
// Values may be persisted and thus existing entries should not be re-numbered.
|
||||
const (
|
||||
LabelControlClientAuto Label = 0 // control/controlclient/auto.go
|
||||
LabelControlClientDialer Label = 1 // control/controlhttp/client.go
|
||||
LabelDERPHTTPClient Label = 2 // derp/derphttp/derphttp_client.go
|
||||
LabelLogtailLogger Label = 3 // logtail/logtail.go
|
||||
LabelDNSForwarderDoH Label = 4 // net/dns/resolver/forwarder.go
|
||||
LabelDNSForwarderUDP Label = 5 // net/dns/resolver/forwarder.go
|
||||
LabelNetcheckClient Label = 6 // net/netcheck/netcheck.go
|
||||
LabelPortmapperClient Label = 7 // net/portmapper/portmapper.go
|
||||
LabelMagicsockConnUDP4 Label = 8 // wgengine/magicsock/magicsock.go
|
||||
LabelMagicsockConnUDP6 Label = 9 // wgengine/magicsock/magicsock.go
|
||||
LabelNetlogLogger Label = 10 // wgengine/netlog/logger.go
|
||||
LabelSockstatlogLogger Label = 11 // log/sockstatlog/logger.go
|
||||
LabelDNSForwarderTCP Label = 12 // net/dns/resolver/forwarder.go
|
||||
)
|
||||
|
||||
// WithSockStats instruments a context so that sockets created with it will
|
||||
// have their statistics collected.
|
||||
func WithSockStats(ctx context.Context, label Label, logf logger.Logf) context.Context {
|
||||
return withSockStats(ctx, label, logf)
|
||||
}
|
||||
|
||||
// Get returns the current socket statistics.
|
||||
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.
|
||||
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
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
// SetNetMon configures the sockstats package to monitor the active
|
||||
// interface, so that per-interface stats can be collected.
|
||||
func SetNetMon(netMon *netmon.Monitor) {
|
||||
setNetMon(netMon)
|
||||
}
|
||||
|
||||
// DebugInfo returns a string containing debug information about the tracked
|
||||
// statistics.
|
||||
func DebugInfo() string {
|
||||
return debugInfo()
|
||||
}
|
||||
38
vendor/tailscale.com/net/sockstats/sockstats_noop.go
generated
vendored
Normal file
38
vendor/tailscale.com/net/sockstats/sockstats_noop.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !tailscale_go || !(darwin || ios || android || ts_enable_sockstats)
|
||||
|
||||
package sockstats
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
const IsAvailable = false
|
||||
|
||||
func withSockStats(ctx context.Context, label Label, logf logger.Logf) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
func get() *SockStats {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getInterfaces() *InterfaceSockStats {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getValidation() *ValidationSockStats {
|
||||
return nil
|
||||
}
|
||||
|
||||
func setNetMon(netMon *netmon.Monitor) {
|
||||
}
|
||||
|
||||
func debugInfo() string {
|
||||
return ""
|
||||
}
|
||||
430
vendor/tailscale.com/net/sockstats/sockstats_tsgo.go
generated
vendored
Normal file
430
vendor/tailscale.com/net/sockstats/sockstats_tsgo.go
generated
vendored
Normal file
@@ -0,0 +1,430 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build tailscale_go && (darwin || ios || android || ts_enable_sockstats)
|
||||
|
||||
package sockstats
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/version"
|
||||
)
|
||||
|
||||
const IsAvailable = true
|
||||
|
||||
type sockStatCounters struct {
|
||||
txBytes, rxBytes atomic.Uint64
|
||||
rxBytesByInterface, txBytesByInterface map[int]*atomic.Uint64
|
||||
|
||||
txBytesMetric, rxBytesMetric, txBytesCellularMetric, rxBytesCellularMetric *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.
|
||||
validationConn atomic.Pointer[syscall.RawConn]
|
||||
validationTxBytes, validationRxBytes atomic.Uint64
|
||||
}
|
||||
|
||||
var sockStats = struct {
|
||||
// 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
|
||||
usedInterfaces map[int]int // set of interface indexes
|
||||
|
||||
// Separate atomic since the current interface is accessed in the per-read/
|
||||
// write callbacks.
|
||||
currentInterface atomic.Uint32
|
||||
currentInterfaceCellular atomic.Bool
|
||||
|
||||
txBytesMetric, rxBytesMetric, txBytesCellularMetric, rxBytesCellularMetric *clientmetric.Metric
|
||||
radioHighMetric *clientmetric.Metric
|
||||
}{
|
||||
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"),
|
||||
radioHighMetric: clientmetric.NewGaugeFunc("sockstats_cellular_radio_high_fraction", radio.radioHighPercent),
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Deltas are not useful for this gauge metric, we want the collector to be
|
||||
// able to get current values without having to wait for the 4 hour
|
||||
// metricLogNameFrequency interval (by which point the cell radio state may
|
||||
// be very different).
|
||||
sockStats.radioHighMetric.DisableDeltas()
|
||||
}
|
||||
|
||||
func withSockStats(ctx context.Context, label Label, logf logger.Logf) context.Context {
|
||||
sockStats.mu.Lock()
|
||||
defer sockStats.mu.Unlock()
|
||||
counters, ok := sockStats.countersByLabel[label]
|
||||
if !ok {
|
||||
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)),
|
||||
txBytesCellularMetric: clientmetric.NewCounter(fmt.Sprintf("sockstats_tx_bytes_cellular_%s", label)),
|
||||
rxBytesCellularMetric: clientmetric.NewCounter(fmt.Sprintf("sockstats_rx_bytes_cellular_%s", label)),
|
||||
}
|
||||
|
||||
// We might be called before setNetMon has been called (and we've
|
||||
// had a chance to populate knownInterfaces). In that case, we'll have
|
||||
// to get the list of interfaces ourselves.
|
||||
if len(sockStats.knownInterfaces) == 0 {
|
||||
if ifaces, err := netmon.GetInterfaceList(); err == nil {
|
||||
for _, iface := range ifaces {
|
||||
counters.rxBytesByInterface[iface.Index] = &atomic.Uint64{}
|
||||
counters.txBytesByInterface[iface.Index] = &atomic.Uint64{}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for iface := range sockStats.knownInterfaces {
|
||||
counters.rxBytesByInterface[iface] = &atomic.Uint64{}
|
||||
counters.txBytesByInterface[iface] = &atomic.Uint64{}
|
||||
}
|
||||
}
|
||||
sockStats.countersByLabel[label] = counters
|
||||
}
|
||||
|
||||
didCreateTCPConn := func(c syscall.RawConn) {
|
||||
counters.validationConn.Store(&c)
|
||||
}
|
||||
|
||||
willCloseTCPConn := func(c syscall.RawConn) {
|
||||
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
|
||||
// collecting.
|
||||
if tcpConnStats == nil {
|
||||
willCloseTCPConn = nil
|
||||
didCreateTCPConn = nil
|
||||
}
|
||||
|
||||
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))
|
||||
counters.rxBytesCellularMetric.Add(int64(n))
|
||||
if n > 0 {
|
||||
radio.active()
|
||||
}
|
||||
}
|
||||
}
|
||||
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))
|
||||
counters.txBytesCellularMetric.Add(int64(n))
|
||||
if n > 0 {
|
||||
radio.active()
|
||||
}
|
||||
}
|
||||
}
|
||||
willOverwrite := func(trace *net.SockTrace) {
|
||||
if version.IsUnstableBuild() {
|
||||
// Only spam about this in dev builds.
|
||||
// See https://github.com/tailscale/tailscale/issues/13731 for known problems.
|
||||
logf("sockstats: trace %q was overwritten by another", label)
|
||||
}
|
||||
}
|
||||
|
||||
return net.WithSockTrace(ctx, &net.SockTrace{
|
||||
DidCreateTCPConn: didCreateTCPConn,
|
||||
DidRead: didRead,
|
||||
DidWrite: didWrite,
|
||||
WillOverwrite: willOverwrite,
|
||||
WillCloseTCPConn: willCloseTCPConn,
|
||||
})
|
||||
}
|
||||
|
||||
// tcpConnStats returns the number of bytes sent and received on the
|
||||
// given TCP socket. Its implementation is platform-dependent (or it may not
|
||||
// be available at all).
|
||||
var tcpConnStats func(c syscall.RawConn) (tx, rx uint64)
|
||||
|
||||
func get() *SockStats {
|
||||
sockStats.mu.Lock()
|
||||
defer sockStats.mu.Unlock()
|
||||
|
||||
r := &SockStats{
|
||||
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 := InterfaceSockStat{
|
||||
TxBytesByInterface: make(map[string]uint64, interfaceCount),
|
||||
RxBytesByInterface: make(map[string]uint64, interfaceCount),
|
||||
}
|
||||
for iface, a := range counters.rxBytesByInterface {
|
||||
ifName := sockStats.knownInterfaces[iface]
|
||||
s.RxBytesByInterface[ifName] = a.Load()
|
||||
}
|
||||
for iface, a := range counters.txBytesByInterface {
|
||||
ifName := sockStats.knownInterfaces[iface]
|
||||
s.TxBytesByInterface[ifName] = a.Load()
|
||||
}
|
||||
r.Stats[label] = s
|
||||
}
|
||||
|
||||
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 setNetMon(netMon *netmon.Monitor) {
|
||||
sockStats.mu.Lock()
|
||||
defer sockStats.mu.Unlock()
|
||||
|
||||
// We intentionally populate all known interfaces now, so that we can
|
||||
// increment stats for them without holding mu.
|
||||
state := netMon.InterfaceState()
|
||||
for ifName, iface := range state.Interface {
|
||||
sockStats.knownInterfaces[iface.Index] = ifName
|
||||
}
|
||||
if ifName := state.DefaultRouteInterface; ifName != "" {
|
||||
ifIndex := state.Interface[ifName].Index
|
||||
sockStats.currentInterface.Store(uint32(ifIndex))
|
||||
sockStats.currentInterfaceCellular.Store(isLikelyCellularInterface(ifName))
|
||||
sockStats.usedInterfaces[ifIndex] = 1
|
||||
}
|
||||
|
||||
netMon.RegisterChangeCallback(func(delta *netmon.ChangeDelta) {
|
||||
if !delta.Major {
|
||||
return
|
||||
}
|
||||
state := delta.New
|
||||
ifName := state.DefaultRouteInterface
|
||||
if ifName == "" {
|
||||
return
|
||||
}
|
||||
ifIndex := state.Interface[ifName].Index
|
||||
sockStats.mu.Lock()
|
||||
defer sockStats.mu.Unlock()
|
||||
// Ignore changes to unknown interfaces -- it would require
|
||||
// updating the tx/rxBytesByInterface maps and thus
|
||||
// additional locking for every read/write. Most of the time
|
||||
// the set of interfaces is static.
|
||||
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 debugInfo() string {
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "radio high percent: %d\n", radio.radioHighPercent())
|
||||
fmt.Fprintf(&b, "radio activity for the last hour (one minute per line):\n")
|
||||
for i, a := range radio.radioActive() {
|
||||
fmt.Fprintf(&b, "%d", a)
|
||||
if i%60 == 59 {
|
||||
fmt.Fprintf(&b, "\n")
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// radioMonitor tracks usage of the cellular radio, approximates the power state transitions,
|
||||
// and reports the percentage of time the radio was on.
|
||||
type radioMonitor struct {
|
||||
// usage tracks the last time (as unix timestamp) the radio was used over the last hour.
|
||||
// Values are indexed by the number of seconds since the beginning of the current hour.
|
||||
usage [radioSampleSize]int64
|
||||
|
||||
// startTime is the time we started tracking radio usage.
|
||||
startTime int64
|
||||
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
// radioSampleSize is the number of samples to store and report for cellular radio usage.
|
||||
// Usage is measured once per second, so this is the number of seconds of history to track.
|
||||
const radioSampleSize = 3600 // 1 hour
|
||||
|
||||
// initStallPeriod is the minimum amount of time in seconds to collect data before reporting.
|
||||
// Otherwise, all clients will report 100% radio usage on startup.
|
||||
var initStallPeriod int64 = 120 // 2 minutes
|
||||
|
||||
var radio = &radioMonitor{
|
||||
now: time.Now,
|
||||
startTime: time.Now().Unix(),
|
||||
}
|
||||
|
||||
// radioActivity should be called whenever network activity occurs on a cellular network interface.
|
||||
func (rm *radioMonitor) active() {
|
||||
t := rm.now().Unix()
|
||||
rm.usage[t%radioSampleSize] = t
|
||||
}
|
||||
|
||||
// Timings for radio power state transitions taken from
|
||||
// https://developer.android.com/training/connectivity/network-access-optimization#radio-state
|
||||
// Even though that documents a typical 3G radio and newer radios are much more efficient,
|
||||
// it provides worst-case timings to use for analysis.
|
||||
const (
|
||||
radioHighIdle = 5 // seconds radio idles in high power state before transitioning to low
|
||||
radioLowIdle = 12 // seconds radio idles in low power state before transitioning to off
|
||||
)
|
||||
|
||||
// radioActive returns a slice of 1s samples (one per second) for the past hour
|
||||
// indicating whether the radio was active (1) or idle (0).
|
||||
func (rm *radioMonitor) radioActive() (active [radioSampleSize]int64) {
|
||||
rm.forEachSample(func(c int, isActive bool) {
|
||||
if isActive {
|
||||
active[c] = 1
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// radioHighPercent returns the percentage of time (as an int from 0 to 100)
|
||||
// that the cellular radio was in high power mode during the past hour.
|
||||
// If the radio has been monitored for less than an hour,
|
||||
// the percentage is calculated based on the time monitored.
|
||||
func (rm *radioMonitor) radioHighPercent() int64 {
|
||||
var highPowerSec int64 // total seconds radio was in high power (active or idle)
|
||||
lastActive := -1 // counter when radio was last active
|
||||
|
||||
periodLength := rm.forEachSample(func(c int, isActive bool) {
|
||||
if isActive {
|
||||
// radio on and active
|
||||
highPowerSec++
|
||||
lastActive = c
|
||||
} else if lastActive != -1 && c-lastActive < radioHighIdle {
|
||||
// radio on but idle
|
||||
highPowerSec++
|
||||
}
|
||||
})
|
||||
|
||||
if periodLength < initStallPeriod {
|
||||
return 0
|
||||
}
|
||||
|
||||
if highPowerSec == 0 {
|
||||
return 0
|
||||
}
|
||||
return highPowerSec * 100 / periodLength
|
||||
}
|
||||
|
||||
// forEachSample calls f for each sample in the past hour (or less if less time
|
||||
// has passed -- the evaluated period is returned, measured in seconds)
|
||||
func (rm *radioMonitor) forEachSample(f func(c int, isActive bool)) (periodLength int64) {
|
||||
now := rm.now().Unix()
|
||||
periodLength = radioSampleSize
|
||||
if t := now - rm.startTime; t < periodLength {
|
||||
if t <= 0 {
|
||||
return 0
|
||||
}
|
||||
periodLength = t + 1 // we want an inclusive range (with the current second)
|
||||
}
|
||||
periodStart := now - periodLength // start of current reporting period
|
||||
|
||||
// split into slices of radio usage, with values in chronological order.
|
||||
// split at now+1 so that the current second is in the second slice.
|
||||
split := (now + 1) % radioSampleSize
|
||||
slices := [2][]int64{
|
||||
rm.usage[split:],
|
||||
rm.usage[:split],
|
||||
}
|
||||
|
||||
var c int // counter
|
||||
for _, slice := range slices {
|
||||
for _, v := range slice {
|
||||
f(c, v >= periodStart)
|
||||
c++
|
||||
}
|
||||
}
|
||||
|
||||
return periodLength
|
||||
}
|
||||
30
vendor/tailscale.com/net/sockstats/sockstats_tsgo_darwin.go
generated
vendored
Normal file
30
vendor/tailscale.com/net/sockstats/sockstats_tsgo_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build tailscale_go && (darwin || ios)
|
||||
|
||||
package sockstats
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tcpConnStats = darwinTcpConnStats
|
||||
}
|
||||
|
||||
func darwinTcpConnStats(c syscall.RawConn) (tx, rx uint64) {
|
||||
c.Control(func(fd uintptr) {
|
||||
if rawInfo, err := unix.GetsockoptTCPConnectionInfo(
|
||||
int(fd),
|
||||
unix.IPPROTO_TCP,
|
||||
unix.TCP_CONNECTION_INFO,
|
||||
); err == nil {
|
||||
tx = uint64(rawInfo.Txbytes)
|
||||
rx = uint64(rawInfo.Rxbytes)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user