diff --git a/go.mod b/go.mod index 080630c10c6bf0d3b37699c7681c31f74b5388ff..adad229eaf5fc23a0e0200df38136f67c27c287a 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,6 @@ require ( github.com/googleapis/gax-go/v2 v2.0.5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/klauspost/compress v1.17.9 // indirect - github.com/kylelemons/godebug v1.1.0 // indirect github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/onsi/ginkgo v1.10.3 // indirect diff --git a/metrics/doc.go b/metrics/doc.go deleted file mode 100644 index ebfa6e782c5cf69e0d0490159d1d779ad6a8b321..0000000000000000000000000000000000000000 --- a/metrics/doc.go +++ /dev/null @@ -1,9 +0,0 @@ -/* -Package metrics is the primary entrypoint into LabKit's metrics utilities. - -# Provided Functionality - -- Provides http middlewares for recording metrics for an http server -- Provides a collector for sql.DBStats metrics -*/ -package metrics diff --git a/metrics/examples_test.go b/metrics/examples_test.go deleted file mode 100644 index fcc6c364ed65676e2db7caeefcc5c97a5a974e47..0000000000000000000000000000000000000000 --- a/metrics/examples_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package metrics_test - -import ( - "context" - "fmt" - "log" - "math/rand" - "net/http" - - "gitlab.com/gitlab-org/labkit/metrics" -) - -func ExampleNewHandlerFactory() { - // Tell prometheus to include a "route" label for the http metrics - newMetricHandlerFunc := metrics.NewHandlerFactory(metrics.WithLabels("route")) - - http.Handle("/foo", - newMetricHandlerFunc( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello foo") - }), - metrics.WithLabelValues(map[string]string{"route": "/foo"}), // Add instrumentation with a custom label of route="/foo" - )) - - http.Handle("/bar", - newMetricHandlerFunc( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello bar") - }), - metrics.WithLabelValues(map[string]string{"route": "/bar"}), // Add instrumentation with a custom label of route="/bar" - )) - - log.Fatal(http.ListenAndServe(":8080", nil)) -} - -// ExampleWithLabelFromContext shows how metrics can be configured to use dynamic labels pulled from the -// request context. -func ExampleWithLabelFromContext() { - // We'll store a random color in the request context using a custom key. - type ctxKeyType struct{} - - var colorKey = ctxKeyType{} - - // A simple middleware that assigns a random color and puts it in the request context. - injectColor := func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - color := "blue" - if rand.Intn(2) == 0 { - color = "red" - } - - ctx := context.WithValue(r.Context(), colorKey, color) - - log.Printf("Injected color: %s", color) - next.ServeHTTP(w, r.WithContext(ctx)) - }) - } - - // Create a metrics handler factory with a custom "color" label - newMetricHandlerFunc := metrics.NewHandlerFactory( - metrics.WithLabels("color"), - ) - - // Define final handler - finalHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello dynamic color!\n") - }) - - // Wrap final handler with LabKit instrumentation, telling it how to extract - // "color" from the request context. - instrumentedHandler := newMetricHandlerFunc( - finalHandler, - metrics.WithLabelFromContext("color", func(ctx context.Context) string { - if c, ok := ctx.Value(colorKey).(string); ok { - return c - } - - return "unknown" - }), - ) - - // Wrap the instrumented handler with injectColor - http.Handle("/color", injectColor(instrumentedHandler)) - - log.Println("Starting server on :8080 for label from context example...") - log.Fatal(http.ListenAndServe(":8080", nil)) -} diff --git a/metrics/handler.go b/metrics/handler.go deleted file mode 100644 index 5af354330cc24efc03ae91ce8d646e34e7131021..0000000000000000000000000000000000000000 --- a/metrics/handler.go +++ /dev/null @@ -1,116 +0,0 @@ -package metrics - -import ( - "net/http" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -// Metric names for the recorded metrics. -// These are the conventional names prometheus uses for these metrics. -const ( - httpInFlightRequestsMetricName = "in_flight_requests" - httpRequestsTotalMetricName = "requests_total" - httpRequestDurationSecondsMetricName = "request_duration_seconds" - httpRequestSizeBytesMetricName = "request_size_bytes" - httpResponseSizeBytesMetricName = "response_size_bytes" - httpTimeToWriteHeaderSecondsMetricName = "time_to_write_header_seconds" -) - -// HandlerFactory creates handler middleware instances. Created by NewHandlerFactory. -type HandlerFactory func(next http.Handler, opts ...HandlerOption) http.Handler - -// NewHandlerFactory will create a function for creating metric middlewares. -// The resulting function can be called multiple times to obtain multiple middleware -// instances. -// Each instance can be configured with different options that will be applied to the -// same underlying metrics. -func NewHandlerFactory(opts ...HandlerFactoryOption) HandlerFactory { - factoryConfig := applyHandlerFactoryOptions(opts) - - var ( - httpInFlightRequests = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: factoryConfig.namespace, - Subsystem: factoryConfig.subsystem, - Name: httpInFlightRequestsMetricName, - Help: "A gauge of requests currently being served by the http server.", - }) - - httpRequestsTotal = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Namespace: factoryConfig.namespace, - Subsystem: factoryConfig.subsystem, - Name: httpRequestsTotalMetricName, - Help: "A counter for requests to the http server.", - }, - factoryConfig.labels, - ) - - httpRequestDurationSeconds = prometheus.NewHistogramVec( - prometheus.HistogramOpts{ - Namespace: factoryConfig.namespace, - Subsystem: factoryConfig.subsystem, - Name: httpRequestDurationSecondsMetricName, - Help: "A histogram of latencies for requests to the http server.", - Buckets: factoryConfig.requestDurationBuckets, - }, - factoryConfig.labels, - ) - - httpRequestSizeBytes = prometheus.NewHistogramVec( - prometheus.HistogramOpts{ - Namespace: factoryConfig.namespace, - Subsystem: factoryConfig.subsystem, - Name: httpRequestSizeBytesMetricName, - Help: "A histogram of sizes of requests to the http server.", - Buckets: factoryConfig.byteSizeBuckets, - }, - factoryConfig.labels, - ) - - httpResponseSizeBytes = prometheus.NewHistogramVec( - prometheus.HistogramOpts{ - Namespace: factoryConfig.namespace, - Subsystem: factoryConfig.subsystem, - Name: httpResponseSizeBytesMetricName, - Help: "A histogram of response sizes for requests to the http server.", - Buckets: factoryConfig.byteSizeBuckets, - }, - factoryConfig.labels, - ) - - httpTimeToWriteHeaderSeconds = prometheus.NewHistogramVec( - prometheus.HistogramOpts{ - Namespace: factoryConfig.namespace, - Subsystem: factoryConfig.subsystem, - Name: httpTimeToWriteHeaderSecondsMetricName, - Help: "A histogram of request durations until the response headers are written.", - Buckets: factoryConfig.timeToWriteHeaderDurationBuckets, - }, - factoryConfig.labels, - ) - ) - - prometheus.MustRegister(httpInFlightRequests) - prometheus.MustRegister(httpRequestsTotal) - prometheus.MustRegister(httpRequestDurationSeconds) - prometheus.MustRegister(httpRequestSizeBytes) - prometheus.MustRegister(httpResponseSizeBytes) - prometheus.MustRegister(httpTimeToWriteHeaderSeconds) - - return func(next http.Handler, handlerOpts ...HandlerOption) http.Handler { - handlerConfig, promOpts := applyHandlerOptions(handlerOpts) - - handler := next - - handler = promhttp.InstrumentHandlerCounter(handlerConfig.applyOptionsToCounterVec(httpRequestsTotal), handler, promOpts...) - handler = promhttp.InstrumentHandlerDuration(handlerConfig.applyOptionsToObserverVec(httpRequestDurationSeconds), handler, promOpts...) - handler = promhttp.InstrumentHandlerInFlight(httpInFlightRequests, handler) - handler = promhttp.InstrumentHandlerRequestSize(handlerConfig.applyOptionsToObserverVec(httpRequestSizeBytes), handler, promOpts...) - handler = promhttp.InstrumentHandlerResponseSize(handlerConfig.applyOptionsToObserverVec(httpResponseSizeBytes), handler, promOpts...) - handler = promhttp.InstrumentHandlerTimeToWriteHeader(handlerConfig.applyOptionsToObserverVec(httpTimeToWriteHeaderSeconds), handler, promOpts...) - - return handler - } -} diff --git a/metrics/handler_factory_options.go b/metrics/handler_factory_options.go deleted file mode 100644 index 726d272d8a379ae3d5ed0d4b8945acaed7f7e7cc..0000000000000000000000000000000000000000 --- a/metrics/handler_factory_options.go +++ /dev/null @@ -1,93 +0,0 @@ -package metrics - -type handlerFactoryConfig struct { - namespace string - subsystem string - requestDurationBuckets []float64 - timeToWriteHeaderDurationBuckets []float64 - byteSizeBuckets []float64 - labels []string -} - -// HandlerFactoryOption is used to pass options in NewHandlerFactory. -type HandlerFactoryOption func(*handlerFactoryConfig) - -func applyHandlerFactoryOptions(opts []HandlerFactoryOption) handlerFactoryConfig { - config := handlerFactoryConfig{ - subsystem: "http", - requestDurationBuckets: []float64{ - 0.005, /* 5ms */ - 0.025, /* 25ms */ - 0.1, /* 100ms */ - 0.5, /* 500ms */ - 1.0, /* 1s */ - 10.0, /* 10s */ - 30.0, /* 30s */ - 60.0, /* 1m */ - 300.0, /* 5m */ - }, - timeToWriteHeaderDurationBuckets: []float64{ - 0.005, /* 5ms */ - 0.025, /* 25ms */ - 0.1, /* 100ms */ - 0.5, /* 500ms */ - 1.0, /* 1s */ - 10.0, /* 10s */ - 30.0, /* 30s */ - }, - byteSizeBuckets: []float64{ - 10, - 64, - 256, - 1024, /* 1KiB */ - 64 * 1024, /* 64KiB */ - 256 * 1024, /* 256KiB */ - 1024 * 1024, /* 1MiB */ - 64 * 1024 * 1024, /* 64MiB */ - }, - labels: []string{"code", "method"}, - } - for _, v := range opts { - v(&config) - } - - return config -} - -// WithNamespace will configure the namespace to apply to the metrics. -func WithNamespace(namespace string) HandlerFactoryOption { - return func(config *handlerFactoryConfig) { - config.namespace = namespace - } -} - -// WithLabels will configure additional labels to apply to the metrics. -func WithLabels(labels ...string) HandlerFactoryOption { - return func(config *handlerFactoryConfig) { - config.labels = append(config.labels, labels...) - } -} - -// WithRequestDurationBuckets will configure the duration buckets used for -// incoming request histogram buckets. -func WithRequestDurationBuckets(buckets []float64) HandlerFactoryOption { - return func(config *handlerFactoryConfig) { - config.requestDurationBuckets = buckets - } -} - -// WithTimeToWriteHeaderDurationBuckets will configure the time to write header -// duration histogram buckets. -func WithTimeToWriteHeaderDurationBuckets(buckets []float64) HandlerFactoryOption { - return func(config *handlerFactoryConfig) { - config.timeToWriteHeaderDurationBuckets = buckets - } -} - -// WithByteSizeBuckets will configure the byte size histogram buckets for request -// and response payloads. -func WithByteSizeBuckets(buckets []float64) HandlerFactoryOption { - return func(config *handlerFactoryConfig) { - config.byteSizeBuckets = buckets - } -} diff --git a/metrics/handler_options.go b/metrics/handler_options.go deleted file mode 100644 index f363d520516c7dfa9d0b4320d98f11379cecb5e1..0000000000000000000000000000000000000000 --- a/metrics/handler_options.go +++ /dev/null @@ -1,84 +0,0 @@ -package metrics - -import ( - "context" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -// LabelValueFromContext is used to compute the label value from request context. -// Context can be filled with values from request through middleware. -// This matches promhttp.LabelValueFromCtx. -type LabelValueFromContext func(ctx context.Context) string - -type labelValueFromContext struct { - name string - valueFn LabelValueFromContext -} - -type handlerConfig struct { - // labelValues contains static label values. - labelValues map[string]string - - // labelValuesFromContext contains dynamic label values - // which can be set by pull information from the - // context. - labelValuesFromContext []labelValueFromContext -} - -// HandlerOption is used to pass options to the HandlerFactory instance. -type HandlerOption func(*handlerConfig) - -// applyHandlerOptions applies the options, -// returns the config, plus a slice of promhttp.Option which can be used -// with the prometheus handlers. -func applyHandlerOptions(opts []HandlerOption) (handlerConfig, []promhttp.Option) { - config := handlerConfig{} - for _, v := range opts { - v(&config) - } - - var promOpts []promhttp.Option - - // Add prometheus options for labels from context - for _, v := range config.labelValuesFromContext { - promOpts = append(promOpts, promhttp.WithLabelFromCtx(v.name, promhttp.LabelValueFromCtx(v.valueFn))) - } - - return config, promOpts -} - -// applyOptionsToCounterVec will apply the options to a CounterVec instance. -func (c *handlerConfig) applyOptionsToCounterVec(cv *prometheus.CounterVec) *prometheus.CounterVec { - if len(c.labelValues) == 0 { - return cv - } - - return cv.MustCurryWith(c.labelValues) -} - -// applyOptionsToObserverVec will apply the options to an ObserverVec instance. -func (c *handlerConfig) applyOptionsToObserverVec(cv prometheus.ObserverVec) prometheus.ObserverVec { - if len(c.labelValues) == 0 { - return cv - } - - return cv.MustCurryWith(c.labelValues) -} - -// WithLabelValues will configure labels values to apply to this handler. -func WithLabelValues(labelValues map[string]string) HandlerOption { - return func(config *handlerConfig) { - config.labelValues = labelValues - } -} - -// WithLabelFromContext will configure labels values to apply to this handler, -// allowing for dynamic label values to be added to the standard metrics. -// This is equivalent to promhttp.WithLabelFromCtx. -func WithLabelFromContext(name string, valueFn LabelValueFromContext) HandlerOption { - return func(config *handlerConfig) { - config.labelValuesFromContext = append(config.labelValuesFromContext, labelValueFromContext{name, valueFn}) - } -} diff --git a/metrics/handler_test.go b/metrics/handler_test.go deleted file mode 100644 index baaea111ea52fafe7b69dd7f8515a552199d87a5..0000000000000000000000000000000000000000 --- a/metrics/handler_test.go +++ /dev/null @@ -1,248 +0,0 @@ -package metrics - -import ( - "context" - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/prometheus/client_golang/prometheus" - dto "github.com/prometheus/client_model/go" - "github.com/stretchr/testify/require" -) - -func TestNewHandlerFactory(t *testing.T) { - var dynamicLabelKey struct{} - - tests := []struct { - name string - opts []HandlerFactoryOption // factory-level options - handlerOpts []HandlerOption // handler-level options - injectCtx func(r *http.Request) *http.Request // optional context injection - wantLabels map[string]string // custom label key/values - }{ - { - name: "basic", - opts: []HandlerFactoryOption{ - WithNamespace("basic"), - }, - }, - { - name: "labels", - opts: []HandlerFactoryOption{ - WithNamespace("labels"), - WithLabels("label1", "label2"), - }, - handlerOpts: []HandlerOption{ - WithLabelValues(map[string]string{"label1": "1", "label2": "1"}), - }, - wantLabels: map[string]string{"label1": "1", "label2": "1"}, - }, - { - name: "dynamic_labels", - opts: []HandlerFactoryOption{ - WithNamespace("dynamic_labels"), - WithLabels("color"), - }, - handlerOpts: []HandlerOption{ - WithLabelFromContext("color", func(ctx context.Context) string { - if c, ok := ctx.Value(dynamicLabelKey).(string); ok { - return c - } - - return "unknown" - }), - }, - injectCtx: func(r *http.Request) *http.Request { - ctx := context.WithValue(r.Context(), dynamicLabelKey, "green") - return r.WithContext(ctx) - }, - wantLabels: map[string]string{"color": "green"}, - }, - { - name: "request_duration_buckets", - opts: []HandlerFactoryOption{ - WithNamespace("request_duration_buckets"), - WithRequestDurationBuckets([]float64{1, 2, 3}), - }, - }, - { - name: "time_to_write_header_duration_buckets", - opts: []HandlerFactoryOption{ - WithNamespace("time_to_write_header_duration_buckets"), - WithTimeToWriteHeaderDurationBuckets([]float64{1, 2, 3}), - }, - }, - { - name: "byte_bucket_size_buckets", - opts: []HandlerFactoryOption{ - WithNamespace("byte_bucket_size_buckets"), - WithByteSizeBuckets([]float64{1, 2, 3}), - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - factory := NewHandlerFactory(tt.opts...) - require.NotNil(t, factory) - - var invoked bool - - handler := factory( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - invoked = true - - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, "OK") - }), - tt.handlerOpts..., - ) - - r := httptest.NewRequest(http.MethodGet, "http://example.com", nil) - if tt.injectCtx != nil { - r = tt.injectCtx(r) - } - - // Serve the request - w := httptest.NewRecorder() - handler.ServeHTTP(w, r) - require.True(t, invoked, "handler was never invoked") - - // Gather metrics from the default registry - metrics, err := prometheus.DefaultGatherer.Gather() - require.NoError(t, err) - require.NotEmpty(t, metrics) - - keyedMetrics := make(map[string]*dto.MetricFamily, len(metrics)) - for _, v := range metrics { - keyedMetrics[v.GetName()] = v - } - - // For each test, the namespace is "tt.name", so we expect e.g. "basic_http_requests_total" - ns := tt.name + "_http_" - - // Since each test calls 1 request, we expect: - // - in-flight gauge => 0 after finishing - // - counters/histograms => sample count 1 - checkGauge(t, keyedMetrics, ns+httpInFlightRequestsMetricName, 0, nil) - checkCounter(t, keyedMetrics, ns+httpRequestsTotalMetricName, 1, tt.wantLabels) - checkHistogramCount(t, keyedMetrics, ns+httpRequestDurationSecondsMetricName, 1, tt.wantLabels) - checkHistogramCount(t, keyedMetrics, ns+httpRequestSizeBytesMetricName, 1, tt.wantLabels) - checkHistogramCount(t, keyedMetrics, ns+httpResponseSizeBytesMetricName, 1, tt.wantLabels) - checkHistogramCount(t, keyedMetrics, ns+httpTimeToWriteHeaderSecondsMetricName, 1, tt.wantLabels) - }) - } -} - -func checkGauge( - t *testing.T, - keyed map[string]*dto.MetricFamily, - name string, - wantVal float64, - wantLabels map[string]string, -) { - t.Helper() - - metric := getMetric(t, keyed, name, wantLabels) - g := metric.GetGauge() - require.NotNilf(t, g, "expected gauge metric for %q", name) - require.Equal(t, wantVal, g.GetValue()) -} - -func checkCounter( - t *testing.T, - keyed map[string]*dto.MetricFamily, - name string, - wantVal float64, - wantLabels map[string]string, -) { - t.Helper() - - metric := getMetric(t, keyed, name, wantLabels) - c := metric.GetCounter() - require.NotNilf(t, c, "expected counter metric for %q", name) - require.Equal(t, wantVal, c.GetValue()) -} - -func checkHistogramCount( - t *testing.T, - keyed map[string]*dto.MetricFamily, - name string, - wantCount uint64, - wantLabels map[string]string, -) { - t.Helper() - - metric := getMetric(t, keyed, name, wantLabels) - h := metric.GetHistogram() - require.NotNilf(t, h, "expected histogram metric for %q", name) - require.Equal(t, wantCount, h.GetSampleCount()) -} - -// getMetric looks up the MetricFamily by name, finds the sample -// matching wantLabels, and returns that single *dto.Metric for further checks. -func getMetric( - t *testing.T, - keyed map[string]*dto.MetricFamily, - name string, - wantLabels map[string]string, -) *dto.Metric { - t.Helper() - - mf := keyed[name] - require.NotNilf(t, mf, "no metric named %q", name) - - m := findMetricWithLabels(t, mf.GetMetric(), wantLabels) - require.NotNilf(t, m, "no metric matching labels %v for %q", wantLabels, name) - - return m -} - -// findMetricWithLabels returns the first metric whose label set matches -// all of wantLabels. If wantLabels is empty, returns the first metric. -func findMetricWithLabels( - t *testing.T, - metrics []*dto.Metric, - wantLabels map[string]string, -) *dto.Metric { - t.Helper() - - if len(wantLabels) == 0 { - // No label constraints => return the first if available - if len(metrics) > 0 { - return metrics[0] - } - - return nil - } - - for _, m := range metrics { - if hasAllLabels(m.GetLabel(), wantLabels) { - return m - } - } - - return nil -} - -// hasAllLabels checks whether labelPairs contain all key=val pairs in wantLabels. -func hasAllLabels(labelPairs []*dto.LabelPair, wantLabels map[string]string) bool { - for k, v := range wantLabels { - match := false - - for _, lp := range labelPairs { - if lp.GetName() == k && lp.GetValue() == v { - match = true - break - } - } - - if !match { - return false - } - } - - return true -} diff --git a/metrics/http_round_tripper/factory_options.go b/metrics/http_round_tripper/factory_options.go deleted file mode 100644 index 0d2a707ab409e51b3e3e4d4b416e6524cc90854e..0000000000000000000000000000000000000000 --- a/metrics/http_round_tripper/factory_options.go +++ /dev/null @@ -1,56 +0,0 @@ -package http_round_tripper - -type factoryConfig struct { - namespace string - subsystem string - requestDurationBuckets []float64 - labels []string -} - -// FactoryOption is used to pass options in NewFactory. -type FactoryOption func(*factoryConfig) - -func applyFactoryOptions(opts []FactoryOption) factoryConfig { - config := factoryConfig{ - subsystem: "http", - requestDurationBuckets: []float64{ - 0.005, /* 5ms */ - 0.025, /* 25ms */ - 0.1, /* 100ms */ - 0.5, /* 500ms */ - 1.0, /* 1s */ - 10.0, /* 10s */ - 30.0, /* 30s */ - 60.0, /* 1m */ - 300.0, /* 5m */ - }, - labels: []string{"code", "method"}, - } - for _, v := range opts { - v(&config) - } - - return config -} - -// WithNamespace will configure the namespace to apply to the metrics. -func WithNamespace(namespace string) FactoryOption { - return func(config *factoryConfig) { - config.namespace = namespace - } -} - -// WithLabels will configure additional labels to apply to the metrics. -func WithLabels(labels ...string) FactoryOption { - return func(config *factoryConfig) { - config.labels = append(config.labels, labels...) - } -} - -// WithRequestDurationBuckets will configure the duration buckets used for -// incoming request histogram buckets. -func WithRequestDurationBuckets(buckets []float64) FactoryOption { - return func(config *factoryConfig) { - config.requestDurationBuckets = buckets - } -} diff --git a/metrics/http_round_tripper/http_round_tripper.go b/metrics/http_round_tripper/http_round_tripper.go deleted file mode 100644 index b3a9be12f62949152183308e3325ede1d05de9db..0000000000000000000000000000000000000000 --- a/metrics/http_round_tripper/http_round_tripper.go +++ /dev/null @@ -1,70 +0,0 @@ -package http_round_tripper - -import ( - "net/http" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -// Metric names for the recorded metrics. -// These are the conventional names prometheus uses for these metrics. -const ( - inFlightRequestsMetricName = "in_flight_requests" - requestsTotalMetricName = "requests_total" - requestDurationSecondsMetricName = "request_duration_seconds" -) - -// Factory creates middleware instances. Created by NewFactory. -type Factory func(next http.RoundTripper, opts ...Option) http.RoundTripper - -// NewFactory will create a function for creating metric middlewares. -// The resulting function can be called multiple times to obtain multiple middleware -// instances. -// Each instance can be configured with different options that will be applied to the -// same underlying metrics. -func NewFactory(opts ...FactoryOption) Factory { - config := applyFactoryOptions(opts) - - inFlightRequests := prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: config.namespace, - Subsystem: config.subsystem, - Name: inFlightRequestsMetricName, - Help: "A gauge of requests currently being handled.", - }) - - requestsTotal := prometheus.NewCounterVec( - prometheus.CounterOpts{ - Namespace: config.namespace, - Subsystem: config.subsystem, - Name: requestsTotalMetricName, - Help: "A counter for total number of requests.", - }, - config.labels, - ) - - requestDurationSeconds := prometheus.NewHistogramVec( - prometheus.HistogramOpts{ - Namespace: config.namespace, - Subsystem: config.subsystem, - Name: requestDurationSecondsMetricName, - Help: "A histogram of latencies for requests.", - Buckets: config.requestDurationBuckets, - }, - config.labels, - ) - - prometheus.MustRegister(inFlightRequests, requestsTotal, requestDurationSeconds) - - return func(next http.RoundTripper, opts ...Option) http.RoundTripper { - config := applyOptions(opts) - - rt := next - - rt = promhttp.InstrumentRoundTripperCounter(requestsTotal.MustCurryWith(config.labelValues), rt) - rt = promhttp.InstrumentRoundTripperDuration(requestDurationSeconds.MustCurryWith(config.labelValues), rt) - rt = promhttp.InstrumentRoundTripperInFlight(inFlightRequests, rt) - - return rt - } -} diff --git a/metrics/http_round_tripper/http_round_tripper_test.go b/metrics/http_round_tripper/http_round_tripper_test.go deleted file mode 100644 index 58f950b19cdf72097e4f53cf818bd4e82328ded3..0000000000000000000000000000000000000000 --- a/metrics/http_round_tripper/http_round_tripper_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package http_round_tripper - -import ( - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/testutil" - "github.com/stretchr/testify/require" -) - -type roundTripFunc func(req *http.Request) *http.Response - -func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { - return f(req), nil -} - -func TestNewFactory(t *testing.T) { - factoryOpts := []FactoryOption{ - WithNamespace("namespace"), - WithLabels("label1", "label2"), - WithRequestDurationBuckets([]float64{1, 2, 3}), - } - - opts := []Option{ - WithLabelValues(map[string]string{"label1": "1", "label2": "1"}), - } - - applyMetrics := NewFactory(factoryOpts...) - - var invoked bool - - roundTripper := applyMetrics(roundTripFunc(func(req *http.Request) *http.Response { - invoked = true - - return &http.Response{} - }), opts...) - - r := httptest.NewRequest(http.MethodGet, "http://example.com", nil) - roundTripper.RoundTrip(r) - - metrics := []string{ - "namespace_http_" + inFlightRequestsMetricName, - "namespace_http_" + requestsTotalMetricName, - } - - expected := strings.NewReader(` - # HELP namespace_http_in_flight_requests A gauge of requests currently being handled. - # TYPE namespace_http_in_flight_requests gauge - namespace_http_in_flight_requests 0 - # HELP namespace_http_requests_total A counter for total number of requests. - # TYPE namespace_http_requests_total counter - namespace_http_requests_total{code="200",label1="1",label2="1",method="get"} 1 - `) - err := testutil.GatherAndCompare(prometheus.DefaultGatherer, expected, metrics...) - require.NoError(t, err) - - // Duration value is unreliable, so let's just check that the metric is counted - count, err := testutil.GatherAndCount(prometheus.DefaultGatherer, "namespace_http_"+requestDurationSecondsMetricName) - require.NoError(t, err) - require.Equal(t, 1, count) - - require.True(t, invoked, "request was not executed") -} diff --git a/metrics/http_round_tripper/options.go b/metrics/http_round_tripper/options.go deleted file mode 100644 index 9c23f1fc11f2c396b1056b5f1cbe6db5d37bdb1d..0000000000000000000000000000000000000000 --- a/metrics/http_round_tripper/options.go +++ /dev/null @@ -1,24 +0,0 @@ -package http_round_tripper - -type Config struct { - labelValues map[string]string -} - -// Option is used to pass options to the Factory instance. -type Option func(*Config) - -func applyOptions(opts []Option) Config { - config := Config{} - for _, v := range opts { - v(&config) - } - - return config -} - -// WithLabelValues will configure labels values to apply to this round tripper. -func WithLabelValues(labelValues map[string]string) Option { - return func(config *Config) { - config.labelValues = labelValues - } -} diff --git a/metrics/sqlmetrics/dbstats.go b/metrics/sqlmetrics/dbstats.go deleted file mode 100644 index 51f508970699132560881163e721c564990746d4..0000000000000000000000000000000000000000 --- a/metrics/sqlmetrics/dbstats.go +++ /dev/null @@ -1,212 +0,0 @@ -package sqlmetrics - -import ( - "database/sql" - - "github.com/prometheus/client_golang/prometheus" -) - -const ( - namespace = "go_sql_dbstats" - subsystem = "connections" - dbNameLabel = "db_name" - - // Names for the recorded metrics. - maxOpenConnectionsName = "max_open" - openConnectionsName = "open" - inUseName = "in_use" - idleName = "idle" - waitCountName = "waits_total" - waitDurationName = "wait_seconds_total" - maxIdleClosedName = "max_idle_closed_count_total" - maxIdleTimeClosedName = "max_idle_time_closed_count_total" - maxLifetimeClosedName = "max_lifetime_closed_count_total" - - // Descriptions for the recorded metrics. - maxOpenConnectionsDesc = "The limit of open connections to the database." - openConnectionsDesc = "The number of established connections both in use and idle." - inUseDesc = "The number of connections currently in use." - idleDesc = "The number of idle connections." - waitCountDesc = "The total number of connections waited for." - waitDurationDesc = "The total time blocked waiting for a new connection." - maxIdleClosedDesc = "The total number of connections closed due to SetMaxIdleConns." - maxIdleTimeClosedDesc = "The total number of connections closed due to SetConnMaxIdleTime." - maxLifetimeClosedDesc = "The total number of connections closed due to SetConnMaxLifetime." -) - -// DBStatsGetter is an interface for sql.DBStats. It's implemented by sql.DB. -type DBStatsGetter interface { - Stats() sql.DBStats -} - -// DBStatsCollector implements the prometheus.Collector interface. -type DBStatsCollector struct { - sg DBStatsGetter - - maxOpenDesc *prometheus.Desc - openDesc *prometheus.Desc - inUseDesc *prometheus.Desc - idleDesc *prometheus.Desc - waitCountDesc *prometheus.Desc - waitDurationDesc *prometheus.Desc - maxIdleClosedDesc *prometheus.Desc - maxIdleTimeClosedDesc *prometheus.Desc - maxLifetimeClosedDesc *prometheus.Desc -} - -// Describe implements the prometheus.Collector interface. -func (c *DBStatsCollector) Describe(ch chan<- *prometheus.Desc) { - ch <- c.maxOpenDesc - ch <- c.openDesc - ch <- c.inUseDesc - ch <- c.idleDesc - ch <- c.waitCountDesc - ch <- c.waitDurationDesc - ch <- c.maxIdleClosedDesc - ch <- c.maxIdleTimeClosedDesc - ch <- c.maxLifetimeClosedDesc -} - -// Collect implements the prometheus.Collector interface. -func (c *DBStatsCollector) Collect(ch chan<- prometheus.Metric) { - stats := c.sg.Stats() - - ch <- prometheus.MustNewConstMetric( - c.maxOpenDesc, - prometheus.GaugeValue, - float64(stats.MaxOpenConnections), - ) - ch <- prometheus.MustNewConstMetric( - c.openDesc, - prometheus.GaugeValue, - float64(stats.OpenConnections), - ) - ch <- prometheus.MustNewConstMetric( - c.inUseDesc, - prometheus.GaugeValue, - float64(stats.InUse), - ) - ch <- prometheus.MustNewConstMetric( - c.idleDesc, - prometheus.GaugeValue, - float64(stats.Idle), - ) - ch <- prometheus.MustNewConstMetric( - c.waitCountDesc, - prometheus.CounterValue, - float64(stats.WaitCount), - ) - ch <- prometheus.MustNewConstMetric( - c.waitDurationDesc, - prometheus.CounterValue, - stats.WaitDuration.Seconds(), - ) - ch <- prometheus.MustNewConstMetric( - c.maxIdleClosedDesc, - prometheus.CounterValue, - float64(stats.MaxIdleClosed), - ) - ch <- prometheus.MustNewConstMetric( - c.maxIdleTimeClosedDesc, - prometheus.CounterValue, - float64(stats.MaxIdleTimeClosed), - ) - ch <- prometheus.MustNewConstMetric( - c.maxLifetimeClosedDesc, - prometheus.CounterValue, - float64(stats.MaxLifetimeClosed), - ) -} - -type dbStatsCollectorConfig struct { - extraLabels prometheus.Labels -} - -// DBStatsCollectorOption is used to pass options in NewDBStatsCollector. -type DBStatsCollectorOption func(*dbStatsCollectorConfig) - -// WithExtraLabels will configure extra label values to apply to the DBStats metrics. -// A label named db_name will be ignored, as this is set internally. -func WithExtraLabels(labelValues map[string]string) DBStatsCollectorOption { - return func(config *dbStatsCollectorConfig) { - config.extraLabels = labelValues - } -} - -func applyDBStatsCollectorOptions(opts []DBStatsCollectorOption) dbStatsCollectorConfig { - config := dbStatsCollectorConfig{} - for _, v := range opts { - v(&config) - } - - return config -} - -// NewDBStatsCollector creates a new DBStatsCollector. -func NewDBStatsCollector(dbName string, sg DBStatsGetter, opts ...DBStatsCollectorOption) *DBStatsCollector { - config := applyDBStatsCollectorOptions(opts) - - if config.extraLabels == nil { - config.extraLabels = make(prometheus.Labels) - } - - config.extraLabels[dbNameLabel] = dbName - - return &DBStatsCollector{ - sg: sg, - maxOpenDesc: prometheus.NewDesc( - prometheus.BuildFQName(namespace, subsystem, maxOpenConnectionsName), - maxOpenConnectionsDesc, - nil, - config.extraLabels, - ), - openDesc: prometheus.NewDesc( - prometheus.BuildFQName(namespace, subsystem, openConnectionsName), - openConnectionsDesc, - nil, - config.extraLabels, - ), - inUseDesc: prometheus.NewDesc( - prometheus.BuildFQName(namespace, subsystem, inUseName), - inUseDesc, - nil, - config.extraLabels, - ), - idleDesc: prometheus.NewDesc( - prometheus.BuildFQName(namespace, subsystem, idleName), - idleDesc, - nil, - config.extraLabels, - ), - waitCountDesc: prometheus.NewDesc( - prometheus.BuildFQName(namespace, subsystem, waitCountName), - waitCountDesc, - nil, - config.extraLabels, - ), - waitDurationDesc: prometheus.NewDesc( - prometheus.BuildFQName(namespace, subsystem, waitDurationName), - waitDurationDesc, - nil, - config.extraLabels, - ), - maxIdleClosedDesc: prometheus.NewDesc( - prometheus.BuildFQName(namespace, subsystem, maxIdleClosedName), - maxIdleClosedDesc, - nil, - config.extraLabels, - ), - maxIdleTimeClosedDesc: prometheus.NewDesc( - prometheus.BuildFQName(namespace, subsystem, maxIdleTimeClosedName), - maxIdleTimeClosedDesc, - nil, - config.extraLabels, - ), - maxLifetimeClosedDesc: prometheus.NewDesc( - prometheus.BuildFQName(namespace, subsystem, maxLifetimeClosedName), - maxLifetimeClosedDesc, - nil, - config.extraLabels, - ), - } -} diff --git a/metrics/sqlmetrics/dbstats_test.go b/metrics/sqlmetrics/dbstats_test.go deleted file mode 100644 index 8d2e43914a9a94a634974ee2f99b1713ce0ab897..0000000000000000000000000000000000000000 --- a/metrics/sqlmetrics/dbstats_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package sqlmetrics - -import ( - "bytes" - "database/sql" - "fmt" - "html/template" - "testing" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/testutil" - "github.com/stretchr/testify/require" -) - -type dbMock sql.DBStats - -func (db dbMock) Stats() sql.DBStats { - return sql.DBStats(db) -} - -func TestNewDBStatsCollector(t *testing.T) { - db := &dbMock{ - MaxOpenConnections: 100, - OpenConnections: 10, - InUse: 3, - Idle: 7, - WaitCount: 5, - WaitDuration: 12, - MaxIdleClosed: 7, - MaxIdleTimeClosed: 6, - MaxLifetimeClosed: 8, - } - - dbName := "foo" - defaultLabels := prometheus.Labels{dbNameLabel: dbName} - - tests := []struct { - name string - opts []DBStatsCollectorOption - expectedLabels prometheus.Labels - }{ - { - name: "default", - expectedLabels: defaultLabels, - }, - { - name: "with custom labels", - opts: []DBStatsCollectorOption{ - WithExtraLabels(map[string]string{"x": "y"}), - }, - expectedLabels: prometheus.Labels{ - dbNameLabel: dbName, - "x": "y", - }, - }, - { - name: "does not override db_name label", - opts: []DBStatsCollectorOption{ - WithExtraLabels(map[string]string{"db_name": "bar"}), - }, - expectedLabels: defaultLabels, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - collector := NewDBStatsCollector(dbName, db, tt.opts...) - - validateMetric(t, collector, maxOpenConnectionsName, maxOpenConnectionsDesc, "gauge", float64(db.MaxOpenConnections), tt.expectedLabels) - validateMetric(t, collector, openConnectionsName, openConnectionsDesc, "gauge", float64(db.OpenConnections), tt.expectedLabels) - validateMetric(t, collector, inUseName, inUseDesc, "gauge", float64(db.InUse), tt.expectedLabels) - validateMetric(t, collector, idleName, idleDesc, "gauge", float64(db.Idle), tt.expectedLabels) - validateMetric(t, collector, waitCountName, waitCountDesc, "counter", float64(db.WaitCount), tt.expectedLabels) - validateMetric(t, collector, waitDurationName, waitDurationDesc, "counter", db.WaitDuration.Seconds(), tt.expectedLabels) - validateMetric(t, collector, maxIdleClosedName, maxIdleClosedDesc, "counter", float64(db.MaxIdleClosed), tt.expectedLabels) - validateMetric(t, collector, maxIdleTimeClosedName, maxIdleTimeClosedDesc, "counter", float64(db.MaxIdleTimeClosed), tt.expectedLabels) - validateMetric(t, collector, maxLifetimeClosedName, maxLifetimeClosedDesc, "counter", float64(db.MaxLifetimeClosed), tt.expectedLabels) - }) - } -} - -type labelsIter struct { - Dict prometheus.Labels - Counter int -} - -func (l *labelsIter) HasMore() bool { - l.Counter++ - return l.Counter < len(l.Dict) -} - -func validateMetric(t *testing.T, collector prometheus.Collector, name string, desc string, valueType string, value float64, labels prometheus.Labels) { - t.Helper() - - tmpl := template.New("") - tmpl.Delims("[[", "]]") - - txt := ` -# HELP [[.Name]] [[.Desc]] -# TYPE [[.Name]] [[.Type]] -[[.Name]]{[[range $k, $v := .Labels.Dict]][[$k]]="[[$v]]"[[if $.Labels.HasMore]],[[end]][[end]]} [[.Value]] -` - _, err := tmpl.Parse(txt) - require.NoError(t, err) - - var expected bytes.Buffer - - fullName := fmt.Sprintf("%s_%s_%s", namespace, subsystem, name) - - err = tmpl.Execute(&expected, struct { - Name string - Desc string - Type string - Value float64 - Labels *labelsIter - }{ - Name: fullName, - Desc: desc, - Labels: &labelsIter{Dict: labels}, - Value: value, - Type: valueType, - }) - require.NoError(t, err) - - err = testutil.CollectAndCompare(collector, &expected, fullName) - require.NoError(t, err) -} diff --git a/metrics/sqlmetrics/examples_test.go b/metrics/sqlmetrics/examples_test.go deleted file mode 100644 index 63ff27ec4de132276dbe51fb9f20ab13d7b241e3..0000000000000000000000000000000000000000 --- a/metrics/sqlmetrics/examples_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package sqlmetrics_test - -import ( - "database/sql" - - "github.com/prometheus/client_golang/prometheus" - "gitlab.com/gitlab-org/labkit/metrics/sqlmetrics" -) - -func ExampleNewDBStatsCollector() { - // Open connection to database - db, err := sql.Open("postgres", "postgres://postgres:postgres@localhost:5432/mydb") - if err != nil { - panic(err) - } - - // Create a new collector - collector := sqlmetrics.NewDBStatsCollector("mydb", db) - - // Register collector with Prometheus - prometheus.MustRegister(collector) -}