Skip to content

Commit

Permalink
Add possibility to dynamically get label values for http instrumentation
Browse files Browse the repository at this point in the history
Signed-off-by: Quentin Devos <4972091+Okhoshi@users.noreply.github.com>
  • Loading branch information
Okhoshi committed Aug 25, 2022
1 parent 83d56b1 commit 50f10d3
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 65 deletions.
30 changes: 18 additions & 12 deletions prometheus/promhttp/instrument_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,17 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou
o.apply(rtOpts)
}

code, method := checkLabels(counter)
// Curry the counter with dynamic labels before checking the remaining labels
code, method := checkLabels(counter.MustCurryWith(rtOpts.labels()))

return func(r *http.Request) (*http.Response, error) {
resp, err := next.RoundTrip(r)
if err == nil {
addWithExemplar(
counter.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)),
1,
rtOpts.getExemplarFn(r.Context()),
)
l := labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)
for label, resolve := range rtOpts.labelResolverFns {
l[label] = resolve(r, resp)
}
addWithExemplar(counter.With(l), 1, rtOpts.getExemplarFn(r.Context()))
}
return resp, err
}
Expand Down Expand Up @@ -110,17 +111,22 @@ func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundT
o.apply(rtOpts)
}

code, method := checkLabels(obs)
dynamicLabels := prometheus.Labels{}
for label := range rtOpts.labelResolverFns {
dynamicLabels[label] = "dummy"
}
// Curry the observer with dynamic labels before checking the remaining labels
code, method := checkLabels(obs.MustCurryWith(dynamicLabels))

return func(r *http.Request) (*http.Response, error) {
start := time.Now()
resp, err := next.RoundTrip(r)
if err == nil {
observeWithExemplar(
obs.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)),
time.Since(start).Seconds(),
rtOpts.getExemplarFn(r.Context()),
)
l := labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)
for label, resolve := range rtOpts.labelResolverFns {
l[label] = resolve(r, resp)
}
observeWithExemplar(obs.With(l), time.Since(start).Seconds(), rtOpts.getExemplarFn(r.Context()))
}
return resp, err
}
Expand Down
126 changes: 80 additions & 46 deletions prometheus/promhttp/instrument_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,31 +87,36 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, op
o.apply(hOpts)
}

code, method := checkLabels(obs)
dynamicLabels := prometheus.Labels{}
for label := range hOpts.labelResolverFns {
dynamicLabels[label] = "dummy"
}

// Curry the observer with dynamic labels before checking the remaining labels
code, method := checkLabels(obs.MustCurryWith(dynamicLabels))

if code {
return func(w http.ResponseWriter, r *http.Request) {
now := time.Now()
d := newDelegator(w, nil)
next.ServeHTTP(d, r)

observeWithExemplar(
obs.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
time.Since(now).Seconds(),
hOpts.getExemplarFn(r.Context()),
)
l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
for label, resolve := range hOpts.labelResolverFns {
l[label] = resolve(r, nil)
}
observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context()))
}
}

return func(w http.ResponseWriter, r *http.Request) {
now := time.Now()
next.ServeHTTP(w, r)

observeWithExemplar(
obs.With(labels(code, method, r.Method, 0, hOpts.extraMethods...)),
time.Since(now).Seconds(),
hOpts.getExemplarFn(r.Context()),
)
l := labels(code, method, r.Method, 0, hOpts.extraMethods...)
for label, resolve := range hOpts.labelResolverFns {
l[label] = resolve(r, nil)
}
observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context()))
}
}

Expand All @@ -138,28 +143,35 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler,
o.apply(hOpts)
}

code, method := checkLabels(counter)
dynamicLabels := prometheus.Labels{}
for label := range hOpts.labelResolverFns {
dynamicLabels[label] = "dummy"
}

// Curry the counter with dynamic labels before checking the remaining labels
code, method := checkLabels(counter.MustCurryWith(dynamicLabels))

