promsafe: improvements. ability to add fast implementation of toLabelNames
Signed-off-by: Eugene <eugene@amberpixels.io>
This commit is contained in:
parent
5b52b012e1
commit
c52d495061
|
@ -50,6 +50,13 @@ type CustomLabelsProvider interface {
|
||||||
ToPrometheusLabels() prometheus.Labels
|
ToPrometheusLabels() prometheus.Labels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CustomLabelNamesProvider is an interface that allows to convert anything to an order list of label names
|
||||||
|
// Result of it is considered to be when registering new metric. As we need proper ORDERED list of label names
|
||||||
|
// It allows to provide your own FAST implementation of Struct->[]string conversion without any reflection
|
||||||
|
type CustomLabelNamesProvider interface {
|
||||||
|
ToLabelNames() []string
|
||||||
|
}
|
||||||
|
|
||||||
// promsafeTag is the tag name used for promsafe labels inside structs.
|
// 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.
|
// 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.
|
// It's useful to use a tag when you want to override the default naming or exclude a field from the metric.
|
||||||
|
@ -108,9 +115,10 @@ type CounterVecT[T labelsProviderMarker] struct {
|
||||||
inner *prometheus.CounterVec
|
inner *prometheus.CounterVec
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMetricWithLabelValues behaves like prometheus.CounterVec.GetMetricWithLabelValues but with type-safe labels.
|
// GetMetricWithLabelValues covers prometheus.CounterVec.GetMetricWithLabelValues
|
||||||
func (c *CounterVecT[T]) GetMetricWithLabelValues(labels T) (prometheus.Counter, error) {
|
// Deprecated: Use GetMetricWith() instead. We can't provide a []string safe implementation in promsafe
|
||||||
return c.inner.GetMetricWithLabelValues(extractLabelValues(labels)...)
|
func (c *CounterVecT[T]) GetMetricWithLabelValues(lvs ...string) (prometheus.Counter, error) {
|
||||||
|
panic("There can't be a SAFE GetMetricWithLabelValues(). Use GetMetricWith() instead")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMetricWith behaves like prometheus.CounterVec.GetMetricWith but with type-safe labels.
|
// GetMetricWith behaves like prometheus.CounterVec.GetMetricWith but with type-safe labels.
|
||||||
|
@ -118,9 +126,10 @@ func (c *CounterVecT[T]) GetMetricWith(labels T) (prometheus.Counter, error) {
|
||||||
return c.inner.GetMetricWith(extractLabelsWithValues(labels))
|
return c.inner.GetMetricWith(extractLabelsWithValues(labels))
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLabelValues behaves like prometheus.CounterVec.WithLabelValues but with type-safe labels.
|
// WithLabelValues covers like prometheus.CounterVec.WithLabelValues.
|
||||||
func (c *CounterVecT[T]) WithLabelValues(labels T) prometheus.Counter {
|
// Deprecated: Use With() instead. We can't provide a []string safe implementation in promsafe
|
||||||
return c.inner.WithLabelValues(extractLabelValues(labels)...)
|
func (c *CounterVecT[T]) WithLabelValues(lvs ...string) prometheus.Counter {
|
||||||
|
panic("There can't be a SAFE WithLabelValues(). Use With() instead")
|
||||||
}
|
}
|
||||||
|
|
||||||
// With behaves like prometheus.CounterVec.With but with type-safe labels.
|
// With behaves like prometheus.CounterVec.With but with type-safe labels.
|
||||||
|
@ -194,6 +203,7 @@ func (c *CounterVecT1) GetMetricWithLabelValues(labelValue string) (prometheus.C
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMetricWith behaves like prometheus.CounterVec.GetMetricWith but with type-safe labels.
|
// GetMetricWith behaves like prometheus.CounterVec.GetMetricWith but with type-safe labels.
|
||||||
|
// We keep this function for consistency. Actually is useless and it's the same as GetMetricWithLabelValues
|
||||||
func (c *CounterVecT1) GetMetricWith(labelValue string) (prometheus.Counter, error) {
|
func (c *CounterVecT1) GetMetricWith(labelValue string) (prometheus.Counter, error) {
|
||||||
return c.inner.GetMetricWith(prometheus.Labels{c.labelName: labelValue})
|
return c.inner.GetMetricWith(prometheus.Labels{c.labelName: labelValue})
|
||||||
}
|
}
|
||||||
|
@ -204,6 +214,7 @@ func (c *CounterVecT1) WithLabelValues(labelValue string) prometheus.Counter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// With behaves like prometheus.CounterVec.With but with type-safe labels.
|
// With behaves like prometheus.CounterVec.With but with type-safe labels.
|
||||||
|
// We keep this function for consistency. Actually is useless and it's the same as WithLabelValues
|
||||||
func (c *CounterVecT1) With(labelValue string) prometheus.Counter {
|
func (c *CounterVecT1) With(labelValue string) prometheus.Counter {
|
||||||
return c.inner.With(prometheus.Labels{c.labelName: labelValue})
|
return c.inner.With(prometheus.Labels{c.labelName: labelValue})
|
||||||
}
|
}
|
||||||
|
@ -284,42 +295,45 @@ func extractLabelsWithValues(labelProvider labelsProviderMarker) prometheus.Labe
|
||||||
return clp.ToPrometheusLabels()
|
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
|
// Here, then, it can be only a struct, that is a parent of StructLabelProvider
|
||||||
return extractLabelFromStruct(labelProvider)
|
return extractLabelFromStruct(labelProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractLabelValues extracts label string values from a given labelsProviderMarker (parent instance of aStructLabelProvider)
|
|
||||||
func extractLabelValues(labelProvider labelsProviderMarker) []string {
|
|
||||||
m := extractLabelsWithValues(labelProvider)
|
|
||||||
|
|
||||||
labelValues := make([]string, 0, len(m))
|
|
||||||
for _, v := range m {
|
|
||||||
labelValues = append(labelValues, v)
|
|
||||||
}
|
|
||||||
return labelValues
|
|
||||||
}
|
|
||||||
|
|
||||||
// extractLabelNames extracts labels names from a given labelsProviderMarker (parent instance of aStructLabelProvider)
|
// 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 {
|
func extractLabelNames(labelProvider labelsProviderMarker) []string {
|
||||||
if any(labelProvider) == nil {
|
if any(labelProvider) == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var labels prometheus.Labels
|
// If custom implementation is done, just do it
|
||||||
if clp, ok := labelProvider.(CustomLabelsProvider); ok {
|
if clp, ok := labelProvider.(CustomLabelNamesProvider); ok {
|
||||||
labels = clp.ToPrometheusLabels()
|
return clp.ToLabelNames()
|
||||||
} else {
|
|
||||||
labels = extractLabelFromStruct(labelProvider)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here, then, it can be only a struct, that is a parent of StructLabelProvider
|
// Fallback to slow implementation via reflect
|
||||||
labelNames := make([]string, 0, len(labels))
|
// We'll return label values in order of fields in the struct
|
||||||
for k := range labels {
|
val := reflect.Indirect(reflect.ValueOf(labelProvider))
|
||||||
labelNames = append(labelNames, k)
|
typ := val.Type()
|
||||||
|
|
||||||
|
labelNames := make([]string, 0, typ.NumField())
|
||||||
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
|
field := typ.Field(i)
|
||||||
|
if field.Anonymous {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var fieldName string
|
||||||
|
if ourTag := field.Tag.Get(promsafeTag); ourTag == "-" {
|
||||||
|
continue
|
||||||
|
} else if ourTag != "" {
|
||||||
|
fieldName = ourTag
|
||||||
|
} else {
|
||||||
|
fieldName = toSnakeCase(field.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
labelNames = append(labelNames, fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return labelNames
|
return labelNames
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,15 +360,19 @@ func extractLabelFromStruct(structWithLabels any) prometheus.Labels {
|
||||||
labelName = toSnakeCase(field.Name)
|
labelName = toSnakeCase(field.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: we don't handle defaults values for now
|
labels[labelName] = stringifyLabelValue(val.Field(i))
|
||||||
// so it can have "nil" values, if you had *string fields, etc
|
|
||||||
fieldVal := fmt.Sprintf("%v", val.Field(i).Interface())
|
|
||||||
|
|
||||||
labels[labelName] = fieldVal
|
|
||||||
}
|
}
|
||||||
return labels
|
return labels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stringifyLabelValue makes up a valid string value from a given field's value
|
||||||
|
// It's used ONLY in fallback reflect mode
|
||||||
|
// Field value might be a pointer, that's why we do reflect.Indirect()
|
||||||
|
// Note: in future we can handle default values here as well
|
||||||
|
func stringifyLabelValue(v reflect.Value) string {
|
||||||
|
return fmt.Sprintf("%v", reflect.Indirect(v).Interface())
|
||||||
|
}
|
||||||
|
|
||||||
// Convert struct field names to snake_case for Prometheus label compliance.
|
// Convert struct field names to snake_case for Prometheus label compliance.
|
||||||
func toSnakeCase(s string) string {
|
func toSnakeCase(s string) string {
|
||||||
s = strings.TrimSpace(s)
|
s = strings.TrimSpace(s)
|
||||||
|
|
|
@ -43,7 +43,7 @@ func ExampleNewCounterVecT_multiple_labels_manual() {
|
||||||
|
|
||||||
// Manually register the counter
|
// Manually register the counter
|
||||||
if err := prometheus.Register(c.Unsafe()); err != nil {
|
if err := prometheus.Register(c.Unsafe()); err != nil {
|
||||||
log.Fatal("could not register1: ", err.Error())
|
log.Fatal("could not register: ", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// and now, because of generics we can call Inc() with filled struct of labels:
|
// and now, because of generics we can call Inc() with filled struct of labels:
|
||||||
|
@ -151,11 +151,16 @@ type FastMyLabels struct {
|
||||||
Source string
|
Source string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToPrometheusLabels does a fast conversion to labels. No reflection involved.
|
// ToPrometheusLabels does a superfast conversion to labels. So no reflection is involved.
|
||||||
func (f FastMyLabels) ToPrometheusLabels() prometheus.Labels {
|
func (f FastMyLabels) ToPrometheusLabels() prometheus.Labels {
|
||||||
return prometheus.Labels{"event_type": f.EventType, "source": f.Source}
|
return prometheus.Labels{"event_type": f.EventType, "source": f.Source}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToLabelNames does a superfast label names list. So no reflection is involved.
|
||||||
|
func (f FastMyLabels) ToLabelNames() []string {
|
||||||
|
return []string{"event_type", "source"}
|
||||||
|
}
|
||||||
|
|
||||||
func ExampleNewCounterVecT_fast_safe_labels_provider() {
|
func ExampleNewCounterVecT_fast_safe_labels_provider() {
|
||||||
// It's possible to use pointer to labels struct
|
// It's possible to use pointer to labels struct
|
||||||
myReg := prometheus.NewRegistry()
|
myReg := prometheus.NewRegistry()
|
||||||
|
|
Loading…
Reference in New Issue