Adds tailscaled_serve_{inbound,outbound}_bytes_total, labeled by Tailscale
Service name, by wrapping the peer-facing conn in tcpHandlerForVIPService.
Per-service counters persist for the process lifetime rather than being
evicted on serve-config changes.
Fixes #19572
Signed-off-by: Raj Singh <raj@tailscale.com>
Co-authored-by: Ethan Smith <ethan.smith@grafana.com>
83 lines
2.3 KiB
Go
83 lines
2.3 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build !ts_omit_serve && !ts_omit_usermetrics
|
|
|
|
package ipnlocal
|
|
|
|
import (
|
|
"expvar"
|
|
"io"
|
|
"net"
|
|
"testing"
|
|
|
|
"tailscale.com/tailcfg"
|
|
"tailscale.com/util/usermetric"
|
|
)
|
|
|
|
func counterValue(m *usermetric.MultiLabelMap[serveLabels], svc string) int64 {
|
|
v, _ := m.Get(serveLabels{Service: svc}).(*expvar.Int)
|
|
if v == nil {
|
|
return -1
|
|
}
|
|
return v.Value()
|
|
}
|
|
|
|
func TestServiceMeteredConn(t *testing.T) {
|
|
b := newTestBackend(t)
|
|
|
|
clientSide, serverSide := net.Pipe()
|
|
defer clientSide.Close()
|
|
defer serverSide.Close()
|
|
|
|
wrapped := b.meteredConnForService(serverSide, tailcfg.ServiceName("svc:foo"))
|
|
|
|
const inboundPayload = "hello from client"
|
|
writeDone := make(chan struct{})
|
|
go func() {
|
|
clientSide.Write([]byte(inboundPayload))
|
|
close(writeDone)
|
|
}()
|
|
buf := make([]byte, len(inboundPayload))
|
|
if _, err := io.ReadFull(wrapped, buf); err != nil {
|
|
t.Fatalf("read: %v", err)
|
|
}
|
|
<-writeDone
|
|
if got := counterValue(b.metrics.serveBytesInbound, "svc:foo"); got != int64(len(inboundPayload)) {
|
|
t.Errorf("inbound = %d; want %d", got, len(inboundPayload))
|
|
}
|
|
|
|
// Deliberately a different length than inboundPayload so a backwards
|
|
// inbound/outbound wiring can't pass.
|
|
const outboundPayload = "hello from the server side"
|
|
writeDone = make(chan struct{})
|
|
go func() {
|
|
wrapped.Write([]byte(outboundPayload))
|
|
close(writeDone)
|
|
}()
|
|
buf = make([]byte, len(outboundPayload))
|
|
if _, err := io.ReadFull(clientSide, buf); err != nil {
|
|
t.Fatalf("read: %v", err)
|
|
}
|
|
<-writeDone
|
|
if got := counterValue(b.metrics.serveBytesOutbound, "svc:foo"); got != int64(len(outboundPayload)) {
|
|
t.Errorf("outbound = %d; want %d", got, len(outboundPayload))
|
|
}
|
|
}
|
|
|
|
func TestServiceMeteredConnLabelKeepsPrefix(t *testing.T) {
|
|
b := newTestBackend(t)
|
|
c1, c2 := net.Pipe()
|
|
defer c1.Close()
|
|
defer c2.Close()
|
|
wrapped := b.meteredConnForService(c1, tailcfg.ServiceName("svc:my-app"))
|
|
go c2.Write([]byte("x"))
|
|
io.ReadFull(wrapped, make([]byte, 1))
|
|
if v := counterValue(b.metrics.serveBytesInbound, "svc:my-app"); v != 1 {
|
|
t.Errorf("inbound for service=\"svc:my-app\" = %d; want 1", v)
|
|
}
|
|
if v := counterValue(b.metrics.serveBytesInbound, "my-app"); v != -1 {
|
|
t.Errorf("counter unexpectedly present under prefix-stripped name; got %d", v)
|
|
}
|
|
}
|