From 10717be2cc6d5295f2695eed953dacaf5fa4af74 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 6 Apr 2016 14:46:35 +0100 Subject: [PATCH] Insturment the app with prometheus. --- common/middleware/instrument.go | 46 +++++++++++++++++++++++++++++++++ prog/app.go | 20 +++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 common/middleware/instrument.go diff --git a/common/middleware/instrument.go b/common/middleware/instrument.go new file mode 100644 index 0000000000..83fe533a3d --- /dev/null +++ b/common/middleware/instrument.go @@ -0,0 +1,46 @@ +package middleware + +import ( + "net/http" + "strconv" + "time" + + "github.com/gorilla/mux" + "github.com/prometheus/client_golang/prometheus" +) + +type Instrument struct { + RouteMatcher RouteMatcher + Duration *prometheus.SummaryVec +} + +// RouteMatcher is implemented by mux.Router. +type RouteMatcher interface { + Match(*http.Request, *mux.RouteMatch) bool +} + +func (i Instrument) Wrap(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + begin := time.Now() + interceptor := &interceptor{ResponseWriter: w, statusCode: http.StatusOK} + next.ServeHTTP(interceptor, r) + var ( + route = i.getRouteName(r) + status = strconv.Itoa(interceptor.statusCode) + took = time.Since(begin) + ) + i.Duration.WithLabelValues(r.Method, route, status).Observe(float64(took.Nanoseconds())) + }) +} + +func (i Instrument) getRouteName(r *http.Request) string { + var routeMatch mux.RouteMatch + if !i.RouteMatcher.Match(r, &routeMatch) { + return "unmatched_path" + } + name := routeMatch.Route.GetName() + if name == "" { + return "unnamed_path" + } + return name +} diff --git a/prog/app.go b/prog/app.go index 0f24112a4d..f5bd8264e3 100644 --- a/prog/app.go +++ b/prog/app.go @@ -16,6 +16,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/gorilla/mux" + "github.com/prometheus/client_golang/prometheus" "github.com/weaveworks/go-checkpoint" "github.com/weaveworks/weave/common" @@ -26,12 +27,25 @@ import ( "github.com/weaveworks/scope/probe/docker" ) +var ( + requestDuration = prometheus.NewSummaryVec(prometheus.SummaryOpts{ + Namespace: "scope", + Name: "request_duration_nanoseconds", + Help: "Time spent serving HTTP requests.", + }, []string{"method", "route", "status_code"}) +) + +func init() { + prometheus.MustRegister(requestDuration) +} + // Router creates the mux for all the various app components. func router(collector app.Collector, controlRouter app.ControlRouter, pipeRouter app.PipeRouter) http.Handler { router := mux.NewRouter().SkipClean(true) // We pull in the http.DefaultServeMux to get the pprof routes router.PathPrefix("/debug/pprof").Handler(http.DefaultServeMux) + router.Path("/metrics").Handler(prometheus.Handler()) app.RegisterReportPostHandler(collector, router) app.RegisterControlRoutes(router, controlRouter) @@ -40,7 +54,11 @@ func router(collector app.Collector, controlRouter app.ControlRouter, pipeRouter router.PathPrefix("/").Handler(http.FileServer(FS(false))) - return router + instrument := middleware.Instrument{ + RouteMatcher: router, + Duration: requestDuration, + } + return instrument.Wrap(router) } func awsConfigFromURL(url *url.URL) *aws.Config {