Support for Metrics and Prometheus.
This commit is contained in:
parent
dd85cbca39
commit
175659a3dd
11 changed files with 279 additions and 5 deletions
51
middlewares/metrics.go
Normal file
51
middlewares/metrics.go
Normal 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
65
middlewares/prometheus.go
Normal 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()
|
||||
}
|
47
middlewares/prometheus_test.go
Normal file
47
middlewares/prometheus_test.go
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue