tailscale/util/usermetric/usermetric.go
Anton Tolchanov b4f46c31bb
Some checks failed
CI / windows (push) Has been cancelled
CI / vm (push) Has been cancelled
checklocks / checklocks (push) Successful in 1m0s
Dockerfile build / deploy (push) Successful in 3m53s
CI / race-root-integration (1/4) (push) Failing after 2m36s
CodeQL / Analyze (go) (push) Failing after 9m13s
CI / race-root-integration (2/4) (push) Successful in 2m33s
CI / race-root-integration (3/4) (push) Successful in 3m46s
CI / race-root-integration (4/4) (push) Failing after 3m43s
CI / test (-race, amd64, 1/3) (push) Failing after 18m39s
CI / test (-coverprofile=/tmp/coverage.out, amd64) (push) Failing after 19m19s
CI / test (-race, amd64, 2/3) (push) Failing after 17m8s
CI / test (-race, amd64, 3/3) (push) Failing after 17m23s
CI / privileged (push) Failing after 30s
CI / race-build (push) Successful in 10m14s
CI / test (386) (push) Failing after 15m46s
CI / cross (386, linux) (push) Successful in 16m47s
CI / cross (amd64, darwin) (push) Successful in 16m50s
CI / cross (amd64, freebsd) (push) Successful in 16m45s
CI / cross (amd64, openbsd) (push) Successful in 16m44s
CI / cross (amd64, windows) (push) Successful in 16m18s
CI / cross (arm, 5, linux) (push) Successful in 16m34s
CI / cross (arm, 7, linux) (push) Successful in 16m27s
CI / cross (arm64, darwin) (push) Successful in 17m30s
CI / cross (arm64, linux) (push) Successful in 16m25s
CI / cross (arm64, windows) (push) Successful in 15m45s
CI / ios (push) Successful in 1m36s
CI / cross (loong64, linux) (push) Successful in 16m30s
CI / crossmin (amd64, plan9) (push) Successful in 10m38s
CI / android (push) Successful in 1m26s
CI / crossmin (ppc64, aix) (push) Successful in 10m43s
CI / tailscale_go (push) Successful in 45s
CI / fuzz (push) Has been skipped
CI / notify_slack (push) Has been cancelled
CI / check_mergeability (push) Has been cancelled
CI / depaware (push) Successful in 1m1s
CI / go_generate (push) Successful in 2m11s
CI / go_mod_tidy (push) Successful in 59s
CI / licenses (push) Successful in 9s
CI / staticcheck (386, windows) (push) Failing after 1m15s
CI / staticcheck (amd64, darwin) (push) Failing after 1m19s
CI / staticcheck (amd64, linux) (push) Failing after 1m19s
CI / staticcheck (amd64, windows) (push) Failing after 1m13s
CI / wasm (push) Successful in 27m59s
wgengine/magicsock: export packet drop metric for outbound errors
This required sharing the dropped packet metric between two packages
(tstun and magicsock), so I've moved its definition to util/usermetric.

Updates tailscale/corp#22075

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
2024-10-31 08:33:24 +00:00

109 lines
2.7 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package usermetric provides a container and handler
// for user-facing metrics.
package usermetric
import (
"expvar"
"fmt"
"io"
"net/http"
"strings"
"tailscale.com/metrics"
"tailscale.com/tsweb/varz"
)
// Registry tracks user-facing metrics of various Tailscale subsystems.
type Registry struct {
vars expvar.Map
// m contains common metrics owned by the registry.
m Metrics
}
// NewMultiLabelMapWithRegistry 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 usermetric are not protected against duplicate
// metrics name. It is the caller's responsibility to ensure that
// the name is unique.
func NewMultiLabelMapWithRegistry[T comparable](m *Registry, name string, promType, helpText string) *metrics.MultiLabelMap[T] {
ml := &metrics.MultiLabelMap[T]{
Type: promType,
Help: helpText,
}
var zero T
_ = metrics.LabelString(zero) // panic early if T is invalid
m.vars.Set(name, ml)
return ml
}
// Gauge is a gauge metric with no labels.
type Gauge struct {
m *expvar.Float
help string
}
// NewGauge creates and register a new gauge metric with the given name and help text.
func (r *Registry) NewGauge(name, help string) *Gauge {
g := &Gauge{&expvar.Float{}, help}
r.vars.Set(name, g)
return g
}
// Set sets the gauge to the given value.
func (g *Gauge) Set(v float64) {
if g == nil {
return
}
g.m.Set(v)
}
// String returns the string of the underlying expvar.Float.
// This satisfies the expvar.Var interface.
func (g *Gauge) String() string {
if g == nil {
return ""
}
return g.m.String()
}
// WritePrometheus writes the gauge metric in Prometheus format to the given writer.
// This satisfies the varz.PrometheusWriter interface.
func (g *Gauge) WritePrometheus(w io.Writer, name string) {
io.WriteString(w, "# TYPE ")
io.WriteString(w, name)
io.WriteString(w, " gauge\n")
if g.help != "" {
io.WriteString(w, "# HELP ")
io.WriteString(w, name)
io.WriteString(w, " ")
io.WriteString(w, g.help)
io.WriteString(w, "\n")
}
io.WriteString(w, name)
fmt.Fprintf(w, " %v\n", g.m.Value())
}
// Handler returns a varz.Handler that serves the userfacing expvar contained
// in this package.
func (r *Registry) Handler(w http.ResponseWriter, req *http.Request) {
varz.ExpvarDoHandler(r.vars.Do)(w, req)
}
// String returns the string representation of all the metrics and their
// values in the registry. It is useful for debugging.
func (r *Registry) String() string {
var sb strings.Builder
r.vars.Do(func(kv expvar.KeyValue) {
fmt.Fprintf(&sb, "%s: %v\n", kv.Key, kv.Value)
})
return sb.String()
}