From a9a3bdbee729424a276cd1be429c5495ae595e9f Mon Sep 17 00:00:00 2001 From: William Petit Date: Mon, 25 Aug 2025 10:40:51 +0200 Subject: [PATCH] feat: add response time histogram metric --- doc/fr/references/metrics.md | 41 +++++++++++++++++++++++------ internal/proxy/director/director.go | 28 +++++++++++++++----- internal/proxy/director/metrics.go | 9 +++++++ 3 files changed, 63 insertions(+), 15 deletions(-) diff --git a/doc/fr/references/metrics.md b/doc/fr/references/metrics.md index 9f8ed99..8c9829d 100644 --- a/doc/fr/references/metrics.md +++ b/doc/fr/references/metrics.md @@ -1,6 +1,6 @@ # Métriques -Bouncer expose un certain nombre de métriques Prometheus sur le serveur proxy ainsi que sur le serveur d'administration. Ces métriques sont par défaut accessibles sur `/.bouncer/metrics`. +Bouncer expose un certain nombre de métriques Prometheus sur le serveur proxy ainsi que sur le serveur d'administration. Ces métriques sont par défaut accessibles sur `/.bouncer/metrics`. Il est possible de configurer le point d'entrée de ces métriques ainsi que d'ajouter une authentification de type `Basic Auth` [via la configuration](../../../misc/packaging/common/config.yml) (voir les clés `admin.metrics` et `proxy.metrics`). @@ -15,15 +15,40 @@ Chaque layer associé à un proxy peut également ses propres métriques spécif #### `bouncer_proxy_director_proxy_requests_total{proxy=}` - **Type:** `counter` -- **Description**: Nombre total de requêtes ayant transité par le proxy +- **Description**: Nombre total de requêtes ayant transité par le proxy - **Exemple** - ``` - # HELP bouncer_proxy_director_proxy_requests_total Bouncer proxy total requests - # TYPE bouncer_proxy_director_proxy_requests_total counter - bouncer_proxy_director_proxy_requests_total{proxy="cadoles"} 64 - ``` + ``` + # HELP bouncer_proxy_director_proxy_requests_total Bouncer proxy total requests + # TYPE bouncer_proxy_director_proxy_requests_total counter + bouncer_proxy_director_proxy_requests_total{proxy="cadoles"} 64 + ``` + +#### `bouncer_proxy_director_proxy_responses_duration_seconds{proxy=}` + +- **Type:** `histogram` +- **Description**: Histogramme du temps de traitement du cycle requête/réponse par proxy +- **Exemple** + + ``` + # HELP bouncer_proxy_director_proxy_responses_duration_seconds Bouncer proxy responses duration + # TYPE bouncer_proxy_director_proxy_responses_duration_seconds histogram + bouncer_proxy_director_proxy_responses_duration_seconds_bucket{proxy="dummy",le="0.005"} 13 + bouncer_proxy_director_proxy_responses_duration_seconds_bucket{proxy="dummy",le="0.01"} 13 + bouncer_proxy_director_proxy_responses_duration_seconds_bucket{proxy="dummy",le="0.025"} 13 + bouncer_proxy_director_proxy_responses_duration_seconds_bucket{proxy="dummy",le="0.05"} 13 + bouncer_proxy_director_proxy_responses_duration_seconds_bucket{proxy="dummy",le="0.1"} 13 + bouncer_proxy_director_proxy_responses_duration_seconds_bucket{proxy="dummy",le="0.25"} 13 + bouncer_proxy_director_proxy_responses_duration_seconds_bucket{proxy="dummy",le="0.5"} 13 + bouncer_proxy_director_proxy_responses_duration_seconds_bucket{proxy="dummy",le="1"} 13 + bouncer_proxy_director_proxy_responses_duration_seconds_bucket{proxy="dummy",le="2.5"} 13 + bouncer_proxy_director_proxy_responses_duration_seconds_bucket{proxy="dummy",le="5"} 13 + bouncer_proxy_director_proxy_responses_duration_seconds_bucket{proxy="dummy",le="10"} 13 + bouncer_proxy_director_proxy_responses_duration_seconds_bucket{proxy="dummy",le="+Inf"} 13 + bouncer_proxy_director_proxy_responses_duration_seconds_sum{proxy="dummy"} 0.009556884 + bouncer_proxy_director_proxy_responses_duration_seconds_count{proxy="dummy"} 13 + ``` ### Serveur d'administration -_Pas de métrique supplémentaire._ \ No newline at end of file +_Pas de métrique supplémentaire._ diff --git a/internal/proxy/director/director.go b/internal/proxy/director/director.go index 4d74625..ad45655 100644 --- a/internal/proxy/director/director.go +++ b/internal/proxy/director/director.go @@ -4,6 +4,7 @@ import ( "context" "net/http" "sort" + "time" "forge.cadoles.com/Cadoles/go-proxy" "forge.cadoles.com/Cadoles/go-proxy/wildcard" @@ -28,12 +29,12 @@ type Director struct { const proxiesCacheKey = "proxies" -func (d *Director) rewriteRequest(r *http.Request) (*http.Request, error) { +func (d *Director) rewriteRequest(r *http.Request) (*http.Request, []store.ProxyName, error) { ctx := r.Context() proxies, _, err := d.cachedProxies.Get(ctx, proxiesCacheKey) if err != nil { - return r, errors.WithStack(err) + return r, nil, errors.WithStack(err) } url := getRequestURL(r) @@ -42,12 +43,16 @@ func (d *Director) rewriteRequest(r *http.Request) (*http.Request, error) { layers := make([]*store.Layer, 0) + matchingProxies := make([]store.ProxyName, 0) + for _, p := range proxies { for _, from := range p.From { if matches := wildcard.Match(url.String(), from); !matches { continue } + matchingProxies = append(matchingProxies, p.Name) + proxyCtx := logger.With(ctx, logger.F("proxy", p.Name), logger.F("host", r.Host), @@ -58,7 +63,7 @@ func (d *Director) rewriteRequest(r *http.Request) (*http.Request, error) { proxyLayers, _, err := d.cachedLayers.Get(proxyCtx, string(p.Name)) if err != nil { - return r, errors.WithStack(err) + return r, nil, errors.WithStack(err) } layers = append(layers, proxyLayers...) @@ -69,7 +74,7 @@ func (d *Director) rewriteRequest(r *http.Request) (*http.Request, error) { toURL, err := url.Parse(p.To) if err != nil { - return r, errors.WithStack(err) + return r, nil, errors.WithStack(err) } r.URL.Host = toURL.Host @@ -87,14 +92,14 @@ func (d *Director) rewriteRequest(r *http.Request) (*http.Request, error) { }) } - return r, nil + return r, matchingProxies, nil } } ctx = withLayers(ctx, layers) r = r.WithContext(ctx) - return r, nil + return r, matchingProxies, nil } func (d *Director) getProxies(ctx context.Context, key string) ([]*store.Proxy, error) { @@ -215,17 +220,26 @@ func (d *Director) ResponseTransformer() proxy.ResponseTransformer { func (d *Director) Middleware() proxy.Middleware { return func(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + sentry.ConfigureScope(func(scope *sentry.Scope) { ctx := withHandleError(r.Context(), d.handleError) ctx = withSentryScope(ctx, scope) r = r.WithContext(ctx) - r, err := d.rewriteRequest(r) + r, matched, err := d.rewriteRequest(r) if err != nil { HandleError(ctx, w, r, http.StatusInternalServerError, errors.Wrap(err, "could not rewrite request")) return } + defer func() { + elapsed := time.Since(start) + for _, p := range matched { + metricProxyResponsesDurationSeconds.WithLabelValues(string(p)).Observe(elapsed.Seconds()) + } + }() + ctx = r.Context() layers, err := ctxLayers(ctx) diff --git a/internal/proxy/director/metrics.go b/internal/proxy/director/metrics.go index 025124a..bc32c6b 100644 --- a/internal/proxy/director/metrics.go +++ b/internal/proxy/director/metrics.go @@ -18,3 +18,12 @@ var metricProxyRequestsTotal = promauto.NewCounterVec( }, []string{metricLabelProxy}, ) + +var metricProxyResponsesDurationSeconds = promauto.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "proxy_responses_duration_seconds", + Help: "Bouncer proxy responses duration", + Namespace: metricNamespace, + }, + []string{metricLabelProxy}, +)