From 5b52b012e1c5c3a983e329a7fae7a0c4b92612b2 Mon Sep 17 00:00:00 2001 From: Eugene Date: Fri, 20 Sep 2024 11:13:27 +0300 Subject: [PATCH] promauto: make it available to avoid reflect when building labels Signed-off-by: Eugene --- prometheus/promsafe/safe.go | 20 ++++++++++++++++++- prometheus/promsafe/safe_test.go | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/prometheus/promsafe/safe.go b/prometheus/promsafe/safe.go index 5caac20..ca930b3 100644 --- a/prometheus/promsafe/safe.go +++ b/prometheus/promsafe/safe.go @@ -43,6 +43,13 @@ func SetupGlobalPromauto(factoryArg ...promauto.Factory) { } } +// CustomLabelsProvider is an interface that allows to convert anything to a prometheus.Labels +// It allows to provide your own FAST implementation of Struct->prometheus.Labels conversion +// without using reflection. +type CustomLabelsProvider interface { + ToPrometheusLabels() prometheus.Labels +} + // promsafeTag is the tag name used for promsafe labels inside structs. // The tag is optional, as if not present, field is used with snake_cased FieldName. // It's useful to use a tag when you want to override the default naming or exclude a field from the metric. @@ -273,6 +280,10 @@ func extractLabelsWithValues(labelProvider labelsProviderMarker) prometheus.Labe return nil } + if clp, ok := labelProvider.(CustomLabelsProvider); ok { + return clp.ToPrometheusLabels() + } + // TODO: let's handle defaults as well, why not? // Here, then, it can be only a struct, that is a parent of StructLabelProvider @@ -291,13 +302,20 @@ func extractLabelValues(labelProvider labelsProviderMarker) []string { } // extractLabelNames extracts labels names from a given labelsProviderMarker (parent instance of aStructLabelProvider) +// Deprecated: refactor is required. Order of result slice is not guaranteed. func extractLabelNames(labelProvider labelsProviderMarker) []string { if any(labelProvider) == nil { return nil } + var labels prometheus.Labels + if clp, ok := labelProvider.(CustomLabelsProvider); ok { + labels = clp.ToPrometheusLabels() + } else { + labels = extractLabelFromStruct(labelProvider) + } + // Here, then, it can be only a struct, that is a parent of StructLabelProvider - labels := extractLabelFromStruct(labelProvider) labelNames := make([]string, 0, len(labels)) for k := range labels { labelNames = append(labelNames, k) diff --git a/prometheus/promsafe/safe_test.go b/prometheus/promsafe/safe_test.go index 56b3330..9aaecea 100644 --- a/prometheus/promsafe/safe_test.go +++ b/prometheus/promsafe/safe_test.go @@ -21,6 +21,10 @@ import ( "github.com/prometheus/client_golang/prometheus/promsafe" ) +// Important: This is not a test file. These are only examples! +// These can be considered smoke tests, but nothing more. +// TODO: Write real tests + func ExampleNewCounterVecT_multiple_labels_manual() { // Manually registering with multiple labels @@ -140,6 +144,35 @@ func ExampleNewCounterVecT_pointer_to_labels_promauto() { // Output: } +// FastMyLabels is a struct that will have a custom method that converts to prometheus.Labels +type FastMyLabels struct { + promsafe.StructLabelProvider + EventType string + Source string +} + +// ToPrometheusLabels does a fast conversion to labels. No reflection involved. +func (f FastMyLabels) ToPrometheusLabels() prometheus.Labels { + return prometheus.Labels{"event_type": f.EventType, "source": f.Source} +} + +func ExampleNewCounterVecT_fast_safe_labels_provider() { + // It's possible to use pointer to labels struct + myReg := prometheus.NewRegistry() + + counterOpts := prometheus.CounterOpts{ + Name: "items_counted_fast", + } + + c := promsafe.WithAuto[FastMyLabels](myReg).NewCounterVecT(counterOpts) + + c.With(FastMyLabels{ + EventType: "reservation", Source: "source1", + }).Inc() + + // Output: +} + func ExampleNewCounterVecT_single_label_manual() { // Manually registering with a single label // Example of usage of shorthand: no structs no generics, but one string only