Add constrained labels and Constrained variant for all MetricVecs (#1151)
* Introduce MetricVecOpts and add constraints to VariableLabels MetricVecOpts exposes options specific to MetricVec initialisation. The first option exposed by MetricVecOpts are constraints on VariableLabels, allowing restrictions on the possible values a label can take, to prevent cardinality explosion when the label value comes from a non-trusted source (as a user input or HTTP header). Signed-off-by: Quentin Devos <4972091+Okhoshi@users.noreply.github.com> * Add tests Signed-off-by: Quentin Devos <4972091+Okhoshi@users.noreply.github.com> Signed-off-by: Quentin Devos <4972091+Okhoshi@users.noreply.github.com>
This commit is contained in:
parent
3d765a161a
commit
fae2f6306b
|
@ -59,6 +59,18 @@ type ExemplarAdder interface {
|
|||
// CounterOpts is an alias for Opts. See there for doc comments.
|
||||
type CounterOpts Opts
|
||||
|
||||
// CounterVecOpts bundles the options to create a CounterVec metric.
|
||||
// It is mandatory to set CounterOpts, see there for mandatory fields. VariableLabels
|
||||
// is optional and can safely be left to its default value.
|
||||
type CounterVecOpts struct {
|
||||
CounterOpts
|
||||
|
||||
// VariableLabels are used to partition the metric vector by the given set
|
||||
// of labels. Each label value will be constrained with the optional Contraint
|
||||
// function, if provided.
|
||||
VariableLabels ConstrainableLabels
|
||||
}
|
||||
|
||||
// NewCounter creates a new Counter based on the provided CounterOpts.
|
||||
//
|
||||
// The returned implementation also implements ExemplarAdder. It is safe to
|
||||
|
@ -174,16 +186,24 @@ type CounterVec struct {
|
|||
// NewCounterVec creates a new CounterVec based on the provided CounterOpts and
|
||||
// partitioned by the given label names.
|
||||
func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
|
||||
desc := NewDesc(
|
||||
return V2.NewCounterVec(CounterVecOpts{
|
||||
CounterOpts: opts,
|
||||
VariableLabels: UnconstrainedLabels(labelNames),
|
||||
})
|
||||
}
|
||||
|
||||
// NewCounterVec creates a new CounterVec based on the provided CounterVecOpts.
|
||||
func (v2) NewCounterVec(opts CounterVecOpts) *CounterVec {
|
||||
desc := V2.NewDesc(
|
||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||
opts.Help,
|
||||
labelNames,
|
||||
opts.VariableLabels,
|
||||
opts.ConstLabels,
|
||||
)
|
||||
return &CounterVec{
|
||||
MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
|
||||
if len(lvs) != len(desc.variableLabels) {
|
||||
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
|
||||
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.labelNames(), lvs))
|
||||
}
|
||||
result := &counter{desc: desc, labelPairs: MakeLabelPairs(desc, lvs), now: time.Now}
|
||||
result.init(result) // Init self-collection.
|
||||
|
|
|
@ -102,7 +102,7 @@ func TestCounterVecGetMetricWithInvalidLabelValues(t *testing.T) {
|
|||
Name: "test",
|
||||
}, []string{"a"})
|
||||
|
||||
labelValues := make([]string, len(test.labels))
|
||||
labelValues := make([]string, 0, len(test.labels))
|
||||
for _, val := range test.labels {
|
||||
labelValues = append(labelValues, val)
|
||||
}
|
||||
|
|
|
@ -53,9 +53,9 @@ type Desc struct {
|
|||
// constLabelPairs contains precalculated DTO label pairs based on
|
||||
// the constant labels.
|
||||
constLabelPairs []*dto.LabelPair
|
||||
// variableLabels contains names of labels for which the metric
|
||||
// maintains variable values.
|
||||
variableLabels []string
|
||||
// variableLabels contains names of labels and normalization function for
|
||||
// which the metric maintains variable values.
|
||||
variableLabels ConstrainedLabels
|
||||
// id is a hash of the values of the ConstLabels and fqName. This
|
||||
// must be unique among all registered descriptors and can therefore be
|
||||
// used as an identifier of the descriptor.
|
||||
|
@ -79,10 +79,24 @@ type Desc struct {
|
|||
// For constLabels, the label values are constant. Therefore, they are fully
|
||||
// specified in the Desc. See the Collector example for a usage pattern.
|
||||
func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *Desc {
|
||||
return V2.NewDesc(fqName, help, UnconstrainedLabels(variableLabels), constLabels)
|
||||
}
|
||||
|
||||
// NewDesc allocates and initializes a new Desc. Errors are recorded in the Desc
|
||||
// and will be reported on registration time. variableLabels and constLabels can
|
||||
// be nil if no such labels should be set. fqName must not be empty.
|
||||
//
|
||||
// variableLabels only contain the label names and normalization functions. Their
|
||||
// label values are variable and therefore not part of the Desc. (They are managed
|
||||
// within the Metric.)
|
||||
//
|
||||
// For constLabels, the label values are constant. Therefore, they are fully
|
||||
// specified in the Desc. See the Collector example for a usage pattern.
|
||||
func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, constLabels Labels) *Desc {
|
||||
d := &Desc{
|
||||
fqName: fqName,
|
||||
help: help,
|
||||
variableLabels: variableLabels,
|
||||
variableLabels: variableLabels.constrainedLabels(),
|
||||
}
|
||||
if !model.IsValidMetricName(model.LabelValue(fqName)) {
|
||||
d.err = fmt.Errorf("%q is not a valid metric name", fqName)
|
||||
|
@ -92,7 +106,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
|
|||
// their sorted label names) plus the fqName (at position 0).
|
||||
labelValues := make([]string, 1, len(constLabels)+1)
|
||||
labelValues[0] = fqName
|
||||
labelNames := make([]string, 0, len(constLabels)+len(variableLabels))
|
||||
labelNames := make([]string, 0, len(constLabels)+len(d.variableLabels))
|
||||
labelNameSet := map[string]struct{}{}
|
||||
// First add only the const label names and sort them...
|
||||
for labelName := range constLabels {
|
||||
|
@ -117,13 +131,13 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
|
|||
// Now add the variable label names, but prefix them with something that
|
||||
// cannot be in a regular label name. That prevents matching the label
|
||||
// dimension with a different mix between preset and variable labels.
|
||||
for _, labelName := range variableLabels {
|
||||
if !checkLabelName(labelName) {
|
||||
d.err = fmt.Errorf("%q is not a valid label name for metric %q", labelName, fqName)
|
||||
for _, label := range d.variableLabels {
|
||||
if !checkLabelName(label.Name) {
|
||||
d.err = fmt.Errorf("%q is not a valid label name for metric %q", label.Name, fqName)
|
||||
return d
|
||||
}
|
||||
labelNames = append(labelNames, "$"+labelName)
|
||||
labelNameSet[labelName] = struct{}{}
|
||||
labelNames = append(labelNames, "$"+label.Name)
|
||||
labelNameSet[label.Name] = struct{}{}
|
||||
}
|
||||
if len(labelNames) != len(labelNameSet) {
|
||||
d.err = fmt.Errorf("duplicate label names in constant and variable labels for metric %q", fqName)
|
||||
|
|
|
@ -294,9 +294,9 @@ func ExampleRegister() {
|
|||
|
||||
// Output:
|
||||
// taskCounter registered.
|
||||
// taskCounterVec not registered: a previously registered descriptor with the same fully-qualified name as Desc{fqName: "worker_pool_completed_tasks_total", help: "Total number of tasks completed.", constLabels: {}, variableLabels: [worker_id]} has different label names or a different help string
|
||||
// taskCounterVec not registered: a previously registered descriptor with the same fully-qualified name as Desc{fqName: "worker_pool_completed_tasks_total", help: "Total number of tasks completed.", constLabels: {}, variableLabels: [{worker_id <nil>}]} has different label names or a different help string
|
||||
// taskCounter unregistered.
|
||||
// taskCounterVec not registered: a previously registered descriptor with the same fully-qualified name as Desc{fqName: "worker_pool_completed_tasks_total", help: "Total number of tasks completed.", constLabels: {}, variableLabels: [worker_id]} has different label names or a different help string
|
||||
// taskCounterVec not registered: a previously registered descriptor with the same fully-qualified name as Desc{fqName: "worker_pool_completed_tasks_total", help: "Total number of tasks completed.", constLabels: {}, variableLabels: [{worker_id <nil>}]} has different label names or a different help string
|
||||
// taskCounterVec registered.
|
||||
// Worker initialization failed: inconsistent label cardinality: expected 1 label values but got 2 in []string{"42", "spurious arg"}
|
||||
// notMyCounter is nil.
|
||||
|
|
|
@ -55,6 +55,18 @@ type Gauge interface {
|
|||
// GaugeOpts is an alias for Opts. See there for doc comments.
|
||||
type GaugeOpts Opts
|
||||
|
||||
// GaugeVecOpts bundles the options to create a GaugeVec metric.
|
||||
// It is mandatory to set GaugeOpts, see there for mandatory fields. VariableLabels
|
||||
// is optional and can safely be left to its default value.
|
||||
type GaugeVecOpts struct {
|
||||
GaugeOpts
|
||||
|
||||
// VariableLabels are used to partition the metric vector by the given set
|
||||
// of labels. Each label value will be constrained with the optional Contraint
|
||||
// function, if provided.
|
||||
VariableLabels ConstrainableLabels
|
||||
}
|
||||
|
||||
// NewGauge creates a new Gauge based on the provided GaugeOpts.
|
||||
//
|
||||
// The returned implementation is optimized for a fast Set method. If you have a
|
||||
|
@ -138,16 +150,24 @@ type GaugeVec struct {
|
|||
// NewGaugeVec creates a new GaugeVec based on the provided GaugeOpts and
|
||||
// partitioned by the given label names.
|
||||
func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
|
||||
desc := NewDesc(
|
||||
return V2.NewGaugeVec(GaugeVecOpts{
|
||||
GaugeOpts: opts,
|
||||
VariableLabels: UnconstrainedLabels(labelNames),
|
||||
})
|
||||
}
|
||||
|
||||
// NewGaugeVec creates a new GaugeVec based on the provided GaugeVecOpts.
|
||||
func (v2) NewGaugeVec(opts GaugeVecOpts) *GaugeVec {
|
||||
desc := V2.NewDesc(
|
||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||
opts.Help,
|
||||
labelNames,
|
||||
opts.VariableLabels,
|
||||
opts.ConstLabels,
|
||||
)
|
||||
return &GaugeVec{
|
||||
MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
|
||||
if len(lvs) != len(desc.variableLabels) {
|
||||
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
|
||||
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.labelNames(), lvs))
|
||||
}
|
||||
result := &gauge{desc: desc, labelPairs: MakeLabelPairs(desc, lvs)}
|
||||
result.init(result) // Init self-collection.
|
||||
|
|
|
@ -469,6 +469,18 @@ type HistogramOpts struct {
|
|||
NativeHistogramMaxZeroThreshold float64
|
||||
}
|
||||
|
||||
// HistogramVecOpts bundles the options to create a HistogramVec metric.
|
||||
// It is mandatory to set HistogramOpts, see there for mandatory fields. VariableLabels
|
||||
// is optional and can safely be left to its default value.
|
||||
type HistogramVecOpts struct {
|
||||
HistogramOpts
|
||||
|
||||
// VariableLabels are used to partition the metric vector by the given set
|
||||
// of labels. Each label value will be constrained with the optional Contraint
|
||||
// function, if provided.
|
||||
VariableLabels ConstrainableLabels
|
||||
}
|
||||
|
||||
// NewHistogram creates a new Histogram based on the provided HistogramOpts. It
|
||||
// panics if the buckets in HistogramOpts are not in strictly increasing order.
|
||||
//
|
||||
|
@ -489,11 +501,11 @@ func NewHistogram(opts HistogramOpts) Histogram {
|
|||
|
||||
func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
|
||||
if len(desc.variableLabels) != len(labelValues) {
|
||||
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
|
||||
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.labelNames(), labelValues))
|
||||
}
|
||||
|
||||
for _, n := range desc.variableLabels {
|
||||
if n == bucketLabel {
|
||||
if n.Name == bucketLabel {
|
||||
panic(errBucketLabelNotAllowed)
|
||||
}
|
||||
}
|
||||
|
@ -1030,15 +1042,23 @@ type HistogramVec struct {
|
|||
// NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
|
||||
// partitioned by the given label names.
|
||||
func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
|
||||
desc := NewDesc(
|
||||
return V2.NewHistogramVec(HistogramVecOpts{
|
||||
HistogramOpts: opts,
|
||||
VariableLabels: UnconstrainedLabels(labelNames),
|
||||
})
|
||||
}
|
||||
|
||||
// NewHistogramVec creates a new HistogramVec based on the provided HistogramVecOpts.
|
||||
func (v2) NewHistogramVec(opts HistogramVecOpts) *HistogramVec {
|
||||
desc := V2.NewDesc(
|
||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||
opts.Help,
|
||||
labelNames,
|
||||
opts.VariableLabels,
|
||||
opts.ConstLabels,
|
||||
)
|
||||
return &HistogramVec{
|
||||
MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
|
||||
return newHistogram(desc, opts, lvs...)
|
||||
return newHistogram(desc, opts.HistogramOpts, lvs...)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,78 @@ import (
|
|||
// create a Desc.
|
||||
type Labels map[string]string
|
||||
|
||||
// ConstrainedLabels represents a label name and its constrain function
|
||||
// to normalize label values. This type is commonly used when constructing
|
||||
// metric vector Collectors.
|
||||
type ConstrainedLabel struct {
|
||||
Name string
|
||||
Constraint func(string) string
|
||||
}
|
||||
|
||||
func (cl ConstrainedLabel) Constrain(v string) string {
|
||||
if cl.Constraint == nil {
|
||||
return v
|
||||
}
|
||||
return cl.Constraint(v)
|
||||
}
|
||||
|
||||
// ConstrainableLabels is an interface that allows creating of labels that can
|
||||
// be optionally constrained.
|
||||
//
|
||||
// prometheus.V2().NewCounterVec(CounterVecOpts{
|
||||
// CounterOpts: {...}, // Usual CounterOpts fields
|
||||
// VariableLabels: []ConstrainedLabels{
|
||||
// {Name: "A"},
|
||||
// {Name: "B", Constraint: func(v string) string { ... }},
|
||||
// },
|
||||
// })
|
||||
type ConstrainableLabels interface {
|
||||
constrainedLabels() ConstrainedLabels
|
||||
labelNames() []string
|
||||
}
|
||||
|
||||
// ConstrainedLabels represents a collection of label name -> constrain function
|
||||
// to normalize label values. This type is commonly used when constructing
|
||||
// metric vector Collectors.
|
||||
type ConstrainedLabels []ConstrainedLabel
|
||||
|
||||
func (cls ConstrainedLabels) constrainedLabels() ConstrainedLabels {
|
||||
return cls
|
||||
}
|
||||
|
||||
func (cls ConstrainedLabels) labelNames() []string {
|
||||
names := make([]string, len(cls))
|
||||
for i, label := range cls {
|
||||
names[i] = label.Name
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// UnconstrainedLabels represents collection of label without any constraint on
|
||||
// their value. Thus, it is simply a collection of label names.
|
||||
//
|
||||
// UnconstrainedLabels([]string{ "A", "B" })
|
||||
//
|
||||
// is equivalent to
|
||||
//
|
||||
// ConstrainedLabels {
|
||||
// { Name: "A" },
|
||||
// { Name: "B" },
|
||||
// }
|
||||
type UnconstrainedLabels []string
|
||||
|
||||
func (uls UnconstrainedLabels) constrainedLabels() ConstrainedLabels {
|
||||
constrainedLabels := make([]ConstrainedLabel, len(uls))
|
||||
for i, l := range uls {
|
||||
constrainedLabels[i] = ConstrainedLabel{Name: l}
|
||||
}
|
||||
return constrainedLabels
|
||||
}
|
||||
|
||||
func (uls UnconstrainedLabels) labelNames() []string {
|
||||
return uls
|
||||
}
|
||||
|
||||
// reservedLabelPrefix is a prefix which is not legal in user-supplied
|
||||
// label names.
|
||||
const reservedLabelPrefix = "__"
|
||||
|
|
|
@ -962,7 +962,7 @@ func checkDescConsistency(
|
|||
copy(lpsFromDesc, desc.constLabelPairs)
|
||||
for _, l := range desc.variableLabels {
|
||||
lpsFromDesc = append(lpsFromDesc, &dto.LabelPair{
|
||||
Name: proto.String(l),
|
||||
Name: proto.String(l.Name),
|
||||
})
|
||||
}
|
||||
if len(lpsFromDesc) != len(dtoMetric.Label) {
|
||||
|
|
|
@ -148,6 +148,18 @@ type SummaryOpts struct {
|
|||
BufCap uint32
|
||||
}
|
||||
|
||||
// SummaryVecOpts bundles the options to create a SummaryVec metric.
|
||||
// It is mandatory to set SummaryOpts, see there for mandatory fields. VariableLabels
|
||||
// is optional and can safely be left to its default value.
|
||||
type SummaryVecOpts struct {
|
||||
SummaryOpts
|
||||
|
||||
// VariableLabels are used to partition the metric vector by the given set
|
||||
// of labels. Each label value will be constrained with the optional Contraint
|
||||
// function, if provided.
|
||||
VariableLabels ConstrainableLabels
|
||||
}
|
||||
|
||||
// Problem with the sliding-window decay algorithm... The Merge method of
|
||||
// perk/quantile is actually not working as advertised - and it might be
|
||||
// unfixable, as the underlying algorithm is apparently not capable of merging
|
||||
|
@ -178,11 +190,11 @@ func NewSummary(opts SummaryOpts) Summary {
|
|||
|
||||
func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
|
||||
if len(desc.variableLabels) != len(labelValues) {
|
||||
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
|
||||
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.labelNames(), labelValues))
|
||||
}
|
||||
|
||||
for _, n := range desc.variableLabels {
|
||||
if n == quantileLabel {
|
||||
if n.Name == quantileLabel {
|
||||
panic(errQuantileLabelNotAllowed)
|
||||
}
|
||||
}
|
||||
|
@ -530,20 +542,28 @@ type SummaryVec struct {
|
|||
// it is handled by the Prometheus server internally, “quantile” is an illegal
|
||||
// label name. NewSummaryVec will panic if this label name is used.
|
||||
func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
|
||||
for _, ln := range labelNames {
|
||||
return V2.NewSummaryVec(SummaryVecOpts{
|
||||
SummaryOpts: opts,
|
||||
VariableLabels: UnconstrainedLabels(labelNames),
|
||||
})
|
||||
}
|
||||
|
||||
// NewSummaryVec creates a new SummaryVec based on the provided SummaryVecOpts.
|
||||
func (v2) NewSummaryVec(opts SummaryVecOpts) *SummaryVec {
|
||||
for _, ln := range opts.VariableLabels.labelNames() {
|
||||
if ln == quantileLabel {
|
||||
panic(errQuantileLabelNotAllowed)
|
||||
}
|
||||
}
|
||||
desc := NewDesc(
|
||||
desc := V2.NewDesc(
|
||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||
opts.Help,
|
||||
labelNames,
|
||||
opts.VariableLabels,
|
||||
opts.ConstLabels,
|
||||
)
|
||||
return &SummaryVec{
|
||||
MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
|
||||
return newSummary(desc, opts, lvs...)
|
||||
return newSummary(desc, opts.SummaryOpts, lvs...)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -188,9 +188,9 @@ func MakeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair {
|
|||
return desc.constLabelPairs
|
||||
}
|
||||
labelPairs := make([]*dto.LabelPair, 0, totalLen)
|
||||
for i, n := range desc.variableLabels {
|
||||
for i, l := range desc.variableLabels {
|
||||
labelPairs = append(labelPairs, &dto.LabelPair{
|
||||
Name: proto.String(n),
|
||||
Name: proto.String(l.Name),
|
||||
Value: proto.String(labelValues[i]),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@ func NewMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *MetricVec {
|
|||
// with a performance overhead (for creating and processing the Labels map).
|
||||
// See also the CounterVec example.
|
||||
func (m *MetricVec) DeleteLabelValues(lvs ...string) bool {
|
||||
lvs = constrainLabelValues(m.desc, lvs, m.curry)
|
||||
h, err := m.hashLabelValues(lvs)
|
||||
if err != nil {
|
||||
return false
|
||||
|
@ -91,6 +92,7 @@ func (m *MetricVec) DeleteLabelValues(lvs ...string) bool {
|
|||
// This method is used for the same purpose as DeleteLabelValues(...string). See
|
||||
// there for pros and cons of the two methods.
|
||||
func (m *MetricVec) Delete(labels Labels) bool {
|
||||
labels = constrainLabels(m.desc, labels)
|
||||
h, err := m.hashLabels(labels)
|
||||
if err != nil {
|
||||
return false
|
||||
|
@ -106,6 +108,7 @@ func (m *MetricVec) Delete(labels Labels) bool {
|
|||
// Note that curried labels will never be matched if deleting from the curried vector.
|
||||
// To match curried labels with DeletePartialMatch, it must be called on the base vector.
|
||||
func (m *MetricVec) DeletePartialMatch(labels Labels) int {
|
||||
labels = constrainLabels(m.desc, labels)
|
||||
return m.metricMap.deleteByLabels(labels, m.curry)
|
||||
}
|
||||
|
||||
|
@ -145,10 +148,10 @@ func (m *MetricVec) CurryWith(labels Labels) (*MetricVec, error) {
|
|||
iCurry int
|
||||
)
|
||||
for i, label := range m.desc.variableLabels {
|
||||
val, ok := labels[label]
|
||||
val, ok := labels[label.Name]
|
||||
if iCurry < len(oldCurry) && oldCurry[iCurry].index == i {
|
||||
if ok {
|
||||
return nil, fmt.Errorf("label name %q is already curried", label)
|
||||
return nil, fmt.Errorf("label name %q is already curried", label.Name)
|
||||
}
|
||||
newCurry = append(newCurry, oldCurry[iCurry])
|
||||
iCurry++
|
||||
|
@ -156,7 +159,7 @@ func (m *MetricVec) CurryWith(labels Labels) (*MetricVec, error) {
|
|||
if !ok {
|
||||
continue // Label stays uncurried.
|
||||
}
|
||||
newCurry = append(newCurry, curriedLabelValue{i, val})
|
||||
newCurry = append(newCurry, curriedLabelValue{i, label.Constrain(val)})
|
||||
}
|
||||
}
|
||||
if l := len(oldCurry) + len(labels) - len(newCurry); l > 0 {
|
||||
|
@ -199,6 +202,7 @@ func (m *MetricVec) CurryWith(labels Labels) (*MetricVec, error) {
|
|||
// a wrapper around MetricVec, implementing a vector for a specific Metric
|
||||
// implementation, for example GaugeVec.
|
||||
func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
|
||||
lvs = constrainLabelValues(m.desc, lvs, m.curry)
|
||||
h, err := m.hashLabelValues(lvs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -224,6 +228,7 @@ func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
|
|||
// around MetricVec, implementing a vector for a specific Metric implementation,
|
||||
// for example GaugeVec.
|
||||
func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) {
|
||||
labels = constrainLabels(m.desc, labels)
|
||||
h, err := m.hashLabels(labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -266,16 +271,16 @@ func (m *MetricVec) hashLabels(labels Labels) (uint64, error) {
|
|||
iCurry int
|
||||
)
|
||||
for i, label := range m.desc.variableLabels {
|
||||
val, ok := labels[label]
|
||||
val, ok := labels[label.Name]
|
||||
if iCurry < len(curry) && curry[iCurry].index == i {
|
||||
if ok {
|
||||
return 0, fmt.Errorf("label name %q is already curried", label)
|
||||
return 0, fmt.Errorf("label name %q is already curried", label.Name)
|
||||
}
|
||||
h = m.hashAdd(h, curry[iCurry].value)
|
||||
iCurry++
|
||||
} else {
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("label name %q missing in label map", label)
|
||||
return 0, fmt.Errorf("label name %q missing in label map", label.Name)
|
||||
}
|
||||
h = m.hashAdd(h, val)
|
||||
}
|
||||
|
@ -453,7 +458,7 @@ func valueMatchesVariableOrCurriedValue(targetValue string, index int, values []
|
|||
func matchPartialLabels(desc *Desc, values []string, labels Labels, curry []curriedLabelValue) bool {
|
||||
for l, v := range labels {
|
||||
// Check if the target label exists in our metrics and get the index.
|
||||
varLabelIndex, validLabel := indexOf(l, desc.variableLabels)
|
||||
varLabelIndex, validLabel := indexOf(l, desc.variableLabels.labelNames())
|
||||
if validLabel {
|
||||
// Check the value of that label against the target value.
|
||||
// We don't consider curried values in partial matches.
|
||||
|
@ -605,7 +610,7 @@ func matchLabels(desc *Desc, values []string, labels Labels, curry []curriedLabe
|
|||
iCurry++
|
||||
continue
|
||||
}
|
||||
if values[i] != labels[k] {
|
||||
if values[i] != labels[k.Name] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -621,7 +626,7 @@ func extractLabelValues(desc *Desc, labels Labels, curry []curriedLabelValue) []
|
|||
iCurry++
|
||||
continue
|
||||
}
|
||||
labelValues[i] = labels[k]
|
||||
labelValues[i] = labels[k.Name]
|
||||
}
|
||||
return labelValues
|
||||
}
|
||||
|
@ -640,3 +645,34 @@ func inlineLabelValues(lvs []string, curry []curriedLabelValue) []string {
|
|||
}
|
||||
return labelValues
|
||||
}
|
||||
|
||||
func constrainLabels(desc *Desc, labels Labels) Labels {
|
||||
constrainedValues := make(Labels, len(labels))
|
||||
for l, v := range labels {
|
||||
if i, ok := indexOf(l, desc.variableLabels.labelNames()); ok {
|
||||
constrainedValues[l] = desc.variableLabels[i].Constrain(v)
|
||||
continue
|
||||
}
|
||||
constrainedValues[l] = v
|
||||
}
|
||||
return constrainedValues
|
||||
}
|
||||
|
||||
func constrainLabelValues(desc *Desc, lvs []string, curry []curriedLabelValue) []string {
|
||||
constrainedValues := make([]string, len(lvs))
|
||||
var iCurry, iLVs int
|
||||
for i := 0; i < len(lvs)+len(curry); i++ {
|
||||
if iCurry < len(curry) && curry[iCurry].index == i {
|
||||
iCurry++
|
||||
continue
|
||||
}
|
||||
|
||||
if i < len(desc.variableLabels) {
|
||||
constrainedValues[iLVs] = desc.variableLabels[i].Constrain(lvs[iLVs])
|
||||
} else {
|
||||
constrainedValues[iLVs] = lvs[iLVs]
|
||||
}
|
||||
iLVs++
|
||||
}
|
||||
return constrainedValues
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ package prometheus
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
|
@ -44,6 +45,20 @@ func TestDeleteWithCollisions(t *testing.T) {
|
|||
testDelete(t, vec)
|
||||
}
|
||||
|
||||
func TestDeleteWithConstraints(t *testing.T) {
|
||||
vec := V2.NewGaugeVec(GaugeVecOpts{
|
||||
GaugeOpts{
|
||||
Name: "test",
|
||||
Help: "helpless",
|
||||
},
|
||||
ConstrainedLabels{
|
||||
{Name: "l1"},
|
||||
{Name: "l2", Constraint: func(s string) string { return "x" + s }},
|
||||
},
|
||||
})
|
||||
testDelete(t, vec)
|
||||
}
|
||||
|
||||
func testDelete(t *testing.T, vec *GaugeVec) {
|
||||
if got, want := vec.Delete(Labels{"l1": "v1", "l2": "v2"}), false; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
|
@ -98,6 +113,20 @@ func TestDeleteLabelValuesWithCollisions(t *testing.T) {
|
|||
testDeleteLabelValues(t, vec)
|
||||
}
|
||||
|
||||
func TestDeleteLabelValuesWithConstraints(t *testing.T) {
|
||||
vec := V2.NewGaugeVec(GaugeVecOpts{
|
||||
GaugeOpts{
|
||||
Name: "test",
|
||||
Help: "helpless",
|
||||
},
|
||||
ConstrainedLabels{
|
||||
{Name: "l1"},
|
||||
{Name: "l2", Constraint: func(s string) string { return "x" + s }},
|
||||
},
|
||||
})
|
||||
testDeleteLabelValues(t, vec)
|
||||
}
|
||||
|
||||
func testDeleteLabelValues(t *testing.T, vec *GaugeVec) {
|
||||
if got, want := vec.DeleteLabelValues("v1", "v2"), false; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
|
@ -126,14 +155,32 @@ func testDeleteLabelValues(t *testing.T, vec *GaugeVec) {
|
|||
}
|
||||
|
||||
func TestDeletePartialMatch(t *testing.T) {
|
||||
baseVec := NewGaugeVec(
|
||||
vec := NewGaugeVec(
|
||||
GaugeOpts{
|
||||
Name: "test",
|
||||
Help: "helpless",
|
||||
},
|
||||
[]string{"l1", "l2", "l3"},
|
||||
)
|
||||
testDeletePartialMatch(t, vec)
|
||||
}
|
||||
|
||||
func TestDeletePartialMatchWithConstraints(t *testing.T) {
|
||||
vec := V2.NewGaugeVec(GaugeVecOpts{
|
||||
GaugeOpts{
|
||||
Name: "test",
|
||||
Help: "helpless",
|
||||
},
|
||||
ConstrainedLabels{
|
||||
{Name: "l1"},
|
||||
{Name: "l2", Constraint: func(s string) string { return "x" + s }},
|
||||
{Name: "l3"},
|
||||
},
|
||||
})
|
||||
testDeletePartialMatch(t, vec)
|
||||
}
|
||||
|
||||
func testDeletePartialMatch(t *testing.T, baseVec *GaugeVec) {
|
||||
assertNoMetric := func(t *testing.T) {
|
||||
if n := len(baseVec.metricMap.metrics); n != 0 {
|
||||
t.Error("expected no metrics, got", n)
|
||||
|
@ -293,6 +340,78 @@ func testMetricVec(t *testing.T, vec *GaugeVec) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMetricVecWithConstraints(t *testing.T) {
|
||||
constraint := func(s string) string { return "x" + s }
|
||||
vec := V2.NewGaugeVec(GaugeVecOpts{
|
||||
GaugeOpts{
|
||||
Name: "test",
|
||||
Help: "helpless",
|
||||
},
|
||||
ConstrainedLabels{
|
||||
{Name: "l1"},
|
||||
{Name: "l2", Constraint: constraint},
|
||||
},
|
||||
})
|
||||
testConstrainedMetricVec(t, vec, constraint)
|
||||
}
|
||||
|
||||
func testConstrainedMetricVec(t *testing.T, vec *GaugeVec, constrain func(string) string) {
|
||||
vec.Reset() // Actually test Reset now!
|
||||
|
||||
var pair [2]string
|
||||
// Keep track of metrics.
|
||||
expected := map[[2]string]int{}
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
pair[0], pair[1] = fmt.Sprint(i%4), fmt.Sprint(i%5) // Varying combinations multiples.
|
||||
expected[[2]string{pair[0], constrain(pair[1])}]++
|
||||
vec.WithLabelValues(pair[0], pair[1]).Inc()
|
||||
|
||||
expected[[2]string{"v1", constrain("v2")}]++
|
||||
vec.WithLabelValues("v1", "v2").Inc()
|
||||
}
|
||||
|
||||
var total int
|
||||
for _, metrics := range vec.metricMap.metrics {
|
||||
for _, metric := range metrics {
|
||||
total++
|
||||
copy(pair[:], metric.values)
|
||||
|
||||
var metricOut dto.Metric
|
||||
if err := metric.metric.Write(&metricOut); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := *metricOut.Gauge.Value
|
||||
|
||||
var actualPair [2]string
|
||||
for i, label := range metricOut.Label {
|
||||
actualPair[i] = *label.Value
|
||||
}
|
||||
|
||||
// Test output pair against metric.values to ensure we've selected
|
||||
// the right one. We check this to ensure the below check means
|
||||
// anything at all.
|
||||
if actualPair != pair {
|
||||
t.Fatalf("unexpected pair association in metric map: %v != %v", actualPair, pair)
|
||||
}
|
||||
|
||||
if actual != float64(expected[pair]) {
|
||||
t.Fatalf("incorrect counter value for %v: %v != %v", pair, actual, expected[pair])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if total != len(expected) {
|
||||
t.Fatalf("unexpected number of metrics: %v != %v", total, len(expected))
|
||||
}
|
||||
|
||||
vec.Reset()
|
||||
|
||||
if len(vec.metricMap.metrics) > 0 {
|
||||
t.Fatalf("reset failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterVecEndToEndWithCollision(t *testing.T) {
|
||||
vec := NewCounterVec(
|
||||
CounterOpts{
|
||||
|
@ -350,6 +469,39 @@ func TestCurryVecWithCollisions(t *testing.T) {
|
|||
testCurryVec(t, vec)
|
||||
}
|
||||
|
||||
func TestCurryVecWithConstraints(t *testing.T) {
|
||||
constraint := func(s string) string { return "x" + s }
|
||||
t.Run("constrainedLabels overlap variableLabels", func(t *testing.T) {
|
||||
vec := V2.NewCounterVec(CounterVecOpts{
|
||||
CounterOpts{
|
||||
Name: "test",
|
||||
Help: "helpless",
|
||||
},
|
||||
ConstrainedLabels{
|
||||
{Name: "one"},
|
||||
{Name: "two"},
|
||||
{Name: "three", Constraint: constraint},
|
||||
},
|
||||
})
|
||||
testCurryVec(t, vec)
|
||||
})
|
||||
t.Run("constrainedLabels reducing cardinality", func(t *testing.T) {
|
||||
constraint := func(s string) string { return "x" }
|
||||
vec := V2.NewCounterVec(CounterVecOpts{
|
||||
CounterOpts{
|
||||
Name: "test",
|
||||
Help: "helpless",
|
||||
},
|
||||
ConstrainedLabels{
|
||||
{Name: "one"},
|
||||
{Name: "two"},
|
||||
{Name: "three", Constraint: constraint},
|
||||
},
|
||||
})
|
||||
testConstrainedCurryVec(t, vec, constraint)
|
||||
})
|
||||
}
|
||||
|
||||
func testCurryVec(t *testing.T, vec *CounterVec) {
|
||||
assertMetrics := func(t *testing.T) {
|
||||
n := 0
|
||||
|
@ -547,6 +699,211 @@ func testCurryVec(t *testing.T, vec *CounterVec) {
|
|||
})
|
||||
}
|
||||
|
||||
func testConstrainedCurryVec(t *testing.T, vec *CounterVec, constraint func(string) string) {
|
||||
assertMetrics := func(t *testing.T) {
|
||||
n := 0
|
||||
for _, m := range vec.metricMap.metrics {
|
||||
n += len(m)
|
||||
}
|
||||
if n != 2 {
|
||||
t.Error("expected two metrics, got", n)
|
||||
}
|
||||
m := &dto.Metric{}
|
||||
c1, err := vec.GetMetricWithLabelValues("1", "2", "3")
|
||||
if err != nil {
|
||||
t.Fatal("unexpected error getting metric:", err)
|
||||
}
|
||||
c1.Write(m)
|
||||
if want, got := 1., m.GetCounter().GetValue(); want != got {
|
||||
t.Errorf("want %f as counter value, got %f", want, got)
|
||||
}
|
||||
values := map[string]string{}
|
||||
for _, label := range m.Label {
|
||||
values[*label.Name] = *label.Value
|
||||
}
|
||||
if want, got := map[string]string{"one": "1", "two": "2", "three": constraint("3")}, values; !reflect.DeepEqual(want, got) {
|
||||
t.Errorf("want %v as label values, got %v", want, got)
|
||||
}
|
||||
m.Reset()
|
||||
c2, err := vec.GetMetricWithLabelValues("11", "22", "33")
|
||||
if err != nil {
|
||||
t.Fatal("unexpected error getting metric:", err)
|
||||
}
|
||||
c2.Write(m)
|
||||
if want, got := 1., m.GetCounter().GetValue(); want != got {
|
||||
t.Errorf("want %f as counter value, got %f", want, got)
|
||||
}
|
||||
values = map[string]string{}
|
||||
for _, label := range m.Label {
|
||||
values[*label.Name] = *label.Value
|
||||
}
|
||||
if want, got := map[string]string{"one": "11", "two": "22", "three": constraint("33")}, values; !reflect.DeepEqual(want, got) {
|
||||
t.Errorf("want %v as label values, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
assertNoMetric := func(t *testing.T) {
|
||||
if n := len(vec.metricMap.metrics); n != 0 {
|
||||
t.Error("expected no metrics, got", n)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("zero labels", func(t *testing.T) {
|
||||
c1 := vec.MustCurryWith(nil)
|
||||
c2 := vec.MustCurryWith(nil)
|
||||
c1.WithLabelValues("1", "2", "3").Inc()
|
||||
c2.With(Labels{"one": "11", "two": "22", "three": "33"}).Inc()
|
||||
assertMetrics(t)
|
||||
if !c1.Delete(Labels{"one": "1", "two": "2", "three": "3"}) {
|
||||
t.Error("deletion failed")
|
||||
}
|
||||
if !c2.DeleteLabelValues("11", "22", "33") {
|
||||
t.Error("deletion failed")
|
||||
}
|
||||
assertNoMetric(t)
|
||||
})
|
||||
t.Run("first label", func(t *testing.T) {
|
||||
c1 := vec.MustCurryWith(Labels{"one": "1"})
|
||||
c2 := vec.MustCurryWith(Labels{"one": "11"})
|
||||
c1.WithLabelValues("2", "3").Inc()
|
||||
c2.With(Labels{"two": "22", "three": "33"}).Inc()
|
||||
assertMetrics(t)
|
||||
if c1.Delete(Labels{"two": "22", "three": "33"}) {
|
||||
t.Error("deletion unexpectedly succeeded")
|
||||
}
|
||||
if c2.DeleteLabelValues("2", "3") {
|
||||
t.Error("deletion unexpectedly succeeded")
|
||||
}
|
||||
if !c1.Delete(Labels{"two": "2", "three": "3"}) {
|
||||
t.Error("deletion failed")
|
||||
}
|
||||
if !c2.DeleteLabelValues("22", "33") {
|
||||
t.Error("deletion failed")
|
||||
}
|
||||
assertNoMetric(t)
|
||||
})
|
||||
t.Run("middle label", func(t *testing.T) {
|
||||
c1 := vec.MustCurryWith(Labels{"two": "2"})
|
||||
c2 := vec.MustCurryWith(Labels{"two": "22"})
|
||||
c1.WithLabelValues("1", "3").Inc()
|
||||
c2.With(Labels{"one": "11", "three": "33"}).Inc()
|
||||
assertMetrics(t)
|
||||
if c1.Delete(Labels{"one": "11", "three": "33"}) {
|
||||
t.Error("deletion unexpectedly succeeded")
|
||||
}
|
||||
if c2.DeleteLabelValues("1", "3") {
|
||||
t.Error("deletion unexpectedly succeeded")
|
||||
}
|
||||
if !c1.Delete(Labels{"one": "1", "three": "3"}) {
|
||||
t.Error("deletion failed")
|
||||
}
|
||||
if !c2.DeleteLabelValues("11", "33") {
|
||||
t.Error("deletion failed")
|
||||
}
|
||||
assertNoMetric(t)
|
||||
})
|
||||
t.Run("last label (constrained to static value)", func(t *testing.T) {
|
||||
c1 := vec.MustCurryWith(Labels{"three": "3"})
|
||||
c2 := vec.MustCurryWith(Labels{"three": "33"})
|
||||
c1.WithLabelValues("1", "2").Inc()
|
||||
c2.With(Labels{"one": "11", "two": "22"}).Inc()
|
||||
assertMetrics(t)
|
||||
if !c1.Delete(Labels{"two": "22", "one": "11"}) {
|
||||
t.Error("deletion failed")
|
||||
}
|
||||
if !c2.DeleteLabelValues("1", "2") {
|
||||
t.Error("deletion failed")
|
||||
}
|
||||
assertNoMetric(t)
|
||||
})
|
||||
t.Run("two labels", func(t *testing.T) {
|
||||
c1 := vec.MustCurryWith(Labels{"three": "3", "one": "1"})
|
||||
c2 := vec.MustCurryWith(Labels{"three": "33", "one": "11"})
|
||||
c1.WithLabelValues("2").Inc()
|
||||
c2.With(Labels{"two": "22"}).Inc()
|
||||
assertMetrics(t)
|
||||
if c1.Delete(Labels{"two": "22"}) {
|
||||
t.Error("deletion unexpectedly succeeded")
|
||||
}
|
||||
if c2.DeleteLabelValues("2") {
|
||||
t.Error("deletion unexpectedly succeeded")
|
||||
}
|
||||
if !c1.Delete(Labels{"two": "2"}) {
|
||||
t.Error("deletion failed")
|
||||
}
|
||||
if !c2.DeleteLabelValues("22") {
|
||||
t.Error("deletion failed")
|
||||
}
|
||||
assertNoMetric(t)
|
||||
})
|
||||
t.Run("all labels", func(t *testing.T) {
|
||||
c1 := vec.MustCurryWith(Labels{"three": "3", "two": "2", "one": "1"})
|
||||
c2 := vec.MustCurryWith(Labels{"three": "33", "one": "11", "two": "22"})
|
||||
c1.WithLabelValues().Inc()
|
||||
c2.With(nil).Inc()
|
||||
assertMetrics(t)
|
||||
if !c1.Delete(Labels{}) {
|
||||
t.Error("deletion failed")
|
||||
}
|
||||
if !c2.DeleteLabelValues() {
|
||||
t.Error("deletion failed")
|
||||
}
|
||||
assertNoMetric(t)
|
||||
})
|
||||
t.Run("double curry", func(t *testing.T) {
|
||||
c1 := vec.MustCurryWith(Labels{"three": "3"}).MustCurryWith(Labels{"one": "1"})
|
||||
c2 := vec.MustCurryWith(Labels{"three": "33"}).MustCurryWith(Labels{"one": "11"})
|
||||
c1.WithLabelValues("2").Inc()
|
||||
c2.With(Labels{"two": "22"}).Inc()
|
||||
assertMetrics(t)
|
||||
if c1.Delete(Labels{"two": "22"}) {
|
||||
t.Error("deletion unexpectedly succeeded")
|
||||
}
|
||||
if c2.DeleteLabelValues("2") {
|
||||
t.Error("deletion unexpectedly succeeded")
|
||||
}
|
||||
if !c1.Delete(Labels{"two": "2"}) {
|
||||
t.Error("deletion failed")
|
||||
}
|
||||
if !c2.DeleteLabelValues("22") {
|
||||
t.Error("deletion failed")
|
||||
}
|
||||
assertNoMetric(t)
|
||||
})
|
||||
t.Run("use already curried label", func(t *testing.T) {
|
||||
c1 := vec.MustCurryWith(Labels{"three": "3"})
|
||||
if _, err := c1.GetMetricWithLabelValues("1", "2", "3"); err == nil {
|
||||
t.Error("expected error when using already curried label")
|
||||
}
|
||||
if _, err := c1.GetMetricWith(Labels{"one": "1", "two": "2", "three": "3"}); err == nil {
|
||||
t.Error("expected error when using already curried label")
|
||||
}
|
||||
assertNoMetric(t)
|
||||
c1.WithLabelValues("1", "2").Inc()
|
||||
if c1.Delete(Labels{"one": "1", "two": "2", "three": "3"}) {
|
||||
t.Error("deletion unexpectedly succeeded")
|
||||
}
|
||||
if !c1.Delete(Labels{"one": "1", "two": "2"}) {
|
||||
t.Error("deletion failed")
|
||||
}
|
||||
assertNoMetric(t)
|
||||
})
|
||||
t.Run("curry already curried label", func(t *testing.T) {
|
||||
if _, err := vec.MustCurryWith(Labels{"three": "3"}).CurryWith(Labels{"three": "33"}); err == nil {
|
||||
t.Error("currying unexpectedly succeeded")
|
||||
} else if err.Error() != `label name "three" is already curried` {
|
||||
t.Error("currying returned unexpected error:", err)
|
||||
}
|
||||
})
|
||||
t.Run("unknown label", func(t *testing.T) {
|
||||
if _, err := vec.CurryWith(Labels{"foo": "bar"}); err == nil {
|
||||
t.Error("currying unexpectedly succeeded")
|
||||
} else if err.Error() != "1 unknown label(s) found during currying" {
|
||||
t.Error("currying returned unexpected error:", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkMetricVecWithLabelValuesBasic(b *testing.B) {
|
||||
benchmarkMetricVecWithLabelValues(b, map[string][]string{
|
||||
"l1": {"onevalue"},
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright 2022 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package prometheus
|
||||
|
||||
type v2 struct{}
|
||||
|
||||
// V2 is a struct that can be referenced to access experimental API that might
|
||||
// be present in v2 of client golang someday. It offers extended functionality
|
||||
// of v1 with slightly changed API. It is acceptable to use some pieces from v1
|
||||
// and e.g `prometheus.NewGauge` and some from v2 e.g. `prometheus.V2.NewDesc`
|
||||
// in the same codebase.
|
||||
var V2 = v2{}
|
|
@ -206,7 +206,7 @@ func wrapDesc(desc *Desc, prefix string, labels Labels) *Desc {
|
|||
constLabels[ln] = lv
|
||||
}
|
||||
// NewDesc will do remaining validations.
|
||||
newDesc := NewDesc(prefix+desc.fqName, desc.help, desc.variableLabels, constLabels)
|
||||
newDesc := V2.NewDesc(prefix+desc.fqName, desc.help, desc.variableLabels, constLabels)
|
||||
// Propagate errors if there was any. This will override any errer
|
||||
// created by NewDesc above, i.e. earlier errors get precedence.
|
||||
if desc.err != nil {
|
||||
|
|
Loading…
Reference in New Issue