mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-21 12:28:39 +00:00
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>
This commit is contained in:
parent
eac62ec5ff
commit
f192c05413
@ -28,6 +28,7 @@ import (
|
|||||||
"tailscale.com/derp"
|
"tailscale.com/derp"
|
||||||
"tailscale.com/derp/derphttp"
|
"tailscale.com/derp/derphttp"
|
||||||
"tailscale.com/logpolicy"
|
"tailscale.com/logpolicy"
|
||||||
|
"tailscale.com/metrics"
|
||||||
"tailscale.com/stun"
|
"tailscale.com/stun"
|
||||||
"tailscale.com/tsweb"
|
"tailscale.com/tsweb"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
@ -213,51 +214,60 @@ func serveSTUN() {
|
|||||||
log.Fatalf("failed to open STUN listener: %v", err)
|
log.Fatalf("failed to open STUN listener: %v", err)
|
||||||
}
|
}
|
||||||
log.Printf("running STUN server on %v", pc.LocalAddr())
|
log.Printf("running STUN server on %v", pc.LocalAddr())
|
||||||
|
|
||||||
var (
|
var (
|
||||||
stunReadErrors = expvar.NewInt("stun_read_error")
|
stats = new(metrics.Set)
|
||||||
stunWriteErrors = expvar.NewInt("stun_write_error")
|
stunDisposition = &metrics.LabelMap{Label: "disposition"}
|
||||||
stunReadNotSTUN = expvar.NewInt("stun_read_not_stun")
|
stunAddrFamily = &metrics.LabelMap{Label: "family"}
|
||||||
stunReadNotSTUNValid = expvar.NewInt("stun_read_not_stun_valid")
|
|
||||||
stunReadIPv4 = expvar.NewInt("stun_read_ipv4")
|
stunReadError = stunDisposition.Get("read_error")
|
||||||
stunReadIPv6 = expvar.NewInt("stun_read_ipv6")
|
stunNotSTUN = stunDisposition.Get("not_stun")
|
||||||
stunWrite = expvar.NewInt("stun_write")
|
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
|
var buf [64 << 10]byte
|
||||||
for {
|
for {
|
||||||
n, addr, err := pc.ReadFrom(buf[:])
|
n, addr, err := pc.ReadFrom(buf[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("STUN ReadFrom: %v", err)
|
log.Printf("STUN ReadFrom: %v", err)
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
stunReadErrors.Add(1)
|
stunReadError.Add(1)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ua, ok := addr.(*net.UDPAddr)
|
ua, ok := addr.(*net.UDPAddr)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("STUN unexpected address %T %v", addr, addr)
|
log.Printf("STUN unexpected address %T %v", addr, addr)
|
||||||
stunReadErrors.Add(1)
|
stunReadError.Add(1)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pkt := buf[:n]
|
pkt := buf[:n]
|
||||||
if !stun.Is(pkt) {
|
if !stun.Is(pkt) {
|
||||||
stunReadNotSTUN.Add(1)
|
stunNotSTUN.Add(1)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
txid, err := stun.ParseBindingRequest(pkt)
|
txid, err := stun.ParseBindingRequest(pkt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stunReadNotSTUNValid.Add(1)
|
stunNotSTUN.Add(1)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if ua.IP.To4() != nil {
|
if ua.IP.To4() != nil {
|
||||||
stunReadIPv4.Add(1)
|
stunIPv4.Add(1)
|
||||||
} else {
|
} else {
|
||||||
stunReadIPv6.Add(1)
|
stunIPv6.Add(1)
|
||||||
}
|
}
|
||||||
res := stun.Response(txid, ua.IP, uint16(ua.Port))
|
res := stun.Response(txid, ua.IP, uint16(ua.Port))
|
||||||
_, err = pc.WriteTo(res, addr)
|
_, err = pc.WriteTo(res, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stunWriteErrors.Add(1)
|
stunWriteError.Add(1)
|
||||||
} else {
|
} else {
|
||||||
stunWrite.Add(1)
|
stunSuccess.Add(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ package metrics
|
|||||||
|
|
||||||
import "expvar"
|
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.
|
// interface.
|
||||||
//
|
//
|
||||||
// Semantically, this is mapped by tsweb's Prometheus exporter as a
|
// Semantically, this is mapped by tsweb's Prometheus exporter as a
|
||||||
@ -21,3 +21,22 @@ import "expvar"
|
|||||||
type Set struct {
|
type Set struct {
|
||||||
expvar.Map
|
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
|
return
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(kv.Key, "gauge_") {
|
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(kv.Key, "gauge_"):
|
||||||
typ = "gauge"
|
typ = "gauge"
|
||||||
name = prefix + strings.TrimPrefix(kv.Key, "gauge_")
|
name = prefix + strings.TrimPrefix(kv.Key, "gauge_")
|
||||||
} else if strings.HasPrefix(kv.Key, "counter_") {
|
|
||||||
|
case strings.HasPrefix(kv.Key, "counter_"):
|
||||||
typ = "counter"
|
typ = "counter"
|
||||||
name = prefix + strings.TrimPrefix(kv.Key, "counter_")
|
name = prefix + strings.TrimPrefix(kv.Key, "counter_")
|
||||||
}
|
|
||||||
if fn, ok := kv.Value.(expvar.Func); ok {
|
default:
|
||||||
val := fn()
|
fmt.Fprintf(w, "# skipping expvar %q with undeclared Prometheus type\n", name)
|
||||||
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, "# skipping expvar func %q returning unknown type %T\n", name, val)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "# skipping func %q returning unknown type %T\n", name, kv.Value)
|
|
||||||
|
switch v := kv.Value.(type) {
|
||||||
|
case expvar.Func:
|
||||||
|
val := v()
|
||||||
|
switch val.(type) {
|
||||||
|
case int64, int:
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
expvar.Do(func(kv expvar.KeyValue) {
|
expvar.Do(func(kv expvar.KeyValue) {
|
||||||
dump("", kv)
|
dump("", kv)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user