Update dependencies
This commit is contained in:
41
vendor/tailscale.com/metrics/fds_linux.go
generated
vendored
Normal file
41
vendor/tailscale.com/metrics/fds_linux.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"sync"
|
||||
|
||||
"go4.org/mem"
|
||||
"tailscale.com/util/dirwalk"
|
||||
)
|
||||
|
||||
// counter is a reusable counter for counting file descriptors.
|
||||
type counter struct {
|
||||
n int
|
||||
|
||||
// cb is the (*counter).count method value. Creating it allocates,
|
||||
// so we have to save it away and use a sync.Pool to keep currentFDs
|
||||
// amortized alloc-free.
|
||||
cb func(name mem.RO, de fs.DirEntry) error
|
||||
}
|
||||
|
||||
var counterPool = &sync.Pool{New: func() any {
|
||||
c := new(counter)
|
||||
c.cb = c.count
|
||||
return c
|
||||
}}
|
||||
|
||||
func (c *counter) count(name mem.RO, de fs.DirEntry) error {
|
||||
c.n++
|
||||
return nil
|
||||
}
|
||||
|
||||
func currentFDs() int {
|
||||
c := counterPool.Get().(*counter)
|
||||
defer counterPool.Put(c)
|
||||
c.n = 0
|
||||
dirwalk.WalkShallow(mem.S("/proc/self/fd"), c.cb)
|
||||
return c.n
|
||||
}
|
||||
8
vendor/tailscale.com/metrics/fds_notlinux.go
generated
vendored
Normal file
8
vendor/tailscale.com/metrics/fds_notlinux.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !linux
|
||||
|
||||
package metrics
|
||||
|
||||
func currentFDs() int { return 0 }
|
||||
163
vendor/tailscale.com/metrics/metrics.go
generated
vendored
Normal file
163
vendor/tailscale.com/metrics/metrics.go
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package metrics contains expvar & Prometheus types and code used by
|
||||
// Tailscale for monitoring.
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"expvar"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Set is a string-to-Var map variable that satisfies the expvar.Var
|
||||
// interface.
|
||||
//
|
||||
// Semantically, this is mapped by tsweb's Prometheus exporter as a
|
||||
// collection of unrelated variables exported with a common prefix.
|
||||
//
|
||||
// This lets us have tsweb recognize *expvar.Map for different
|
||||
// purposes in the future. (Or perhaps all uses of expvar.Map will
|
||||
// require explicit types like this one, declaring how we want tsweb
|
||||
// to export it to Prometheus.)
|
||||
type Set struct {
|
||||
expvar.Map
|
||||
}
|
||||
|
||||
// LabelMap is a string-to-Var map variable that satisfies the
|
||||
// expvar.Var interface.
|
||||
//
|
||||
// Semantically, this is mapped by tsweb's Prometheus exporter as a
|
||||
// collection of variables with the same name, with a varying label
|
||||
// value. Use this to export things that are intuitively breakdowns
|
||||
// into different buckets.
|
||||
type LabelMap struct {
|
||||
Label string
|
||||
expvar.Map
|
||||
}
|
||||
|
||||
// SetInt64 sets the *Int value stored under the given map key.
|
||||
func (m *LabelMap) SetInt64(key string, v int64) {
|
||||
m.Get(key).Set(v)
|
||||
}
|
||||
|
||||
// Get returns a direct pointer to the expvar.Int for key, creating it
|
||||
// if necessary.
|
||||
func (m *LabelMap) Get(key string) *expvar.Int {
|
||||
m.Add(key, 0)
|
||||
return m.Map.Get(key).(*expvar.Int)
|
||||
}
|
||||
|
||||
// GetIncrFunc returns a function that increments the expvar.Int named by key.
|
||||
//
|
||||
// Most callers should not need this; it exists to satisfy an
|
||||
// interface elsewhere.
|
||||
func (m *LabelMap) GetIncrFunc(key string) func(delta int64) {
|
||||
return m.Get(key).Add
|
||||
}
|
||||
|
||||
// GetFloat returns a direct pointer to the expvar.Float for key, creating it
|
||||
// if necessary.
|
||||
func (m *LabelMap) GetFloat(key string) *expvar.Float {
|
||||
m.AddFloat(key, 0.0)
|
||||
return m.Map.Get(key).(*expvar.Float)
|
||||
}
|
||||
|
||||
// CurrentFDs reports how many file descriptors are currently open.
|
||||
//
|
||||
// It only works on Linux. It returns zero otherwise.
|
||||
func CurrentFDs() int {
|
||||
return currentFDs()
|
||||
}
|
||||
|
||||
// Histogram is a histogram of values.
|
||||
// It should be created with NewHistogram.
|
||||
type Histogram struct {
|
||||
// buckets is a list of bucket boundaries, in increasing order.
|
||||
buckets []float64
|
||||
|
||||
// bucketStrings is a list of the same buckets, but as strings.
|
||||
// This are allocated once at creation time by NewHistogram.
|
||||
bucketStrings []string
|
||||
|
||||
bucketVars []expvar.Int
|
||||
sum expvar.Float
|
||||
count expvar.Int
|
||||
}
|
||||
|
||||
// NewHistogram returns a new histogram that reports to the given
|
||||
// expvar map under the given name.
|
||||
//
|
||||
// The buckets are the boundaries of the histogram buckets, in
|
||||
// increasing order. The last bucket is +Inf.
|
||||
func NewHistogram(buckets []float64) *Histogram {
|
||||
if !slices.IsSorted(buckets) {
|
||||
panic("buckets must be sorted")
|
||||
}
|
||||
labels := make([]string, len(buckets))
|
||||
for i, b := range buckets {
|
||||
labels[i] = fmt.Sprintf("%v", b)
|
||||
}
|
||||
h := &Histogram{
|
||||
buckets: buckets,
|
||||
bucketStrings: labels,
|
||||
bucketVars: make([]expvar.Int, len(buckets)),
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// Observe records a new observation in the histogram.
|
||||
func (h *Histogram) Observe(v float64) {
|
||||
h.sum.Add(v)
|
||||
h.count.Add(1)
|
||||
for i, b := range h.buckets {
|
||||
if v <= b {
|
||||
h.bucketVars[i].Add(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a JSON representation of the histogram.
|
||||
// This is used to satisfy the expvar.Var interface.
|
||||
func (h *Histogram) String() string {
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "{")
|
||||
first := true
|
||||
h.Do(func(kv expvar.KeyValue) {
|
||||
if !first {
|
||||
fmt.Fprintf(&b, ",")
|
||||
}
|
||||
fmt.Fprintf(&b, "%q: ", kv.Key)
|
||||
if kv.Value != nil {
|
||||
fmt.Fprintf(&b, "%v", kv.Value)
|
||||
} else {
|
||||
fmt.Fprint(&b, "null")
|
||||
}
|
||||
first = false
|
||||
})
|
||||
fmt.Fprintf(&b, ",\"sum\": %v", &h.sum)
|
||||
fmt.Fprintf(&b, ",\"count\": %v", &h.count)
|
||||
fmt.Fprintf(&b, "}")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Do calls f for each bucket in the histogram.
|
||||
func (h *Histogram) Do(f func(expvar.KeyValue)) {
|
||||
for i := range h.bucketVars {
|
||||
f(expvar.KeyValue{Key: h.bucketStrings[i], Value: &h.bucketVars[i]})
|
||||
}
|
||||
f(expvar.KeyValue{Key: "+Inf", Value: &h.count})
|
||||
}
|
||||
|
||||
// PromExport writes the histogram to w in Prometheus exposition format.
|
||||
func (h *Histogram) PromExport(w io.Writer, name string) {
|
||||
fmt.Fprintf(w, "# TYPE %s histogram\n", name)
|
||||
h.Do(func(kv expvar.KeyValue) {
|
||||
fmt.Fprintf(w, "%s_bucket{le=%q} %v\n", name, kv.Key, kv.Value)
|
||||
})
|
||||
fmt.Fprintf(w, "%s_sum %v\n", name, &h.sum)
|
||||
fmt.Fprintf(w, "%s_count %v\n", name, &h.count)
|
||||
}
|
||||
297
vendor/tailscale.com/metrics/multilabelmap.go
generated
vendored
Normal file
297
vendor/tailscale.com/metrics/multilabelmap.go
generated
vendored
Normal file
@@ -0,0 +1,297 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"expvar"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MultiLabelMap is a struct-value-to-Var map variable that satisfies the
|
||||
// [expvar.Var] interface but also allows for multiple Prometheus labels to be
|
||||
// associated with each value.
|
||||
//
|
||||
// T must be a struct type with scalar fields. The struct field names
|
||||
// (lowercased) are used as the labels, unless a "prom" struct tag is present.
|
||||
// The struct fields must all be strings, and the string values must be valid
|
||||
// Prometheus label values without requiring quoting.
|
||||
type MultiLabelMap[T comparable] struct {
|
||||
Type string // optional Prometheus type ("counter", "gauge")
|
||||
Help string // optional Prometheus help string
|
||||
|
||||
m sync.Map // map[T]expvar.Var
|
||||
|
||||
mu sync.RWMutex
|
||||
sorted []labelsAndValue[T] // by labels string, to match expvar.Map + for aesthetics in output
|
||||
}
|
||||
|
||||
// NewMultiLabelMap creates and publishes (via expvar.Publish) a new
|
||||
// MultiLabelMap[T] variable with the given name and returns it.
|
||||
func NewMultiLabelMap[T comparable](name string, promType, helpText string) *MultiLabelMap[T] {
|
||||
m := &MultiLabelMap[T]{
|
||||
Type: promType,
|
||||
Help: helpText,
|
||||
}
|
||||
var zero T
|
||||
_ = LabelString(zero) // panic early if T is invalid
|
||||
expvar.Publish(name, m)
|
||||
return m
|
||||
}
|
||||
|
||||
type labelsAndValue[T comparable] struct {
|
||||
key T
|
||||
labels string // Prometheus-formatted {label="value",label="value"} string
|
||||
val expvar.Var
|
||||
}
|
||||
|
||||
// LabelString returns a Prometheus-formatted label string for the given key.
|
||||
// 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)
|
||||
t := rv.Type()
|
||||
if t.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("MultiLabelMap must use keys of type struct; got %v", t))
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString("{")
|
||||
|
||||
for i := range t.NumField() {
|
||||
if i > 0 {
|
||||
sb.WriteString(",")
|
||||
}
|
||||
ft := t.Field(i)
|
||||
label := ft.Tag.Get("prom")
|
||||
if label == "" {
|
||||
label = strings.ToLower(ft.Name)
|
||||
}
|
||||
fv := rv.Field(i)
|
||||
switch fv.Kind() {
|
||||
case reflect.String:
|
||||
fmt.Fprintf(&sb, "%s=%q", label, fv.String())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
fmt.Fprintf(&sb, "%s=\"%d\"", label, fv.Int())
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
fmt.Fprintf(&sb, "%s=\"%d\"", label, fv.Uint())
|
||||
case reflect.Bool:
|
||||
fmt.Fprintf(&sb, "%s=\"%v\"", label, fv.Bool())
|
||||
default:
|
||||
panic(fmt.Sprintf("MultiLabelMap key field %q has unsupported type %v", ft.Name, fv.Type()))
|
||||
}
|
||||
}
|
||||
sb.WriteString("}")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// KeyValue represents a single entry in a [MultiLabelMap].
|
||||
type KeyValue[T comparable] struct {
|
||||
Key T
|
||||
Value expvar.Var
|
||||
}
|
||||
|
||||
func (v *MultiLabelMap[T]) String() string {
|
||||
// NOTE: This has to be valid JSON because it's used by expvar.
|
||||
return `"MultiLabelMap"`
|
||||
}
|
||||
|
||||
// WritePrometheus writes v to w in Prometheus exposition format.
|
||||
// The name argument is the metric name.
|
||||
func (v *MultiLabelMap[T]) WritePrometheus(w io.Writer, name string) {
|
||||
if v.Type != "" {
|
||||
io.WriteString(w, "# TYPE ")
|
||||
io.WriteString(w, name)
|
||||
io.WriteString(w, " ")
|
||||
io.WriteString(w, v.Type)
|
||||
io.WriteString(w, "\n")
|
||||
}
|
||||
if v.Help != "" {
|
||||
io.WriteString(w, "# HELP ")
|
||||
io.WriteString(w, name)
|
||||
io.WriteString(w, " ")
|
||||
io.WriteString(w, v.Help)
|
||||
io.WriteString(w, "\n")
|
||||
}
|
||||
v.mu.RLock()
|
||||
defer v.mu.RUnlock()
|
||||
|
||||
for _, kv := range v.sorted {
|
||||
io.WriteString(w, name)
|
||||
io.WriteString(w, kv.labels)
|
||||
switch v := kv.val.(type) {
|
||||
case *expvar.Int:
|
||||
fmt.Fprintf(w, " %d\n", v.Value())
|
||||
case *expvar.Float:
|
||||
fmt.Fprintf(w, " %v\n", v.Value())
|
||||
default:
|
||||
fmt.Fprintf(w, " %s\n", kv.val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Init removes all keys from the map.
|
||||
//
|
||||
// Think of it as "Reset", but it's named Init to match expvar.Map.Init.
|
||||
func (v *MultiLabelMap[T]) Init() *MultiLabelMap[T] {
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
v.sorted = nil
|
||||
v.m.Range(func(k, _ any) bool {
|
||||
v.m.Delete(k)
|
||||
return true
|
||||
})
|
||||
return v
|
||||
}
|
||||
|
||||
// addKeyLocked updates the sorted list of keys in v.keys.
|
||||
//
|
||||
// v.mu must be held.
|
||||
func (v *MultiLabelMap[T]) addKeyLocked(key T, val expvar.Var) {
|
||||
ls := LabelString(key)
|
||||
|
||||
ent := labelsAndValue[T]{key, ls, val}
|
||||
// Using insertion sort to place key into the already-sorted v.keys.
|
||||
i := sort.Search(len(v.sorted), func(i int) bool {
|
||||
return v.sorted[i].labels >= ls
|
||||
})
|
||||
if i >= len(v.sorted) {
|
||||
v.sorted = append(v.sorted, ent)
|
||||
} else if v.sorted[i].key == key {
|
||||
v.sorted[i].val = val
|
||||
} else {
|
||||
var zero labelsAndValue[T]
|
||||
v.sorted = append(v.sorted, zero)
|
||||
copy(v.sorted[i+1:], v.sorted[i:])
|
||||
v.sorted[i] = ent
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the expvar for the given key, or nil if it doesn't exist.
|
||||
func (v *MultiLabelMap[T]) Get(key T) expvar.Var {
|
||||
i, _ := v.m.Load(key)
|
||||
av, _ := i.(expvar.Var)
|
||||
return av
|
||||
}
|
||||
|
||||
func newInt() expvar.Var { return new(expvar.Int) }
|
||||
func newFloat() expvar.Var { return new(expvar.Float) }
|
||||
|
||||
// getOrFill returns the expvar.Var for the given key, atomically creating it
|
||||
// once (for all callers) with fill if it doesn't exist.
|
||||
func (v *MultiLabelMap[T]) getOrFill(key T, fill func() expvar.Var) expvar.Var {
|
||||
if v := v.Get(key); v != nil {
|
||||
return v
|
||||
}
|
||||
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
|
||||
if v := v.Get(key); v != nil {
|
||||
return v
|
||||
}
|
||||
nv := fill()
|
||||
v.addKeyLocked(key, nv)
|
||||
v.m.Store(key, nv)
|
||||
return nv
|
||||
}
|
||||
|
||||
// Set sets key to val.
|
||||
//
|
||||
// This is not optimized for highly concurrent usage; it's presumed to only be
|
||||
// used rarely, at startup.
|
||||
func (v *MultiLabelMap[T]) Set(key T, val expvar.Var) {
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
v.addKeyLocked(key, val)
|
||||
v.m.Store(key, val)
|
||||
}
|
||||
|
||||
// SetInt sets val to the *[expvar.Int] value stored under the given map key,
|
||||
// creating it if it doesn't exist yet.
|
||||
// It does nothing if key exists but is of the wrong type.
|
||||
func (v *MultiLabelMap[T]) SetInt(key T, val int64) {
|
||||
// Set to Int; ignore otherwise.
|
||||
if iv, ok := v.getOrFill(key, newInt).(*expvar.Int); ok {
|
||||
iv.Set(val)
|
||||
}
|
||||
}
|
||||
|
||||
// SetFloat sets val to the *[expvar.Float] value stored under the given map key,
|
||||
// creating it if it doesn't exist yet.
|
||||
// It does nothing if key exists but is of the wrong type.
|
||||
func (v *MultiLabelMap[T]) SetFloat(key T, val float64) {
|
||||
// Set to Float; ignore otherwise.
|
||||
if iv, ok := v.getOrFill(key, newFloat).(*expvar.Float); ok {
|
||||
iv.Set(val)
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds delta to the *[expvar.Int] value stored under the given map key,
|
||||
// creating it if it doesn't exist yet.
|
||||
// It does nothing if key exists but is of the wrong type.
|
||||
func (v *MultiLabelMap[T]) Add(key T, delta int64) {
|
||||
// Add to Int; ignore otherwise.
|
||||
if iv, ok := v.getOrFill(key, newInt).(*expvar.Int); ok {
|
||||
iv.Add(delta)
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds delta to the *[expvar.Float] value stored under the given map key,
|
||||
// creating it if it doesn't exist yet.
|
||||
// It does nothing if key exists but is of the wrong type.
|
||||
func (v *MultiLabelMap[T]) AddFloat(key T, delta float64) {
|
||||
// Add to Float; ignore otherwise.
|
||||
if iv, ok := v.getOrFill(key, newFloat).(*expvar.Float); ok {
|
||||
iv.Add(delta)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete deletes the given key from the map.
|
||||
//
|
||||
// This is not optimized for highly concurrent usage; it's presumed to only be
|
||||
// used rarely, at startup.
|
||||
func (v *MultiLabelMap[T]) Delete(key T) {
|
||||
ls := LabelString(key)
|
||||
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
|
||||
// Using insertion sort to place key into the already-sorted v.keys.
|
||||
i := sort.Search(len(v.sorted), func(i int) bool {
|
||||
return v.sorted[i].labels >= ls
|
||||
})
|
||||
if i < len(v.sorted) && v.sorted[i].key == key {
|
||||
v.sorted = append(v.sorted[:i], v.sorted[i+1:]...)
|
||||
v.m.Delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
// Do calls f for each entry in the map.
|
||||
// The map is locked during the iteration,
|
||||
// but existing entries may be concurrently updated.
|
||||
func (v *MultiLabelMap[T]) Do(f func(KeyValue[T])) {
|
||||
v.mu.RLock()
|
||||
defer v.mu.RUnlock()
|
||||
for _, e := range v.sorted {
|
||||
f(KeyValue[T]{e.key, e.val})
|
||||
}
|
||||
}
|
||||
|
||||
// ResetAllForTest resets all values for metrics to zero.
|
||||
// Should only be used in tests.
|
||||
func (v *MultiLabelMap[T]) ResetAllForTest() {
|
||||
v.Do(func(kv KeyValue[T]) {
|
||||
switch v := kv.Value.(type) {
|
||||
case *expvar.Int:
|
||||
v.Set(0)
|
||||
case *expvar.Float:
|
||||
v.Set(0)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user