util/usermetrics: add package and container for usermetrics

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
Kristoffer Dalby
2024-08-01 17:42:19 +02:00
parent dd5499ed5f
commit 9eec0d969c
10 changed files with 82 additions and 28 deletions

View File

@@ -34,9 +34,9 @@ import (
"tailscale.com/net/netutil" "tailscale.com/net/netutil"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tsweb/varz"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/httpm" "tailscale.com/util/httpm"
"tailscale.com/util/usermetrics"
"tailscale.com/version" "tailscale.com/version"
"tailscale.com/version/distro" "tailscale.com/version/distro"
) )
@@ -285,7 +285,7 @@ func (s *Server) serve(w http.ResponseWriter, r *http.Request) {
} }
if strings.HasPrefix(r.URL.Path, "/metrics") { if strings.HasPrefix(r.URL.Path, "/metrics") {
varz.Handler(w, r) usermetrics.Handler(w, r)
return return
} }

View File

@@ -754,7 +754,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/tstime from tailscale.com/cmd/k8s-operator+ tailscale.com/tstime from tailscale.com/cmd/k8s-operator+
tailscale.com/tstime/mono from tailscale.com/net/tstun+ tailscale.com/tstime/mono from tailscale.com/net/tstun+
tailscale.com/tstime/rate from tailscale.com/derp+ tailscale.com/tstime/rate from tailscale.com/derp+
tailscale.com/tsweb/varz from tailscale.com/client/web tailscale.com/tsweb/varz from tailscale.com/util/usermetrics
tailscale.com/types/appctype from tailscale.com/ipn/ipnlocal tailscale.com/types/appctype from tailscale.com/ipn/ipnlocal
tailscale.com/types/dnstype from tailscale.com/ipn/ipnlocal+ tailscale.com/types/dnstype from tailscale.com/ipn/ipnlocal+
tailscale.com/types/empty from tailscale.com/ipn+ tailscale.com/types/empty from tailscale.com/ipn+
@@ -813,6 +813,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/util/testenv from tailscale.com/control/controlclient+ tailscale.com/util/testenv from tailscale.com/control/controlclient+
tailscale.com/util/truncate from tailscale.com/logtail tailscale.com/util/truncate from tailscale.com/logtail
tailscale.com/util/uniq from tailscale.com/ipn/ipnlocal+ tailscale.com/util/uniq from tailscale.com/ipn/ipnlocal+
tailscale.com/util/usermetrics from tailscale.com/client/web+
tailscale.com/util/vizerror from tailscale.com/tailcfg+ tailscale.com/util/vizerror from tailscale.com/tailcfg+
💣 tailscale.com/util/winutil from tailscale.com/clientupdate+ 💣 tailscale.com/util/winutil from tailscale.com/clientupdate+
W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/clientupdate+ W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/clientupdate+

View File

@@ -132,7 +132,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/tstime from tailscale.com/control/controlhttp+ tailscale.com/tstime from tailscale.com/control/controlhttp+
tailscale.com/tstime/mono from tailscale.com/tstime/rate tailscale.com/tstime/mono from tailscale.com/tstime/rate
tailscale.com/tstime/rate from tailscale.com/cmd/tailscale/cli+ tailscale.com/tstime/rate from tailscale.com/cmd/tailscale/cli+
tailscale.com/tsweb/varz from tailscale.com/client/web tailscale.com/tsweb/varz from tailscale.com/util/usermetrics
tailscale.com/types/dnstype from tailscale.com/tailcfg tailscale.com/types/dnstype from tailscale.com/tailcfg
tailscale.com/types/empty from tailscale.com/ipn tailscale.com/types/empty from tailscale.com/ipn
tailscale.com/types/ipproto from tailscale.com/net/flowtrack+ tailscale.com/types/ipproto from tailscale.com/net/flowtrack+
@@ -174,6 +174,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/util/syspolicy/setting from tailscale.com/util/syspolicy tailscale.com/util/syspolicy/setting from tailscale.com/util/syspolicy
tailscale.com/util/testenv from tailscale.com/cmd/tailscale/cli tailscale.com/util/testenv from tailscale.com/cmd/tailscale/cli
tailscale.com/util/truncate from tailscale.com/cmd/tailscale/cli tailscale.com/util/truncate from tailscale.com/cmd/tailscale/cli
tailscale.com/util/usermetrics from tailscale.com/client/web
tailscale.com/util/vizerror from tailscale.com/tailcfg+ tailscale.com/util/vizerror from tailscale.com/tailcfg+
💣 tailscale.com/util/winutil from tailscale.com/clientupdate+ 💣 tailscale.com/util/winutil from tailscale.com/clientupdate+
W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/clientupdate W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/clientupdate

