Skip to content

Commit

Permalink
metrics: Fix custom initialization
Browse files Browse the repository at this point in the history
New... functions in pkg/metrics take an initialization function as an argument,
in case custom initialization is needed. However, such function passed as part
of a metric definition can't reference this metric - this would mean an
initialization cycle. This commit changes the initialization functions
signatures to take an inner prometheus metric as an argument, so that we can
actually initialize it and avoid a cycle.

Signed-off-by: Anna Kapuscinska <[email protected]>
  • Loading branch information
lambdanis committed Jul 10, 2024
1 parent dc1dcfb commit a33529c
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 29 deletions.
24 changes: 15 additions & 9 deletions pkg/metrics/counter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"github.com/prometheus/client_golang/prometheus"
)

type initCounterFunc func(*prometheus.CounterVec)

// NewCounterVecWithPod is a wrapper around prometheus.NewCounterVec that also
// registers the metric to be cleaned up when a pod is deleted.
//
Expand Down Expand Up @@ -37,7 +39,7 @@ func NewCounterVecWithPodV2(opts prometheus.CounterVecOpts) *prometheus.CounterV
type GranularCounter[L FilteredLabels] struct {
metric *prometheus.CounterVec
constrained bool
initFunc func()
initFunc initCounterFunc
initForDocs func()
}

