metrics: add a LabelMap type for variables with 1 label dimension.

This lets us publish sets of vars that are breakdowns along one
dimension in a format that Prometheus and Grafana natively know
how to do useful things with.

Signed-off-by: David Anderson <dave@natulte.net>
crawshaw/magicsock
David Anderson 4 years ago committed by Dave Anderson
parent eac62ec5ff
commit f192c05413

@ -28,6 +28,7 @@ import (
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
"tailscale.com/logpolicy"
"tailscale.com/metrics"
"tailscale.com/stun"
"tailscale.com/tsweb"
"tailscale.com/types/key"
@ -213,51 +214,60 @@ func serveSTUN() {
log.Fatalf("failed to open STUN listener: %v", err)
}
log.Printf("running STUN server on %v", pc.LocalAddr())
var (
stunReadErrors = expvar.NewInt("stun_read_error")
stunWriteErrors = expvar.NewInt("stun_write_error")
stunReadNotSTUN = expvar.NewInt("stun_read_not_stun")
stunReadNotSTUNValid = expvar.NewInt("stun_read_not_stun_valid")
stunReadIPv4 = expvar.NewInt("stun_read_ipv4")
stunReadIPv6 = expvar.NewInt("stun_read_ipv6")
stunWrite = expvar.NewInt("stun_write")
stats = new(metrics.Set)
stunDisposition = &metrics.LabelMap{Label: "disposition"}
stunAddrFamily = &metrics.LabelMap{Label: "family"}
stunReadError = stunDisposition.Get("read_error")
stunNotSTUN = stunDisposition.Get("not_stun")
stunWriteError = stunDisposition.Get("write_error")
stunSuccess = stunDisposition.Get("success")
stunIPv4 = stunAddrFamily.Get("ipv4")
stunIPv6 = stunAddrFamily.Get("ipv6")
)
stats.Set("counter_requests", stunDisposition)
stats.Set("counter_addrfamily", stunAddrFamily)
expvar.Publish("stun", stats)
var buf [64 << 10]byte
for {
n, addr, err := pc.ReadFrom(buf[:])
if err != nil {
log.Printf("STUN ReadFrom: %v", err)
time.Sleep(time.Second)
stunReadErrors.Add(1)
stunReadError.Add(1)
continue
}
ua, ok := addr.(*net.UDPAddr)
if !ok {
log.Printf("STUN unexpected address %T %v", addr, addr)
stunReadErrors.Add(1)
stunReadError.Add(1)
continue
}
pkt := buf[:n]
if !stun.Is(pkt) {
stunReadNotSTUN.Add(1)
stunNotSTUN.Add(1)
continue
}
txid, err := stun.ParseBindingRequest(pkt)
if err != nil {
stunReadNotSTUNValid.Add(1)
stunNotSTUN.Add(1)
continue
}
if ua.IP.To4() != nil {
stunReadIPv4.Add(1)
stunIPv4.Add(1)
} else {
stunReadIPv6.Add(1)
stunIPv6.Add(1)
}
res := stun.Response(txid, ua.IP, uint16(ua.Port))
_, err = pc.WriteTo(res, addr)
if err != nil {
stunWriteErrors.Add(1)
stunWriteError.Add(1)
} else {
stunWrite.Add(1)
stunSuccess.Add(1)
}
}
}

@ -8,7 +8,7 @@ package metrics
import "expvar"
// Map is a string-to-Var map variable that satisfies the expvar.Var
// 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
@ -21,3 +21,22 @@ import "expvar"
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
}
// 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)
}

@ -156,26 +156,39 @@ func varzHandler(w http.ResponseWriter, r *http.Request) {
})
return
}
if strings.HasPrefix(kv.Key, "gauge_") {
switch {
case strings.HasPrefix(kv.Key, "gauge_"):
typ = "gauge"
name = prefix + strings.TrimPrefix(kv.Key, "gauge_")
} else if strings.HasPrefix(kv.Key, "counter_") {
case strings.HasPrefix(kv.Key, "counter_"):
typ = "counter"
name = prefix + strings.TrimPrefix(kv.Key, "counter_")
default:
fmt.Fprintf(w, "# skipping expvar %q with undeclared Prometheus type\n", name)
return
}
if fn, ok := kv.Value.(expvar.Func); ok {
val := fn()
switch v := kv.Value.(type) {
case expvar.Func:
val := v()
switch val.(type) {
case int64, int:
if typ != "" {
fmt.Fprintf(w, "# TYPE %s %s\n%s %v\n", name, typ, name, val)
return
}
fmt.Fprintf(w, "# TYPE %s %s\n%s %v\n", name, typ, name, val)
default:
fmt.Fprintf(w, "# skipping expvar func %q returning unknown type %T\n", name, val)
}
fmt.Fprintf(w, "# skipping expvar func %q returning unknown type %T\n", name, val)
return
case *metrics.LabelMap:
fmt.Fprintf(w, "# TYPE %s %s\n", name, typ)
// IntMap uses expvar.Map on the inside, which presorts
// keys. The output ordering is deterministic.
v.Do(func(kv expvar.KeyValue) {
fmt.Fprintf(w, "%s{%s=%s} %v\n", name, v.Label, kv.Key, kv.Value)
})
}
fmt.Fprintf(w, "# skipping func %q returning unknown type %T\n", name, kv.Value)
}
expvar.Do(func(kv expvar.KeyValue) {
dump("", kv)

Loading…
Cancel
Save