@@ -19,9 +19,11 @@ import (
1919 "log/slog"
2020 "net/http"
2121 "runtime"
22+ "strings"
2223 "time"
2324
2425 "github.com/prometheus/client_golang/prometheus"
26+ "github.com/prometheus/client_golang/prometheus/promhttp"
2527 "github.com/prometheus/common/model"
2628 "github.com/prometheus/common/promslog"
2729 "github.com/prometheus/common/route"
@@ -40,6 +42,7 @@ type API struct {
4042 v2 * apiv2.API
4143 deprecationRouter * V1DeprecationRouter
4244
45+ requestDuration * prometheus.HistogramVec
4346 requestsInFlight prometheus.Gauge
4447 concurrencyLimitExceeded prometheus.Counter
4548 timeout time.Duration
@@ -75,6 +78,8 @@ type Options struct {
7578 // Registry is used to register Prometheus metrics. If nil, no metrics
7679 // registration will happen.
7780 Registry prometheus.Registerer
81+ // RequestDuration is used to measure the duration of HTTP requests.
82+ RequestDuration * prometheus.HistogramVec
7883 // GroupFunc returns a list of alert groups. The alerts are grouped
7984 // according to the current active configuration. Alerts returned are
8085 // filtered by the arguments provided to the function.
@@ -132,8 +137,6 @@ func New(opts Options) (*API, error) {
132137 return nil , err
133138 }
134139
135- // TODO(beorn7): For now, this hardcodes the method="get" label. Other
136- // methods should get the same instrumentation.
137140 requestsInFlight := prometheus .NewGauge (prometheus.GaugeOpts {
138141 Name : "alertmanager_http_requests_in_flight" ,
139142 Help : "Current number of HTTP requests being processed." ,
@@ -156,6 +159,7 @@ func New(opts Options) (*API, error) {
156159 return & API {
157160 deprecationRouter : NewV1DeprecationRouter (l .With ("version" , "v1" )),
158161 v2 : v2 ,
162+ requestDuration : opts .RequestDuration ,
159163 requestsInFlight : requestsInFlight ,
160164 concurrencyLimitExceeded : concurrencyLimitExceeded ,
161165 timeout : opts .Timeout ,
@@ -181,13 +185,17 @@ func (api *API) Register(r *route.Router, routePrefix string) *http.ServeMux {
181185 if routePrefix != "/" {
182186 apiPrefix = routePrefix
183187 }
184- // TODO(beorn7): HTTP instrumentation is only in place for Router. Since
185- // /api/v2 works on the Handler level, it is currently not instrumented
186- // at all (with the exception of requestsInFlight, which is handled in
187- // limitHandler below).
188188 mux .Handle (
189189 apiPrefix + "/api/v2/" ,
190- api .limitHandler (http .StripPrefix (apiPrefix , api .v2 .Handler )),
190+ api .instrumentHandler (
191+ apiPrefix ,
192+ api .limitHandler (
193+ http .StripPrefix (
194+ apiPrefix ,
195+ api .v2 .Handler ,
196+ ),
197+ ),
198+ ),
191199 )
192200
193201 return mux
@@ -226,3 +234,17 @@ func (api *API) limitHandler(h http.Handler) http.Handler {
226234 "Exceeded configured timeout of %v.\n " , api .timeout ,
227235 ))
228236}
237+
238+ func (api * API ) instrumentHandler (prefix string , h http.Handler ) http.Handler {
239+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
240+ path , _ := strings .CutPrefix (r .URL .Path , prefix )
241+ // avoid high cardinality label values by replacing the actual silence IDs with a placeholder
242+ if strings .HasPrefix (path , "/api/v2/silence/" ) {
243+ path = "/api/v2/silence/{silenceID}"
244+ }
245+ promhttp .InstrumentHandlerDuration (
246+ api .requestDuration .MustCurryWith (prometheus.Labels {"handler" : path }),
247+ h ,
248+ ).ServeHTTP (w , r )
249+ })
250+ }
0 commit comments