View File

@@ -403,6 +403,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/util/testenv from tailscale.com/ipn/ipnlocal+ tailscale.com/util/testenv from tailscale.com/ipn/ipnlocal+
tailscale.com/util/truncate from tailscale.com/logtail tailscale.com/util/truncate from tailscale.com/logtail
tailscale.com/util/uniq from tailscale.com/ipn/ipnlocal+ tailscale.com/util/uniq from tailscale.com/ipn/ipnlocal+
tailscale.com/util/usermetrics from tailscale.com/client/web+
tailscale.com/util/vizerror from tailscale.com/tailcfg+ tailscale.com/util/vizerror from tailscale.com/tailcfg+
💣 tailscale.com/util/winutil from tailscale.com/clientupdate+ 💣 tailscale.com/util/winutil from tailscale.com/clientupdate+
W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/clientupdate+ W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/clientupdate+

View File

@@ -23,7 +23,6 @@ import (
xmaps "golang.org/x/exp/maps" xmaps "golang.org/x/exp/maps"
"tailscale.com/control/controlknobs" "tailscale.com/control/controlknobs"
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/metrics"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tstime" "tailscale.com/tstime"
"tailscale.com/types/key" "tailscale.com/types/key"
@@ -34,6 +33,7 @@ import (
"tailscale.com/util/clientmetric" "tailscale.com/util/clientmetric"
"tailscale.com/util/mak" "tailscale.com/util/mak"
"tailscale.com/util/set" "tailscale.com/util/set"
"tailscale.com/util/usermetrics"
"tailscale.com/wgengine/filter" "tailscale.com/wgengine/filter"
) )
@@ -362,7 +362,7 @@ type healthMessageLabel struct {
Severity string Severity string
} }
var metricHealthMessages = metrics.NewMultiLabelMap[healthMessageLabel]( var metricHealthMessages = usermetrics.NewMultiLabelMap[healthMessageLabel](
"tailscaled_health_messages", "tailscaled_health_messages",
"gauge", "gauge",
"A gauge of health messages from control, by severity", "A gauge of health messages from control, by severity",

View File

@@ -61,7 +61,6 @@ import (
"tailscale.com/ipn/policy" "tailscale.com/ipn/policy"
"tailscale.com/log/sockstatlog" "tailscale.com/log/sockstatlog"
"tailscale.com/logpolicy" "tailscale.com/logpolicy"
"tailscale.com/metrics"
"tailscale.com/net/captivedetection" "tailscale.com/net/captivedetection"
"tailscale.com/net/dns" "tailscale.com/net/dns"
"tailscale.com/net/dnscache" "tailscale.com/net/dnscache"
@@ -108,6 +107,7 @@ import (
"tailscale.com/util/systemd" "tailscale.com/util/systemd"
"tailscale.com/util/testenv" "tailscale.com/util/testenv"
"tailscale.com/util/uniq" "tailscale.com/util/uniq"
"tailscale.com/util/usermetrics"
"tailscale.com/version" "tailscale.com/version"
"tailscale.com/version/distro" "tailscale.com/version/distro"
"tailscale.com/wgengine" "tailscale.com/wgengine"
@@ -4616,7 +4616,7 @@ func unmapIPPrefixes(ippsList ...[]netip.Prefix) (ret []netip.Prefix) {
return ret return ret
} }
var metricAdvertisedRoutes = metrics.NewMultiLabelMap[struct{}]( var metricAdvertisedRoutes = usermetrics.NewMultiLabelMap[struct{}](
"tailscaled_advertised_routes", "tailscaled_advertised_routes",
"gauge", "gauge",
"Number of subnet routes advertised by the node. (excluding exit node /0 routes)", "Number of subnet routes advertised by the node. (excluding exit node /0 routes)",

View File

@@ -39,7 +39,7 @@ func NewMultiLabelMap[T comparable](name string, promType, helpText string) *Mul
Help: helpText, Help: helpText,
} }
var zero T var zero T
_ = labelString(zero) // panic early if T is invalid _ = LabelString(zero) // panic early if T is invalid
expvar.Publish(name, m) expvar.Publish(name, m)
return m return m
} }
@@ -50,8 +50,10 @@ type labelsAndValue[T comparable] struct {
val expvar.Var val expvar.Var
} }
// labelString returns a Prometheus-formatted label string for the given key. // LabelString returns a Prometheus-formatted label string for the given key.
func labelString(k any) string { // k must be a struct type with scalar fields, as required by MultiLabelMap,
// if k is not a struct, it will panic.
func LabelString(k any) string {
rv := reflect.ValueOf(k) rv := reflect.ValueOf(k)
t := rv.Type() t := rv.Type()
if t.Kind() != reflect.Struct { if t.Kind() != reflect.Struct {
@@ -150,7 +152,7 @@ func (v *MultiLabelMap[T]) Init() *MultiLabelMap[T] {
// //
// v.mu must be held. // v.mu must be held.
func (v *MultiLabelMap[T]) addKeyLocked(key T, val expvar.Var) { func (v *MultiLabelMap[T]) addKeyLocked(key T, val expvar.Var) {
ls := labelString(key) ls := LabelString(key)
ent := labelsAndValue[T]{key, ls, val} ent := labelsAndValue[T]{key, ls, val}
// Using insertion sort to place key into the already-sorted v.keys. // Using insertion sort to place key into the already-sorted v.keys.
@@ -234,7 +236,7 @@ func (v *MultiLabelMap[T]) AddFloat(key T, delta float64) {
// This is not optimized for highly concurrent usage; it's presumed to only be // This is not optimized for highly concurrent usage; it's presumed to only be
// used rarely, at startup. // used rarely, at startup.
func (v *MultiLabelMap[T]) Delete(key T) { func (v *MultiLabelMap[T]) Delete(key T) {
ls := labelString(key) ls := LabelString(key)
v.mu.Lock() v.mu.Lock()
defer v.mu.Unlock() defer v.mu.Unlock()

View File

@@ -24,7 +24,6 @@ import (
"go4.org/mem" "go4.org/mem"
"gvisor.dev/gvisor/pkg/tcpip/stack" "gvisor.dev/gvisor/pkg/tcpip/stack"
"tailscale.com/disco" "tailscale.com/disco"
"tailscale.com/metrics"
"tailscale.com/net/connstats" "tailscale.com/net/connstats"
"tailscale.com/net/packet" "tailscale.com/net/packet"
"tailscale.com/net/packet/checksum" "tailscale.com/net/packet/checksum"
@@ -35,6 +34,7 @@ import (
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/clientmetric" "tailscale.com/util/clientmetric"
"tailscale.com/util/usermetrics"
"tailscale.com/wgengine/capture" "tailscale.com/wgengine/capture"
"tailscale.com/wgengine/filter" "tailscale.com/wgengine/filter"
"tailscale.com/wgengine/wgcfg" "tailscale.com/wgengine/wgcfg"
@@ -1434,12 +1434,12 @@ type trafficLabel struct {
} }
var ( var (
metricInboundPacketsTotal = metrics.NewMultiLabelMap[trafficLabel]( metricInboundPacketsTotal = usermetrics.NewMultiLabelMap[trafficLabel](
"tailscaled_inbound_packets_total", "tailscaled_inbound_packets_total",
"counter", "counter",
"Counts the number of packets received by the node from other peers", "Counts the number of packets received by the node from other peers",
) )
metricOutboundPacketsTotal = metrics.NewMultiLabelMap[trafficLabel]( metricOutboundPacketsTotal = usermetrics.NewMultiLabelMap[trafficLabel](
"tailscaled_outbound_packets_total", "tailscaled_outbound_packets_total",
"counter", "counter",
"Counts the number of packets sent by the node to other peers", "Counts the number of packets sent by the node to other peers",

View File

@@ -273,19 +273,28 @@ type sortedKVs struct {
// //
// This will evolve over time, or perhaps be replaced. // This will evolve over time, or perhaps be replaced.
func Handler(w http.ResponseWriter, r *http.Request) { func Handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain;version=0.0.4;charset=utf-8") ExpvarDoHandler(expvarDo)(w, r)
}
s := sortedKVsPool.Get().(*sortedKVs) // ExpvarDoHandler handler returns a Handler like above, but takes an optional
defer sortedKVsPool.Put(s) // expvar.Do func allow the usage of alternative containers of metrics, other
s.kvs = s.kvs[:0] // than the global expvar.Map.
expvarDo(func(kv expvar.KeyValue) { func ExpvarDoHandler(expvarDoFunc func(f func(expvar.KeyValue))) func(http.ResponseWriter, *http.Request) {
s.kvs = append(s.kvs, sortedKV{kv, removeTypePrefixes(kv.Key)}) return func(w http.ResponseWriter, r *http.Request) {
}) w.Header().Set("Content-Type", "text/plain;version=0.0.4;charset=utf-8")
sort.Slice(s.kvs, func(i, j int) bool {
return s.kvs[i].sortKey < s.kvs[j].sortKey s := sortedKVsPool.Get().(*sortedKVs)
}) defer sortedKVsPool.Put(s)
for _, e := range s.kvs { s.kvs = s.kvs[:0]
writePromExpVar(w, "", e.KeyValue) expvarDoFunc(func(kv expvar.KeyValue) {
s.kvs = append(s.kvs, sortedKV{kv, removeTypePrefixes(kv.Key)})
})
sort.Slice(s.kvs, func(i, j int) bool {
return s.kvs[i].sortKey < s.kvs[j].sortKey
})
for _, e := range s.kvs {
writePromExpVar(w, "", e.KeyValue)
}
} }
} }

View File

@@ -0,0 +1,40 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package usermetrics provides a container and handler
// for user-facing metrics.
package usermetrics
import (
"expvar"
"net/http"
"tailscale.com/metrics"
"tailscale.com/tsweb/varz"
)
var vars expvar.Map
// NewMultiLabelMap creates and register a new
// MultiLabelMap[T] variable with the given name and returns it.
// The variable is registered with the userfacing metrics package.
//
// Note that usermetrics are not protected against duplicate
// metrics name. It is the caller's responsibility to ensure that
// the name is unique.
func NewMultiLabelMap[T comparable](name string, promType, helpText string) *metrics.MultiLabelMap[T] {
m := &metrics.MultiLabelMap[T]{
Type: promType,
Help: helpText,
}
var zero T
_ = metrics.LabelString(zero) // panic early if T is invalid
vars.Set(name, m)
return m
}
// Handler returns a varz.Handler that serves the userfacing expvar contained
// in this package.
func Handler(w http.ResponseWriter, r *http.Request) {
varz.ExpvarDoHandler(vars.Do)(w, r)
}