From 48d7ee1c6a99fd4e240ad487a6cec0333e1b47fd Mon Sep 17 00:00:00 2001 From: David Anderson Date: Wed, 1 Apr 2020 16:50:02 -0700 Subject: [PATCH] cmd/microproxy: adjust to export node stats and a Go expvar server's stats. This is a temporary specialization to what tailscale prod needs right now, it'll go back to something more generic later. --- cmd/microproxy/microproxy.go | 55 ++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/cmd/microproxy/microproxy.go b/cmd/microproxy/microproxy.go index 2afe4a194..f73dad9b0 100644 --- a/cmd/microproxy/microproxy.go +++ b/cmd/microproxy/microproxy.go @@ -9,8 +9,10 @@ package main import ( "crypto/tls" + "encoding/json" "flag" "fmt" + "io" "io/ioutil" "log" "net/http" @@ -29,7 +31,8 @@ var ( certdir = flag.String("certdir", "", "directory to borrow LetsEncrypt certificates from") hostname = flag.String("hostname", "", "hostname to serve") logCollection = flag.String("logcollection", "", "If non-empty, logtail collection to log to") - target = flag.String("target", "", "URL to proxy to (usually http://localhost:...") + nodeExporter = flag.String("node-exporter", "http://localhost:9100", "URL of the local prometheus node exporter") + goVarsURL = flag.String("go-vars-url", "http://localhost:8383/debug/vars", "URL of a local Go server's /debug/vars endpoint") ) func main() { @@ -39,14 +42,20 @@ func main() { logpolicy.New(*logCollection) } - u, err := url.Parse(*target) + ne, err := url.Parse(*nodeExporter) if err != nil { - log.Fatalf("Couldn't parse URL %q: %v", *target, err) + log.Fatalf("Couldn't parse URL %q: %v", *nodeExporter, err) } - proxy := httputil.NewSingleHostReverseProxy(u) + proxy := httputil.NewSingleHostReverseProxy(ne) proxy.FlushInterval = time.Second + + if _, err = url.Parse(*goVarsURL); err != nil { + log.Fatalf("Couldn't parse URL %q: %v", *goVarsURL, err) + } + mux := tsweb.NewMux(http.HandlerFunc(debugHandler)) - mux.Handle("/", tsweb.Protected(proxy)) + mux.Handle("/metrics", tsweb.Protected(proxy)) + mux.Handle("/varz", tsweb.Protected(tsweb.StdHandler(&goVarsHandler{*goVarsURL}, log.Printf))) ch := &certHolder{ hostname: *hostname, @@ -66,6 +75,42 @@ func main() { } } +type goVarsHandler struct { + url string +} + +func promPrint(w io.Writer, prefix string, obj map[string]interface{}) { + for k, i := range obj { + if prefix != "" { + k = prefix + "_" + k + } + switch v := i.(type) { + case map[string]interface{}: + promPrint(w, k, v) + case float64: + fmt.Fprintf(w, "%s %f\n", k, v) + default: + fmt.Fprintf(w, "# Skipping key %q, unhandled type %T\n", k, v) + } + } +} + +func (h *goVarsHandler) ServeHTTPErr(w http.ResponseWriter, r *http.Request) error { + resp, err := http.Get(h.url) + if err != nil { + return tsweb.Error(http.StatusInternalServerError, "fetch failed", err) + } + defer resp.Body.Close() + var mon map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&mon); err != nil { + return tsweb.Error(http.StatusInternalServerError, "fetch failed", err) + } + + w.WriteHeader(http.StatusOK) + promPrint(w, "", mon) + return nil +} + // certHolder loads and caches a TLS certificate from disk, reloading // it every hour. type certHolder struct {