if code {
return func(w http.ResponseWriter, r *http.Request) {
d := newDelegator(w, nil)
next.ServeHTTP(d, r)

addWithExemplar(
counter.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
1,
hOpts.getExemplarFn(r.Context()),
)
l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
for label, resolve := range hOpts.labelResolverFns {
l[label] = resolve(r, nil)
}
addWithExemplar(counter.With(l), 1, hOpts.getExemplarFn(r.Context()))
}
}

return func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
addWithExemplar(
counter.With(labels(code, method, r.Method, 0, hOpts.extraMethods...)),
1,
hOpts.getExemplarFn(r.Context()),
)

l := labels(code, method, r.Method, 0, hOpts.extraMethods...)
for label, resolve := range hOpts.labelResolverFns {
l[label] = resolve(r, nil)
}
addWithExemplar(counter.With(l), 1, hOpts.getExemplarFn(r.Context()))
}
}

Expand Down Expand Up @@ -191,16 +203,22 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha
o.apply(hOpts)
}

code, method := checkLabels(obs)
dynamicLabels := prometheus.Labels{}
for label := range hOpts.labelResolverFns {
dynamicLabels[label] = "dummy"
}

// Curry the observer with dynamic labels before checking the remaining labels
code, method := checkLabels(obs.MustCurryWith(dynamicLabels))

return func(w http.ResponseWriter, r *http.Request) {
now := time.Now()
d := newDelegator(w, func(status int) {
observeWithExemplar(
obs.With(labels(code, method, r.Method, status, hOpts.extraMethods...)),
time.Since(now).Seconds(),
hOpts.getExemplarFn(r.Context()),
)
l := labels(code, method, r.Method, status, hOpts.extraMethods...)
for label, resolve := range hOpts.labelResolverFns {
l[label] = resolve(r, nil)
}
observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context()))
})
next.ServeHTTP(d, r)
}
Expand Down Expand Up @@ -231,28 +249,37 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler,
o.apply(hOpts)
}

code, method := checkLabels(obs)
dynamicLabels := prometheus.Labels{}
for label := range hOpts.labelResolverFns {
dynamicLabels[label] = "dummy"
}

// Curry the observer with dynamic labels before checking the remaining labels
code, method := checkLabels(obs.MustCurryWith(dynamicLabels))

if code {
return func(w http.ResponseWriter, r *http.Request) {
d := newDelegator(w, nil)
next.ServeHTTP(d, r)
size := computeApproximateRequestSize(r)
observeWithExemplar(
obs.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
float64(size),
hOpts.getExemplarFn(r.Context()),
)

l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
for label, resolve := range hOpts.labelResolverFns {
l[label] = resolve(r, nil)
}
observeWithExemplar(obs.With(l), float64(size), hOpts.getExemplarFn(r.Context()))
}
}

return func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
size := computeApproximateRequestSize(r)
observeWithExemplar(
obs.With(labels(code, method, r.Method, 0, hOpts.extraMethods...)),
float64(size),
hOpts.getExemplarFn(r.Context()),
)

l := labels(code, method, r.Method, 0, hOpts.extraMethods...)
for label, resolve := range hOpts.labelResolverFns {
l[label] = resolve(r, nil)
}
observeWithExemplar(obs.With(l), float64(size), hOpts.getExemplarFn(r.Context()))
}
}

Expand Down Expand Up @@ -281,16 +308,23 @@ func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler
o.apply(hOpts)
}

code, method := checkLabels(obs)
dynamicLabels := prometheus.Labels{}
for label := range hOpts.labelResolverFns {
dynamicLabels[label] = "dummy"
}

// Curry the observer with dynamic labels before checking the remaining labels
code, method := checkLabels(obs.MustCurryWith(dynamicLabels))

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
d := newDelegator(w, nil)
next.ServeHTTP(d, r)
observeWithExemplar(
obs.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
float64(d.Written()),
hOpts.getExemplarFn(r.Context()),
)

l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
for label, resolve := range hOpts.labelResolverFns {
l[label] = resolve(r, nil)
}
observeWithExemplar(obs.With(l), float64(d.Written()), hOpts.getExemplarFn(r.Context()))
})
}

