1
0
Fork 0

Support for Metrics and Prometheus.

This commit is contained in:
enxebre 2017-01-12 14:34:54 +01:00
parent dd85cbca39
commit 175659a3dd
11 changed files with 279 additions and 5 deletions

51
middlewares/metrics.go Normal file
View file

@ -0,0 +1,51 @@
package middlewares
import (
"github.com/go-kit/kit/metrics"
"net/http"
"strconv"
"time"
)
// Metrics is an Interface that must be satisfied by any system that
// wants to expose and monitor metrics
type Metrics interface {
getReqsCounter() metrics.Counter
getLatencyHistogram() metrics.Histogram
handler() http.Handler
}
// MetricsWrapper is a Negroni compatible Handler which relies on a
// given Metrics implementation to expose and monitor Traefik metrics
type MetricsWrapper struct {
Impl Metrics
}
// NewMetricsWrapper return a MetricsWrapper struct with
// a given Metrics implementation e.g Prometheuss
func NewMetricsWrapper(impl Metrics) *MetricsWrapper {
var metricsWrapper = MetricsWrapper{
Impl: impl,
}
return &metricsWrapper
}
func (m *MetricsWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
start := time.Now()
prw := &responseRecorder{rw, http.StatusOK}
next(prw, r)
labels := []string{"code", strconv.Itoa(prw.StatusCode()), "method", r.Method}
m.Impl.getReqsCounter().With(labels...).Add(1)
m.Impl.getLatencyHistogram().With(labels...).Observe(float64(time.Since(start).Nanoseconds()) / 1000000)
}
func (rw *responseRecorder) StatusCode() int {
return rw.statusCode
}
// Handler is the chance for the Metrics implementation
// to expose its metrics on a server endpoint
func (m *MetricsWrapper) Handler() http.Handler {
return m.Impl.handler()
}

65
middlewares/prometheus.go Normal file
View file

@ -0,0 +1,65 @@
package middlewares
import (
"github.com/containous/traefik/types"
"github.com/go-kit/kit/metrics"
"github.com/go-kit/kit/metrics/prometheus"
stdprometheus "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"net/http"
)
const (
reqsName = "requests_total"
latencyName = "request_duration_milliseconds"
)
// Prometheus is an Implementation for Metrics that exposes prometheus metrics for the number of requests,
// the latency and the response size, partitioned by status code and method.
type Prometheus struct {
reqsCounter metrics.Counter
latencyHistogram metrics.Histogram
}
func (p *Prometheus) getReqsCounter() metrics.Counter {
return p.reqsCounter
}
func (p *Prometheus) getLatencyHistogram() metrics.Histogram {
return p.latencyHistogram
}
// NewPrometheus returns a new prometheus Metrics implementation.
func NewPrometheus(name string, config *types.Prometheus) *Prometheus {
var m Prometheus
m.reqsCounter = prometheus.NewCounterFrom(
stdprometheus.CounterOpts{
Name: reqsName,
Help: "How many HTTP requests processed, partitioned by status code and method.",
ConstLabels: stdprometheus.Labels{"service": name},
},
[]string{"code", "method"},
)
var buckets []float64
if config.Buckets != nil {
buckets = config.Buckets
} else {
buckets = []float64{100, 300, 1200, 5000}
}
m.latencyHistogram = prometheus.NewHistogramFrom(
stdprometheus.HistogramOpts{
Name: latencyName,
Help: "How long it took to process the request, partitioned by status code and method.",
ConstLabels: stdprometheus.Labels{"service": name},
Buckets: buckets,
},
[]string{"code", "method"},
)
return &m
}
func (p *Prometheus) handler() http.Handler {
return promhttp.Handler()
}

View file

@ -0,0 +1,47 @@
package middlewares
import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/codegangsta/negroni"
"github.com/containous/traefik/types"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func TestPrometheus(t *testing.T) {
recorder := httptest.NewRecorder()
n := negroni.New()
metricsMiddlewareBackend := NewMetricsWrapper(NewPrometheus("test", &types.Prometheus{}))
n.Use(metricsMiddlewareBackend)
r := http.NewServeMux()
r.Handle("/metrics", promhttp.Handler())
r.HandleFunc(`/ok`, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "ok")
})
n.UseHandler(r)
req1, err := http.NewRequest("GET", "http://localhost:3000/ok", nil)
if err != nil {
t.Error(err)
}
req2, err := http.NewRequest("GET", "http://localhost:3000/metrics", nil)
if err != nil {
t.Error(err)
}
n.ServeHTTP(recorder, req1)
n.ServeHTTP(recorder, req2)
body := recorder.Body.String()
if !strings.Contains(body, reqsName) {
t.Errorf("body does not contain request total entry '%s'", reqsName)
}
if !strings.Contains(body, latencyName) {
t.Errorf("body does not contain request duration entry '%s'", reqsName)
}
}