From 0e56788133c47c11c3eeaead6469afe12ab2fb3e Mon Sep 17 00:00:00 2001 From: e_forbes Date: Wed, 10 Sep 2025 10:55:26 +0100 Subject: [PATCH] chore: deletes the unused metrics package Based on my searches, I've been unable to find any users of this package. As such, I'm opting to remove it so that we can be more intentional about how any prospective metrics packages would look in the future. --- go.mod | 1 - metrics/doc.go | 9 - metrics/examples_test.go | 87 ------ metrics/handler.go | 116 -------- metrics/handler_factory_options.go | 93 ------- metrics/handler_options.go | 84 ------ metrics/handler_test.go | 248 ------------------ metrics/http_round_tripper/factory_options.go | 56 ---- .../http_round_tripper/http_round_tripper.go | 70 ----- .../http_round_tripper_test.go | 66 ----- metrics/http_round_tripper/options.go | 24 -- metrics/sqlmetrics/dbstats.go | 212 --------------- metrics/sqlmetrics/dbstats_test.go | 126 --------- metrics/sqlmetrics/examples_test.go | 22 -- 14 files changed, 1214 deletions(-) delete mode 100644 metrics/doc.go delete mode 100644 metrics/examples_test.go delete mode 100644 metrics/handler.go delete mode 100644 metrics/handler_factory_options.go delete mode 100644 metrics/handler_options.go delete mode 100644 metrics/handler_test.go delete mode 100644 metrics/http_round_tripper/factory_options.go delete mode 100644 metrics/http_round_tripper/http_round_tripper.go delete mode 100644 metrics/http_round_tripper/http_round_tripper_test.go delete mode 100644 metrics/http_round_tripper/options.go delete mode 100644 metrics/sqlmetrics/dbstats.go delete mode 100644 metrics/sqlmetrics/dbstats_test.go delete mode 100644 metrics/sqlmetrics/examples_test.go diff --git a/go.mod b/go.mod index 080630c1..adad229e 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 ebfa6e78..00000000 --- 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 fcc6c364..00000000 --- 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 5af35433..00000000 --- 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 726d272d..00000000 --- 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 f363d520..00000000 --- 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 baaea111..00000000 --- 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 0d2a707a..00000000 --- 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 b3a9be12..00000000 --- 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 58f950b1..00000000 --- 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 9c23f1fc..00000000 --- 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 51f50897..00000000 --- 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 8d2e4391..00000000 --- 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 63ff27ec..00000000 --- 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) -} -- GitLab