derp: add TCP RTT metric on Linux (#5949)
Periodically poll the TCP RTT metric from all open TCP connections and update a (bucketed) histogram metric. Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: I6214902196b05bf7829c9d0ea501ce0e13d984cfmain
parent
a04f1ff9e6
commit
64ea60aaa3
@ -0,0 +1,14 @@ |
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package derp |
||||
|
||||
import "context" |
||||
|
||||
func (c *sclient) statsLoop(ctx context.Context) error { |
||||
return nil |
||||
} |
||||
@ -0,0 +1,95 @@ |
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package derp |
||||
|
||||
import ( |
||||
"context" |
||||
"crypto/tls" |
||||
"net" |
||||
"time" |
||||
|
||||
"golang.org/x/sys/unix" |
||||
) |
||||
|
||||
func (c *sclient) statsLoop(ctx context.Context) error { |
||||
// If we can't get a TCP socket, then we can't send stats.
|
||||
tcpConn := c.tcpConn() |
||||
if tcpConn == nil { |
||||
c.s.tcpRtt.Add("non-tcp", 1) |
||||
return nil |
||||
} |
||||
rawConn, err := tcpConn.SyscallConn() |
||||
if err != nil { |
||||
c.logf("error getting SyscallConn: %v", err) |
||||
c.s.tcpRtt.Add("error", 1) |
||||
return nil |
||||
} |
||||
|
||||
const statsInterval = 10 * time.Second |
||||
|
||||
ticker := time.NewTicker(statsInterval) |
||||
defer ticker.Stop() |
||||
|
||||
var ( |
||||
tcpInfo *unix.TCPInfo |
||||
sysErr error |
||||
) |
||||
statsLoop: |
||||
for { |
||||
select { |
||||
case <-ticker.C: |
||||
err = rawConn.Control(func(fd uintptr) { |
||||
tcpInfo, sysErr = unix.GetsockoptTCPInfo(int(fd), unix.IPPROTO_TCP, unix.TCP_INFO) |
||||
}) |
||||
if err != nil || sysErr != nil { |
||||
continue statsLoop |
||||
} |
||||
|
||||
// TODO(andrew): more metrics?
|
||||
rtt := time.Duration(tcpInfo.Rtt) * time.Microsecond |
||||
c.s.tcpRtt.Add(durationToLabel(rtt), 1) |
||||
|
||||
case <-ctx.Done(): |
||||
return ctx.Err() |
||||
} |
||||
} |
||||
} |
||||
|
||||
// tcpConn attempts to get the underlying *net.TCPConn from this client's
|
||||
// Conn; if it cannot, then it will return nil.
|
||||
func (c *sclient) tcpConn() *net.TCPConn { |
||||
nc := c.nc |
||||
for { |
||||
switch v := nc.(type) { |
||||
case *net.TCPConn: |
||||
return v |
||||
case *tls.Conn: |
||||
nc = v.NetConn() |
||||
default: |
||||
return nil |
||||
} |
||||
} |
||||
} |
||||
|
||||
func durationToLabel(dur time.Duration) string { |
||||
switch { |
||||
case dur <= 10*time.Millisecond: |
||||
return "10ms" |
||||
case dur <= 20*time.Millisecond: |
||||
return "20ms" |
||||
case dur <= 50*time.Millisecond: |
||||
return "50ms" |
||||
case dur <= 100*time.Millisecond: |
||||
return "100ms" |
||||
case dur <= 150*time.Millisecond: |
||||
return "150ms" |
||||
case dur <= 250*time.Millisecond: |
||||
return "250ms" |
||||
case dur <= 500*time.Millisecond: |
||||
return "500ms" |
||||
default: |
||||
return "inf" |
||||
} |
||||
} |
||||
Loading…
Reference in new issue