mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-01 17:49:02 +00:00
tsweb, derp: add expvar http.Handler for Prometheus's format
And add some opinions. Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
89a2c3eb04
commit
e371520cc5
@@ -18,8 +18,12 @@ import (
|
||||
"time"
|
||||
|
||||
"tailscale.com/interfaces"
|
||||
"tailscale.com/metrics"
|
||||
)
|
||||
|
||||
// DevMode controls whether extra output in shown, for when the binary is being run in dev mode.
|
||||
var DevMode bool
|
||||
|
||||
// NewMux returns a new ServeMux with debugHandler registered (and protected) at /debug/.
|
||||
func NewMux(debugHandler http.Handler) *http.ServeMux {
|
||||
mux := http.NewServeMux()
|
||||
@@ -29,9 +33,10 @@ func NewMux(debugHandler http.Handler) *http.ServeMux {
|
||||
}
|
||||
|
||||
func RegisterCommonDebug(mux *http.ServeMux) {
|
||||
expvar.Publish("uptime", uptimeVar{})
|
||||
expvar.Publish("counter_uptime_sec", expvar.Func(func() interface{} { return int64(Uptime().Seconds()) }))
|
||||
mux.Handle("/debug/pprof/", Protected(http.DefaultServeMux)) // to net/http/pprof
|
||||
mux.Handle("/debug/vars", Protected(http.DefaultServeMux)) // to expvar
|
||||
mux.Handle("/debug/varz", Protected(http.HandlerFunc(varzHandler)))
|
||||
}
|
||||
|
||||
func DefaultCertDir(leafDir string) string {
|
||||
@@ -69,7 +74,12 @@ func AllowDebugAccess(r *http.Request) bool {
|
||||
func Protected(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !AllowDebugAccess(r) {
|
||||
http.Error(w, "debug access denied", http.StatusForbidden)
|
||||
msg := "debug access denied"
|
||||
if DevMode {
|
||||
ipStr, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||
msg += fmt.Sprintf("; to permit access, set ALLOW_DEBUG_IP=%v", ipStr)
|
||||
}
|
||||
http.Error(w, msg, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
@@ -80,10 +90,6 @@ var timeStart = time.Now()
|
||||
|
||||
func Uptime() time.Duration { return time.Since(timeStart).Round(time.Second) }
|
||||
|
||||
type uptimeVar struct{}
|
||||
|
||||
func (uptimeVar) String() string { return fmt.Sprint(int64(Uptime().Seconds())) }
|
||||
|
||||
// Port80Handler is the handler to be given to
|
||||
// autocert.Manager.HTTPHandler. The inner handler is the mux
|
||||
// returned by NewMux containing registered /debug handlers.
|
||||
@@ -114,3 +120,64 @@ func stripPort(hostport string) string {
|
||||
}
|
||||
return net.JoinHostPort(host, "443")
|
||||
}
|
||||
|
||||
// varzHandler is an HTTP handler to write expvar values into the
|
||||
// prometheus export format:
|
||||
//
|
||||
// https://github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md
|
||||
//
|
||||
// It makes the following assumptions:
|
||||
//
|
||||
// * *expvar.Int are counters.
|
||||
// * a *tailscale/metrics.Set is descended into, joining keys with
|
||||
// underscores. So use underscores as your metric names.
|
||||
// * an expvar named starting with "gauge_" or "counter_" is of that
|
||||
// Prometheus type, and has that prefix stripped.
|
||||
// * anything else is untyped and thus not exported.
|
||||
// * expvar.Func can return an int or int64 (for now) and anything else
|
||||
// is not exported.
|
||||
//
|
||||
// This will evolve over time, or perhaps be replaced.
|
||||
func varzHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; version=0.0.4")
|
||||
|
||||
var dump func(prefix string, kv expvar.KeyValue)
|
||||
dump = func(prefix string, kv expvar.KeyValue) {
|
||||
name := prefix + kv.Key
|
||||
var typ string
|
||||
switch v := kv.Value.(type) {
|
||||
case *expvar.Int:
|
||||
// Fast path for common value type.
|
||||
fmt.Fprintf(w, "# TYPE %s counter\n%s %v\n", name, name, v.Value())
|
||||
return
|
||||
case *metrics.Set:
|
||||
v.Do(func(kv expvar.KeyValue) {
|
||||
dump(name+"_", kv)
|
||||
})
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(kv.Key, "gauge_") {
|
||||
typ = "gauge"
|
||||
name = prefix + strings.TrimPrefix(kv.Key, "gauge_")
|
||||
} else if strings.HasPrefix(kv.Key, "counter_") {
|
||||
typ = "counter"
|
||||
name = prefix + strings.TrimPrefix(kv.Key, "counter_")
|
||||
}
|
||||
if fn, ok := kv.Value.(expvar.Func); ok {
|
||||
val := fn()
|
||||
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
|
||||
}
|
||||
fmt.Fprintf(w, "# skipping func %q returning unknown type %T\n", name, kv.Value)
|
||||
}
|
||||
expvar.Do(func(kv expvar.KeyValue) {
|
||||
dump("", kv)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user