Skip to content

Commit

Permalink
change the Accept header sent to the exporters
Browse files Browse the repository at this point in the history
Create a new version of the API endpoint (`/v2`).
This new version change the `Accept` HTTP header sent to the exporters requested
(from `application/openmetrics-text` … to `text/plain`) because the Go library
we use does not seems to support all OpenMetrics specificities ^1.

The requests sent to the exporters contains a new HTTP header `X-Promfetcher-API-version` with the Promfetchers API version.

^1: Java actuator does respect the OpenMetrics specifications; the double quotes in the comments is not supported for example:
> # HELP process_cpu_usage The \"recent cpu usage\" for the Java Virtual Machine process

See also:
[1]: prometheus/client_golang#829 (comment)
[2]: prometheus/common#214
  • Loading branch information
romain-dartigues committed Dec 15, 2022
1 parent de8eb40 commit 82206db
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 8 deletions.
27 changes: 22 additions & 5 deletions api/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,37 @@ func Register(rtr *mux.Router, metFetcher *fetchers.MetricsFetcher, broker *Brok
}

handlerMetrics := handlers.CompressHandler(http.HandlerFunc(api.metrics))
rtr.Handle("/v1/apps/{appIdOrPathOrName:.*}/metrics", handlerMetrics).
handlerOnlyAppMetrics := handlers.CompressHandler(forceOnlyForApp(http.HandlerFunc(api.metrics)))

// API v1: deprecated
routerApiV1 := rtr.PathPrefix("/v1").Subrouter()
routerApiV1.Handle("/apps/{appIdOrPathOrName:.*}/metrics", handlerMetrics).
Methods(http.MethodGet)

rtr.Handle("/v1/apps/metrics", handlerMetrics).
routerApiV1.Handle("/apps/metrics", handlerMetrics).
Methods(http.MethodGet)

handlerOnlyAppMetrics := handlers.CompressHandler(forceOnlyForApp(http.HandlerFunc(api.metrics)))
routerApiV1.Handle("/apps/{appIdOrPathOrName:.*}/only-app-metrics", handlerOnlyAppMetrics).
Methods(http.MethodGet)

routerApiV1.Handle("/apps/only-app-metrics", handlerOnlyAppMetrics).
Methods(http.MethodGet)

// API v2
routerApiV2 := rtr.PathPrefix("/v2").Subrouter()
routerApiV2.Handle("/apps/{appIdOrPathOrName:.*}/metrics", handlerMetrics).
Methods(http.MethodGet)

routerApiV2.Handle("/apps/metrics", handlerMetrics).
Methods(http.MethodGet)

rtr.Handle("/v1/apps/{appIdOrPathOrName:.*}/only-app-metrics", handlerOnlyAppMetrics).
routerApiV2.Handle("/apps/{appIdOrPathOrName:.*}/only-app-metrics", handlerOnlyAppMetrics).
Methods(http.MethodGet)

rtr.Handle("/v1/apps/only-app-metrics", handlerOnlyAppMetrics).
routerApiV2.Handle("/apps/only-app-metrics", handlerOnlyAppMetrics).
Methods(http.MethodGet)

// non-API routes
rtr.NewRoute().MatcherFunc(func(req *http.Request, m *mux.RouteMatch) bool {
return strings.HasPrefix(req.URL.Path, "/broker/v2")
}).Handler(http.StripPrefix("/broker", broker.Handler()))
Expand Down
12 changes: 12 additions & 0 deletions api/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,27 @@ package api
import (
"fmt"
"net/http"
"regexp"
"strings"

"github.com/gorilla/mux"
"github.com/orange-cloudfoundry/promfetcher/models"
"github.com/prometheus/common/expfmt"

"github.com/orange-cloudfoundry/promfetcher/errors"
)

func (a Api) metrics(w http.ResponseWriter, req *http.Request) {
// extract the API version from the requested path (ie: /v2)
// and set it to an HTTP header
apiVersion := regexp.MustCompile("/v([0-9]+)(?:/|$)").FindStringSubmatch(req.URL.Path)
if len(apiVersion) == 2 {
req.Header.Set(models.XPromfetcherApiVersion, apiVersion[1])
} else {
// default to v1
req.Header.Set(models.XPromfetcherApiVersion, "1")
}

appIdOrPathOrName, ok := mux.Vars(req)["appIdOrPathOrName"]
if !ok {
appIdOrPathOrName = req.URL.Query().Get("app")
Expand Down
4 changes: 4 additions & 0 deletions models/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package models

//XPromfetcherApiVersion name of the HTTP header corresponding to the Promfetcher API version
const XPromfetcherApiVersion = `X-Promfetcher-API-version`
12 changes: 9 additions & 3 deletions scrapers/scrape.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ import (
"github.com/orange-cloudfoundry/promfetcher/clients"
"github.com/orange-cloudfoundry/promfetcher/errors"
"github.com/orange-cloudfoundry/promfetcher/models"
"github.com/prometheus/common/expfmt"
)

const acceptHeader = `application/openmetrics-text; version=0.0.1,text/plain;version=0.0.4;q=0.5,*/*;q=0.1`

type Scraper struct {
backendFactory *clients.BackendFactory
db *gorm.DB
Expand Down Expand Up @@ -69,7 +68,14 @@ func (s Scraper) Scrape(route *models.Route, metricPathDefault string, headers h
req.Header[k] = v
}
}
req.Header.Add("Accept", acceptHeader)
// Prometheus parser is not OpenMetrics compliant
// See: prometheus/common issues: 214, 829
req.Header.Set("Accept", string(expfmt.FmtText))
// keep the OpenMetrics accept HTTP header for the /v1 endpoint
if req.Header.Get(models.XPromfetcherApiVersion) == "1" {
req.Header.Set(models.XPromfetcherApiVersion, "1")
req.Header.Set("Accept", `application/openmetrics-text; version=0.0.1,text/plain;version=0.0.4;q=0.5,*/*;q=0.1`)
}
req.Header.Add("Accept-Encoding", "gzip")
req.Header.Set("X-Prometheus-Scrape-Timeout-Seconds", fmt.Sprintf("%f", (30*time.Second).Seconds()))
req.Header.Set("X-Forwarded-Proto", scheme)
Expand Down

0 comments on commit 82206db

Please sign in to comment.