Expand All @@ -56,8 +58,9 @@ type GranularCounter[L FilteredLabels] struct {
// same thing in different formats, or two labels are mutually exclusive).
// - metric is unconstrained, but some of the unconstrained label values are
// known beforehand, so can be initialized.
// - you want to disable default initialization - pass func() {} in such case
func NewGranularCounter[L FilteredLabels](opts Opts, init func()) (*GranularCounter[L], error) {
// - you want to disable default initialization - pass
// func(*prometheus.CounterVec) {} in such case
func NewGranularCounter[L FilteredLabels](opts Opts, init initCounterFunc) (*GranularCounter[L], error) {
labels, constrained, err := getVariableLabels[L](&opts)
if err != nil {
return nil, err
Expand Down Expand Up @@ -85,9 +88,12 @@ func NewGranularCounter[L FilteredLabels](opts Opts, init func()) (*GranularCoun
metric.WithLabelValues(lvs...).Add(0)
}

// if metric is constrained, default to initializing all combinations of labels
// If metric is constrained, default to initializing all combinations of
// labels. Note that in such case the initialization function doesn't
// reference the wrapped metric passed as an argument because this metric
// is captured already in initMetric closure.
if constrained && init == nil {
init = func() {
init = func(_ *prometheus.CounterVec) {
initAllCombinations(initMetric, opts.ConstrainedLabels)
}
}
Expand Down Expand Up @@ -125,7 +131,7 @@ func MustNewGranularCounter[L FilteredLabels](promOpts prometheus.CounterOpts, e

// MustNewGranularCounterWithInit is a convenience function that wraps
// NewGranularCounter and panics on error.
func MustNewGranularCounterWithInit[L FilteredLabels](opts Opts, init func()) *GranularCounter[L] {
func MustNewGranularCounterWithInit[L FilteredLabels](opts Opts, init initCounterFunc) *GranularCounter[L] {
metric, err := NewGranularCounter[L](opts, init)
if err != nil {
panic(err)
Expand All @@ -151,7 +157,7 @@ func (m *GranularCounter[L]) IsConstrained() bool {
// Init implements CollectorWithInit.
func (m *GranularCounter[L]) Init() {
if m.initFunc != nil {
m.initFunc()
m.initFunc(m.metric)
}
}

Expand Down Expand Up @@ -188,7 +194,7 @@ type Counter struct {
// NewCounter creates a new Counter.
//
// See NewGranularCounter for usage notes.
func NewCounter(opts Opts, init func()) (*Counter, error) {
func NewCounter(opts Opts, init initCounterFunc) (*Counter, error) {
metric, err := NewGranularCounter[NilLabels](opts, init)
if err != nil {
return nil, err
Expand All @@ -198,7 +204,7 @@ func NewCounter(opts Opts, init func()) (*Counter, error) {

// MustNewCounter is a convenience function that wraps NewCounter and panics on
// error.
func MustNewCounter(opts Opts, init func()) *Counter {
func MustNewCounter(opts Opts, init initCounterFunc) *Counter {
metric, err := NewCounter(opts, init)
if err != nil {
panic(err)
Expand Down
21 changes: 13 additions & 8 deletions pkg/metrics/gauge.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"github.com/prometheus/client_golang/prometheus"
)

type initGaugeFunc func(*prometheus.GaugeVec)

// NewGaugeVecWithPod is a wrapper around prometheus.NewGaugeVec that also
// registers the metric to be cleaned up when a pod is deleted.
//
Expand Down Expand Up @@ -35,14 +37,14 @@ func NewGaugeVecWithPodV2(opts prometheus.GaugeVecOpts) *prometheus.GaugeVec {
type GranularGauge[L FilteredLabels] struct {
metric *prometheus.GaugeVec
constrained bool
initFunc func()
initFunc initGaugeFunc
initForDocs func()
}

// NewGranularGauge creates a new GranularGauge.
//
// See NewGranularCounter for usage notes.
func NewGranularGauge[L FilteredLabels](opts Opts, init func()) (*GranularGauge[L], error) {
func NewGranularGauge[L FilteredLabels](opts Opts, init initGaugeFunc) (*GranularGauge[L], error) {
labels, constrained, err := getVariableLabels[L](&opts)
if err != nil {
return nil, err
Expand Down Expand Up @@ -70,9 +72,12 @@ func NewGranularGauge[L FilteredLabels](opts Opts, init func()) (*GranularGauge[
metric.WithLabelValues(lvs...).Set(0)
}

// if metric is constrained, default to initializing all combinations of labels
// If metric is constrained, default to initializing all combinations of
// labels. Note that in such case the initialization function doesn't
// reference the wrapped metric passed as an argument because this metric
// is captured already in initMetric closure.
if constrained && init == nil {
init = func() {
init = func(_ *prometheus.GaugeVec) {
initAllCombinations(initMetric, opts.ConstrainedLabels)
}
}
Expand Down Expand Up @@ -108,7 +113,7 @@ func MustNewGranularGauge[L FilteredLabels](promOpts prometheus.GaugeOpts, extra

// MustNewGranularGaugeWithInit is a convenience function that wraps
// NewGranularGauge and panics on error.
func MustNewGranularGaugeWithInit[L FilteredLabels](opts Opts, init func()) *GranularGauge[L] {
func MustNewGranularGaugeWithInit[L FilteredLabels](opts Opts, init initGaugeFunc) *GranularGauge[L] {
metric, err := NewGranularGauge[L](opts, init)
if err != nil {
panic(err)
Expand All @@ -134,7 +139,7 @@ func (m *GranularGauge[L]) IsConstrained() bool {
// Init implements CollectorWithInit.
func (m *GranularGauge[L]) Init() {
if m.initFunc != nil {
m.initFunc()
m.initFunc(m.metric)
}
}

Expand Down Expand Up @@ -171,7 +176,7 @@ type Gauge struct {
// NewGauge creates a new Gauge.
//
// See NewGranularCounter for usage notes.
func NewGauge(opts Opts, init func()) (*Gauge, error) {
func NewGauge(opts Opts, init initGaugeFunc) (*Gauge, error) {
metric, err := NewGranularGauge[NilLabels](opts, init)
if err != nil {
return nil, err
Expand All @@ -181,7 +186,7 @@ func NewGauge(opts Opts, init func()) (*Gauge, error) {

// MustNewGauge is a convenience function that wraps NewGauge and panics on
// error.
func MustNewGauge(opts Opts, init func()) *Gauge {
func MustNewGauge(opts Opts, init initGaugeFunc) *Gauge {
metric, err := NewGauge(opts, init)
if err != nil {
panic(err)
Expand Down
8 changes: 4 additions & 4 deletions pkg/metrics/granularmetric.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

package metrics

type initMetricFunc func(...string)
type initLabelValuesFunc func(...string)

// initAllCombinations initializes a metric with all possible combinations of
// label values.
func initAllCombinations(initMetric initMetricFunc, labels []ConstrainedLabel) {
func initAllCombinations(initMetric initLabelValuesFunc, labels []ConstrainedLabel) {
initCombinations(initMetric, labels, make([]string, len(labels)), 0)
}

Expand All @@ -20,7 +20,7 @@ func initAllCombinations(initMetric initMetricFunc, labels []ConstrainedLabel) {
// - cursor is in the range [0, len(labels)]
//
// If any of these is not met, the function will do nothing.
func initCombinations(initMetric initMetricFunc, labels []ConstrainedLabel, lvs []string, cursor int) {
func initCombinations(initMetric initLabelValuesFunc, labels []ConstrainedLabel, lvs []string, cursor int) {
if initMetric == nil || len(labels) != len(lvs) || cursor < 0 || cursor > len(labels) {
// The function was called with invalid arguments. Silently return.
return
Expand All @@ -43,7 +43,7 @@ func initCombinations(initMetric initMetricFunc, labels []ConstrainedLabel, lvs
// metrics server - but here we care only about extracting labels for
// documentation, so we don't try to make the metrics realistic.
func initForDocs[L FilteredLabels](
initMetric initMetricFunc, constrained []ConstrainedLabel, unconstrained []UnconstrainedLabel,
initMetric initLabelValuesFunc, constrained []ConstrainedLabel, unconstrained []UnconstrainedLabel,
) {
var dummy L
commonLabels := dummy.Keys()
Expand Down
21 changes: 13 additions & 8 deletions pkg/metrics/histogram.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"github.com/prometheus/client_golang/prometheus"
)

type initHistogramFunc func(*prometheus.HistogramVec)

// NewHistogramVecWithPod is a wrapper around prometheus.NewHistogramVec that also
// registers the metric to be cleaned up when a pod is deleted.
//
Expand Down Expand Up @@ -35,14 +37,14 @@ func NewHistogramVecWithPodV2(opts prometheus.HistogramVecOpts) *prometheus.Hist
type GranularHistogram[L FilteredLabels] struct {
metric *prometheus.HistogramVec
constrained bool
initFunc func()
initFunc initHistogramFunc
initForDocs func()
}

// NewGranularHistogram creates a new GranularHistogram.
//
// See NewGranularCounter for usage notes.
func NewGranularHistogram[L FilteredLabels](opts HistogramOpts, init func()) (*GranularHistogram[L], error) {
func NewGranularHistogram[L FilteredLabels](opts HistogramOpts, init initHistogramFunc) (*GranularHistogram[L], error) {
labels, constrained, err := getVariableLabels[L](&opts.Opts)
if err != nil {
return nil, err
Expand Down Expand Up @@ -71,9 +73,12 @@ func NewGranularHistogram[L FilteredLabels](opts HistogramOpts, init func()) (*G
metric.WithLabelValues(lvs...)
}

// if metric is constrained, default to initializing all combinations of labels
// If metric is constrained, default to initializing all combinations of
// labels. Note that in such case the initialization function doesn't
// reference the wrapped metric passed as an argument because this metric
// is captured already in initMetric closure.
if constrained && init == nil {
init = func() {
init = func(_ *prometheus.HistogramVec) {
initAllCombinations(initMetric, opts.ConstrainedLabels)
}
}
Expand Down Expand Up @@ -118,7 +123,7 @@ func MustNewGranularHistogram[L FilteredLabels](promOpts prometheus.HistogramOpt

// MustNewGranularHistogramWithInit is a convenience function that wraps
// NewGranularHistogram and panics on error.
func MustNewGranularHistogramWithInit[L FilteredLabels](opts HistogramOpts, init func()) *GranularHistogram[L] {
func MustNewGranularHistogramWithInit[L FilteredLabels](opts HistogramOpts, init initHistogramFunc) *GranularHistogram[L] {
metric, err := NewGranularHistogram[L](opts, init)
if err != nil {
panic(err)
Expand All @@ -144,7 +149,7 @@ func (m *GranularHistogram[L]) IsConstrained() bool {
// Init implements CollectorWithInit.
func (m *GranularHistogram[L]) Init() {
if m.initFunc != nil {
m.initFunc()
m.initFunc(m.metric)
}
}

Expand Down Expand Up @@ -181,7 +186,7 @@ type Histogram struct {
// NewHistogram creates a new Histogram.
//
// See NewGranularCounter for usage notes.
func NewHistogram(opts HistogramOpts, init func()) (*Histogram, error) {
func NewHistogram(opts HistogramOpts, init initHistogramFunc) (*Histogram, error) {
metric, err := NewGranularHistogram[NilLabels](opts, init)
if err != nil {
return nil, err
Expand All @@ -191,7 +196,7 @@ func NewHistogram(opts HistogramOpts, init func()) (*Histogram, error) {

// MustNewHistogram is a convenience function that wraps NewHistogram and panics on
// error.
func MustNewHistogram(opts HistogramOpts, init func()) *Histogram {
func MustNewHistogram(opts HistogramOpts, init initHistogramFunc) *Histogram {
metric, err := NewHistogram(opts, init)
if err != nil {
panic(err)
Expand Down

0 comments on commit a33529c

Please sign in to comment.