1// Copyright (c) The Thanos Authors.
2// Licensed under the Apache License 2.0.
3
4package http
5
6import (
7	"net/http"
8
9	"github.com/prometheus/client_golang/prometheus"
10	"github.com/prometheus/client_golang/prometheus/promhttp"
11)
12
13// InstrumentationMiddleware holds necessary metrics to instrument an http.Server
14// and provides necessary behaviors.
15type InstrumentationMiddleware interface {
16	// NewHandler wraps the given HTTP handler for instrumentation.
17	NewHandler(handlerName string, handler http.Handler) http.HandlerFunc
18}
19
20type nopInstrumentationMiddleware struct{}
21
22func (ins nopInstrumentationMiddleware) NewHandler(handlerName string, handler http.Handler) http.HandlerFunc {
23	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
24		handler.ServeHTTP(w, r)
25	})
26}
27
28// NewNopInstrumentationMiddleware provides a InstrumentationMiddleware which does nothing.
29func NewNopInstrumentationMiddleware() InstrumentationMiddleware {
30	return nopInstrumentationMiddleware{}
31}
32
33type defaultInstrumentationMiddleware struct {
34	requestDuration *prometheus.HistogramVec
35	requestSize     *prometheus.SummaryVec
36	requestsTotal   *prometheus.CounterVec
37	responseSize    *prometheus.SummaryVec
38}
39
40// NewInstrumentationMiddleware provides default InstrumentationMiddleware.
41func NewInstrumentationMiddleware(reg prometheus.Registerer) InstrumentationMiddleware {
42	ins := defaultInstrumentationMiddleware{
43		requestDuration: prometheus.NewHistogramVec(
44			prometheus.HistogramOpts{
45				Name:    "http_request_duration_seconds",
46				Help:    "Tracks the latencies for HTTP requests.",
47				Buckets: []float64{0.001, 0.01, 0.1, 0.3, 0.6, 1, 3, 6, 9, 20, 30, 60, 90, 120},
48			},
49			[]string{"code", "handler", "method"},
50		),
51
52		requestSize: prometheus.NewSummaryVec(
53			prometheus.SummaryOpts{
54				Name: "http_request_size_bytes",
55				Help: "Tracks the size of HTTP requests.",
56			},
57			[]string{"code", "handler", "method"},
58		),
59
60		requestsTotal: prometheus.NewCounterVec(
61			prometheus.CounterOpts{
62				Name: "http_requests_total",
63				Help: "Tracks the number of HTTP requests.",
64			}, []string{"code", "handler", "method"},
65		),
66
67		responseSize: prometheus.NewSummaryVec(
68			prometheus.SummaryOpts{
69				Name: "http_response_size_bytes",
70				Help: "Tracks the size of HTTP responses.",
71			},
72			[]string{"code", "handler", "method"},
73		),
74	}
75	reg.MustRegister(ins.requestDuration, ins.requestSize, ins.requestsTotal, ins.responseSize)
76	return &ins
77}
78
79// NewHandler wraps the given HTTP handler for instrumentation. It
80// registers four metric collectors (if not already done) and reports HTTP
81// metrics to the (newly or already) registered collectors: http_requests_total
82// (CounterVec), http_request_duration_seconds (Histogram),
83// http_request_size_bytes (Summary), http_response_size_bytes (Summary). Each
84// has a constant label named "handler" with the provided handlerName as
85// value. http_requests_total is a metric vector partitioned by HTTP method
86// (label name "method") and HTTP status code (label name "code").
87func (ins *defaultInstrumentationMiddleware) NewHandler(handlerName string, handler http.Handler) http.HandlerFunc {
88	return promhttp.InstrumentHandlerDuration(
89		ins.requestDuration.MustCurryWith(prometheus.Labels{"handler": handlerName}),
90		promhttp.InstrumentHandlerRequestSize(
91			ins.requestSize.MustCurryWith(prometheus.Labels{"handler": handlerName}),
92			promhttp.InstrumentHandlerCounter(
93				ins.requestsTotal.MustCurryWith(prometheus.Labels{"handler": handlerName}),
94				promhttp.InstrumentHandlerResponseSize(
95					ins.responseSize.MustCurryWith(prometheus.Labels{"handler": handlerName}),
96					handler,
97				),
98			),
99		),
100	)
101}
102