summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server_test.go')
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server_test.go375
1 files changed, 375 insertions, 0 deletions
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server_test.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server_test.go
new file mode 100644
index 000000000..e9af63e04
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server_test.go
@@ -0,0 +1,375 @@
+// Copyright 2017 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package promhttp
+
+import (
+ "io"
+ "log"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+func TestLabelCheck(t *testing.T) {
+ scenarios := map[string]struct {
+ varLabels []string
+ constLabels []string
+ curriedLabels []string
+ ok bool
+ }{
+ "empty": {
+ varLabels: []string{},
+ constLabels: []string{},
+ curriedLabels: []string{},
+ ok: true,
+ },
+ "code as single var label": {
+ varLabels: []string{"code"},
+ constLabels: []string{},
+ curriedLabels: []string{},
+ ok: true,
+ },
+ "method as single var label": {
+ varLabels: []string{"method"},
+ constLabels: []string{},
+ curriedLabels: []string{},
+ ok: true,
+ },
+ "cade and method as var labels": {
+ varLabels: []string{"method", "code"},
+ constLabels: []string{},
+ curriedLabels: []string{},
+ ok: true,
+ },
+ "valid case with all labels used": {
+ varLabels: []string{"code", "method"},
+ constLabels: []string{"foo", "bar"},
+ curriedLabels: []string{"dings", "bums"},
+ ok: true,
+ },
+ "unsupported var label": {
+ varLabels: []string{"foo"},
+ constLabels: []string{},
+ curriedLabels: []string{},
+ ok: false,
+ },
+ "mixed var labels": {
+ varLabels: []string{"method", "foo", "code"},
+ constLabels: []string{},
+ curriedLabels: []string{},
+ ok: false,
+ },
+ "unsupported var label but curried": {
+ varLabels: []string{},
+ constLabels: []string{},
+ curriedLabels: []string{"foo"},
+ ok: true,
+ },
+ "mixed var labels but unsupported curried": {
+ varLabels: []string{"code", "method"},
+ constLabels: []string{},
+ curriedLabels: []string{"foo"},
+ ok: true,
+ },
+ "supported label as const and curry": {
+ varLabels: []string{},
+ constLabels: []string{"code"},
+ curriedLabels: []string{"method"},
+ ok: true,
+ },
+ "supported label as const and curry with unsupported as var": {
+ varLabels: []string{"foo"},
+ constLabels: []string{"code"},
+ curriedLabels: []string{"method"},
+ ok: false,
+ },
+ }
+
+ for name, sc := range scenarios {
+ t.Run(name, func(t *testing.T) {
+ constLabels := prometheus.Labels{}
+ for _, l := range sc.constLabels {
+ constLabels[l] = "dummy"
+ }
+ c := prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "c",
+ Help: "c help",
+ ConstLabels: constLabels,
+ },
+ append(sc.varLabels, sc.curriedLabels...),
+ )
+ o := prometheus.ObserverVec(prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "c",
+ Help: "c help",
+ ConstLabels: constLabels,
+ },
+ append(sc.varLabels, sc.curriedLabels...),
+ ))
+ for _, l := range sc.curriedLabels {
+ c = c.MustCurryWith(prometheus.Labels{l: "dummy"})
+ o = o.MustCurryWith(prometheus.Labels{l: "dummy"})
+ }
+
+ func() {
+ defer func() {
+ if err := recover(); err != nil {
+ if sc.ok {
+ t.Error("unexpected panic:", err)
+ }
+ } else if !sc.ok {
+ t.Error("expected panic")
+ }
+ }()
+ InstrumentHandlerCounter(c, nil)
+ }()
+ func() {
+ defer func() {
+ if err := recover(); err != nil {
+ if sc.ok {
+ t.Error("unexpected panic:", err)
+ }
+ } else if !sc.ok {
+ t.Error("expected panic")
+ }
+ }()
+ InstrumentHandlerDuration(o, nil)
+ }()
+ if sc.ok {
+ // Test if wantCode and wantMethod were detected correctly.
+ var wantCode, wantMethod bool
+ for _, l := range sc.varLabels {
+ if l == "code" {
+ wantCode = true
+ }
+ if l == "method" {
+ wantMethod = true
+ }
+ }
+ gotCode, gotMethod := checkLabels(c)
+ if gotCode != wantCode {
+ t.Errorf("wanted code=%t for counter, got code=%t", wantCode, gotCode)
+ }
+ if gotMethod != wantMethod {
+ t.Errorf("wanted method=%t for counter, got method=%t", wantMethod, gotMethod)
+ }
+ gotCode, gotMethod = checkLabels(o)
+ if gotCode != wantCode {
+ t.Errorf("wanted code=%t for observer, got code=%t", wantCode, gotCode)
+ }
+ if gotMethod != wantMethod {
+ t.Errorf("wanted method=%t for observer, got method=%t", wantMethod, gotMethod)
+ }
+ }
+ })
+ }
+}
+
+func TestMiddlewareAPI(t *testing.T) {
+ reg := prometheus.NewRegistry()
+
+ inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
+ Name: "in_flight_requests",
+ Help: "A gauge of requests currently being served by the wrapped handler.",
+ })
+
+ counter := prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "api_requests_total",
+ Help: "A counter for requests to the wrapped handler.",
+ },
+ []string{"code", "method"},
+ )
+
+ histVec := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "response_duration_seconds",
+ Help: "A histogram of request latencies.",
+ Buckets: prometheus.DefBuckets,
+ ConstLabels: prometheus.Labels{"handler": "api"},
+ },
+ []string{"method"},
+ )
+
+ writeHeaderVec := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "write_header_duration_seconds",
+ Help: "A histogram of time to first write latencies.",
+ Buckets: prometheus.DefBuckets,
+ ConstLabels: prometheus.Labels{"handler": "api"},
+ },
+ []string{},
+ )
+
+ responseSize := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "push_request_size_bytes",
+ Help: "A histogram of request sizes for requests.",
+ Buckets: []float64{200, 500, 900, 1500},
+ },
+ []string{},
+ )
+
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("OK"))
+ })
+
+ reg.MustRegister(inFlightGauge, counter, histVec, responseSize, writeHeaderVec)
+
+ chain := InstrumentHandlerInFlight(inFlightGauge,
+ InstrumentHandlerCounter(counter,
+ InstrumentHandlerDuration(histVec,
+ InstrumentHandlerTimeToWriteHeader(writeHeaderVec,
+ InstrumentHandlerResponseSize(responseSize, handler),
+ ),
+ ),
+ ),
+ )
+
+ r, _ := http.NewRequest("GET", "www.example.com", nil)
+ w := httptest.NewRecorder()
+ chain.ServeHTTP(w, r)
+}
+
+func TestInstrumentTimeToFirstWrite(t *testing.T) {
+ var i int
+ dobs := &responseWriterDelegator{
+ ResponseWriter: httptest.NewRecorder(),
+ observeWriteHeader: func(status int) {
+ i = status
+ },
+ }
+ d := newDelegator(dobs, nil)
+
+ d.WriteHeader(http.StatusOK)
+
+ if i != http.StatusOK {
+ t.Fatalf("failed to execute observeWriteHeader")
+ }
+}
+
+// testResponseWriter is an http.ResponseWriter that also implements
+// http.CloseNotifier, http.Flusher, and io.ReaderFrom.
+type testResponseWriter struct {
+ closeNotifyCalled, flushCalled, readFromCalled bool
+}
+
+func (t *testResponseWriter) Header() http.Header { return nil }
+func (t *testResponseWriter) Write([]byte) (int, error) { return 0, nil }
+func (t *testResponseWriter) WriteHeader(int) {}
+func (t *testResponseWriter) CloseNotify() <-chan bool {
+ t.closeNotifyCalled = true
+ return nil
+}
+func (t *testResponseWriter) Flush() { t.flushCalled = true }
+func (t *testResponseWriter) ReadFrom(io.Reader) (int64, error) {
+ t.readFromCalled = true
+ return 0, nil
+}
+
+func TestInterfaceUpgrade(t *testing.T) {
+ w := &testResponseWriter{}
+ d := newDelegator(w, nil)
+ d.(http.CloseNotifier).CloseNotify()
+ if !w.closeNotifyCalled {
+ t.Error("CloseNotify not called")
+ }
+ d.(http.Flusher).Flush()
+ if !w.flushCalled {
+ t.Error("Flush not called")
+ }
+ d.(io.ReaderFrom).ReadFrom(nil)
+ if !w.readFromCalled {
+ t.Error("ReadFrom not called")
+ }
+ if _, ok := d.(http.Hijacker); ok {
+ t.Error("delegator unexpectedly implements http.Hijacker")
+ }
+}
+
+func ExampleInstrumentHandlerDuration() {
+ inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
+ Name: "in_flight_requests",
+ Help: "A gauge of requests currently being served by the wrapped handler.",
+ })
+
+ counter := prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "api_requests_total",
+ Help: "A counter for requests to the wrapped handler.",
+ },
+ []string{"code", "method"},
+ )
+
+ // duration is partitioned by the HTTP method and handler. It uses custom
+ // buckets based on the expected request duration.
+ duration := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "request_duration_seconds",
+ Help: "A histogram of latencies for requests.",
+ Buckets: []float64{.25, .5, 1, 2.5, 5, 10},
+ },
+ []string{"handler", "method"},
+ )
+
+ // responseSize has no labels, making it a zero-dimensional
+ // ObserverVec.
+ responseSize := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "response_size_bytes",
+ Help: "A histogram of response sizes for requests.",
+ Buckets: []float64{200, 500, 900, 1500},
+ },
+ []string{},
+ )
+
+ // Create the handlers that will be wrapped by the middleware.
+ pushHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("Push"))
+ })
+ pullHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("Pull"))
+ })
+
+ // Register all of the metrics in the standard registry.
+ prometheus.MustRegister(inFlightGauge, counter, duration, responseSize)
+
+ // Instrument the handlers with all the metrics, injecting the "handler"
+ // label by currying.
+ pushChain := InstrumentHandlerInFlight(inFlightGauge,
+ InstrumentHandlerDuration(duration.MustCurryWith(prometheus.Labels{"handler": "push"}),
+ InstrumentHandlerCounter(counter,
+ InstrumentHandlerResponseSize(responseSize, pushHandler),
+ ),
+ ),
+ )
+ pullChain := InstrumentHandlerInFlight(inFlightGauge,
+ InstrumentHandlerDuration(duration.MustCurryWith(prometheus.Labels{"handler": "pull"}),
+ InstrumentHandlerCounter(counter,
+ InstrumentHandlerResponseSize(responseSize, pullHandler),
+ ),
+ ),
+ )
+
+ http.Handle("/metrics", Handler())
+ http.Handle("/push", pushChain)
+ http.Handle("/pull", pullChain)
+
+ if err := http.ListenAndServe(":3000", nil); err != nil {
+ log.Fatal(err)
+ }
+}