Expand Down
38 changes: 34 additions & 4 deletions prometheus/promhttp/instrument_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func TestLabelCheck(t *testing.T) {
varLabels []string
constLabels []string
curriedLabels []string
dynamicLabels []string
ok bool
}{
"empty": {
Expand Down Expand Up @@ -60,12 +61,14 @@ func TestLabelCheck(t *testing.T) {
varLabels: []string{"code", "method"},
constLabels: []string{"foo", "bar"},
curriedLabels: []string{"dings", "bums"},
dynamicLabels: []string{"dyn", "amics"},
ok: true,
},
"all labels used with an invalid const label name": {
varLabels: []string{"code", "method"},
constLabels: []string{"in-valid", "bar"},
curriedLabels: []string{"dings", "bums"},
dynamicLabels: []string{"dyn", "amics"},
ok: false,
},
"unsupported var label": {
Expand Down Expand Up @@ -98,6 +101,18 @@ func TestLabelCheck(t *testing.T) {
curriedLabels: []string{"method"},
ok: true,
},
"supported label as const and dynamic": {
varLabels: []string{},
constLabels: []string{"code"},
dynamicLabels: []string{"method"},
ok: true,
},
"supported label as curried and dynamic": {
varLabels: []string{},
curriedLabels: []string{"code"},
dynamicLabels: []string{"method"},
ok: true,
},
"supported label as const and curry with unsupported as var": {
varLabels: []string{"foo"},
constLabels: []string{"code"},
Expand All @@ -116,6 +131,7 @@ func TestLabelCheck(t *testing.T) {
varLabels: []string{"code", "method"},
constLabels: []string{"foo", "bar"},
curriedLabels: []string{"dings", "bums"},
dynamicLabels: []string{"dyn", "amics"},
ok: false,
},
}
Expand All @@ -130,26 +146,35 @@ func TestLabelCheck(t *testing.T) {
for _, l := range sc.constLabels {
constLabels[l] = "dummy"
}
labelNames := append(append(sc.varLabels, sc.curriedLabels...), sc.dynamicLabels...)
c := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: metricName,
Help: "c help",
ConstLabels: constLabels,
},
append(sc.varLabels, sc.curriedLabels...),
labelNames,
)
o := prometheus.ObserverVec(prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: metricName,
Help: "c help",
ConstLabels: constLabels,
},
append(sc.varLabels, sc.curriedLabels...),
labelNames,
))
for _, l := range sc.curriedLabels {
c = c.MustCurryWith(prometheus.Labels{l: "dummy"})
o = o.MustCurryWith(prometheus.Labels{l: "dummy"})
}
opts := []Option{}
for _, l := range sc.dynamicLabels {
opts = append(opts, WithLabelResolver(l, []string{"dummy"},
func(_ *http.Request, _ *http.Response) string {
return "dummy"
},
))
}

func() {
defer func() {
Expand All @@ -161,7 +186,7 @@ func TestLabelCheck(t *testing.T) {
t.Error("expected panic")
}
}()
InstrumentHandlerCounter(c, nil)
InstrumentHandlerCounter(c, nil, opts...)
}()
func() {
defer func() {
Expand All @@ -173,7 +198,7 @@ func TestLabelCheck(t *testing.T) {
t.Error("expected panic")
}
}()
InstrumentHandlerDuration(o, nil)
InstrumentHandlerDuration(o, nil, opts...)
}()
if sc.ok {
// Test if wantCode and wantMethod were detected correctly.
Expand All @@ -186,6 +211,11 @@ func TestLabelCheck(t *testing.T) {
wantMethod = true
}
}
// Curry the dynamic labels since this is done normally behind the scenes for the check
for _, l := range sc.dynamicLabels {
c = c.MustCurryWith(prometheus.Labels{l: "dummy"})
o = o.MustCurryWith(prometheus.Labels{l: "dummy"})
}
gotCode, gotMethod := checkLabels(c)
if gotCode != wantCode {
t.Errorf("wanted code=%t for counter, got code=%t", wantCode, gotCode)
Expand Down
Loading

0 comments on commit 50f10d3

Please sign in to comment.