mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Merge pull request #99738 from YoyinZyc/unbounded_metric
Enforce metric cardinality check to Gauge, Histogram and Summary metric
This commit is contained in:
commit
f63cac6cdf
@ -211,12 +211,12 @@ func TestCounterVec(t *testing.T) {
|
|||||||
|
|
||||||
func TestCounterWithLabelValueAllowList(t *testing.T) {
|
func TestCounterWithLabelValueAllowList(t *testing.T) {
|
||||||
labelAllowValues := map[string]string{
|
labelAllowValues := map[string]string{
|
||||||
"namespace_subsystem_metric_test_name,label_a": "allowed",
|
"namespace_subsystem_metric_allowlist_test,label_a": "allowed",
|
||||||
}
|
}
|
||||||
labels := []string{"label_a", "label_b"}
|
labels := []string{"label_a", "label_b"}
|
||||||
opts := &CounterOpts{
|
opts := &CounterOpts{
|
||||||
Namespace: "namespace",
|
Namespace: "namespace",
|
||||||
Name: "metric_test_name",
|
Name: "metric_allowlist_test",
|
||||||
Subsystem: "subsystem",
|
Subsystem: "subsystem",
|
||||||
}
|
}
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
|
@ -94,13 +94,20 @@ type GaugeVec struct {
|
|||||||
func NewGaugeVec(opts *GaugeOpts, labels []string) *GaugeVec {
|
func NewGaugeVec(opts *GaugeOpts, labels []string) *GaugeVec {
|
||||||
opts.StabilityLevel.setDefaults()
|
opts.StabilityLevel.setDefaults()
|
||||||
|
|
||||||
|
fqName := BuildFQName(opts.Namespace, opts.Subsystem, opts.Name)
|
||||||
|
allowListLock.RLock()
|
||||||
|
if allowList, ok := labelValueAllowLists[fqName]; ok {
|
||||||
|
opts.LabelValueAllowLists = allowList
|
||||||
|
}
|
||||||
|
allowListLock.RUnlock()
|
||||||
|
|
||||||
cv := &GaugeVec{
|
cv := &GaugeVec{
|
||||||
GaugeVec: noopGaugeVec,
|
GaugeVec: noopGaugeVec,
|
||||||
GaugeOpts: opts,
|
GaugeOpts: opts,
|
||||||
originalLabels: labels,
|
originalLabels: labels,
|
||||||
lazyMetric: lazyMetric{},
|
lazyMetric: lazyMetric{},
|
||||||
}
|
}
|
||||||
cv.lazyInit(cv, BuildFQName(opts.Namespace, opts.Subsystem, opts.Name))
|
cv.lazyInit(cv, fqName)
|
||||||
return cv
|
return cv
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,6 +146,9 @@ func (v *GaugeVec) WithLabelValues(lvs ...string) GaugeMetric {
|
|||||||
if !v.IsCreated() {
|
if !v.IsCreated() {
|
||||||
return noop // return no-op gauge
|
return noop // return no-op gauge
|
||||||
}
|
}
|
||||||
|
if v.LabelValueAllowLists != nil {
|
||||||
|
v.LabelValueAllowLists.ConstrainToAllowedList(v.originalLabels, lvs)
|
||||||
|
}
|
||||||
return v.GaugeVec.WithLabelValues(lvs...)
|
return v.GaugeVec.WithLabelValues(lvs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,6 +160,9 @@ func (v *GaugeVec) With(labels map[string]string) GaugeMetric {
|
|||||||
if !v.IsCreated() {
|
if !v.IsCreated() {
|
||||||
return noop // return no-op gauge
|
return noop // return no-op gauge
|
||||||
}
|
}
|
||||||
|
if v.LabelValueAllowLists != nil {
|
||||||
|
v.LabelValueAllowLists.ConstrainLabelMap(labels)
|
||||||
|
}
|
||||||
return v.GaugeVec.With(labels)
|
return v.GaugeVec.With(labels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,3 +268,80 @@ namespace_subsystem_metric_deprecated 1
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGaugeWithLabelValueAllowList(t *testing.T) {
|
||||||
|
labelAllowValues := map[string]string{
|
||||||
|
"namespace_subsystem_metric_allowlist_test,label_a": "allowed",
|
||||||
|
}
|
||||||
|
labels := []string{"label_a", "label_b"}
|
||||||
|
opts := &GaugeOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_allowlist_test",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
}
|
||||||
|
var tests = []struct {
|
||||||
|
desc string
|
||||||
|
labelValues [][]string
|
||||||
|
expectMetricValues map[string]float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Test no unexpected input",
|
||||||
|
labelValues: [][]string{{"allowed", "b1"}, {"allowed", "b2"}},
|
||||||
|
expectMetricValues: map[string]float64{
|
||||||
|
"allowed b1": 100.0,
|
||||||
|
"allowed b2": 100.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Test unexpected input",
|
||||||
|
labelValues: [][]string{{"allowed", "b1"}, {"not_allowed", "b1"}},
|
||||||
|
expectMetricValues: map[string]float64{
|
||||||
|
"allowed b1": 100.0,
|
||||||
|
"unexpected b1": 100.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
SetLabelAllowListFromCLI(labelAllowValues)
|
||||||
|
registry := newKubeRegistry(apimachineryversion.Info{
|
||||||
|
Major: "1",
|
||||||
|
Minor: "15",
|
||||||
|
GitVersion: "v1.15.0-alpha-1.12345",
|
||||||
|
})
|
||||||
|
g := NewGaugeVec(opts, labels)
|
||||||
|
registry.MustRegister(g)
|
||||||
|
|
||||||
|
for _, lv := range test.labelValues {
|
||||||
|
g.WithLabelValues(lv...).Set(100.0)
|
||||||
|
}
|
||||||
|
mfs, err := registry.Gather()
|
||||||
|
assert.Nil(t, err, "Gather failed %v", err)
|
||||||
|
|
||||||
|
for _, mf := range mfs {
|
||||||
|
if *mf.Name != BuildFQName(opts.Namespace, opts.Subsystem, opts.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mfMetric := mf.GetMetric()
|
||||||
|
|
||||||
|
for _, m := range mfMetric {
|
||||||
|
var aValue, bValue string
|
||||||
|
for _, l := range m.Label {
|
||||||
|
if *l.Name == "label_a" {
|
||||||
|
aValue = *l.Value
|
||||||
|
}
|
||||||
|
if *l.Name == "label_b" {
|
||||||
|
bValue = *l.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
labelValuePair := aValue + " " + bValue
|
||||||
|
expectedValue, ok := test.expectMetricValues[labelValuePair]
|
||||||
|
assert.True(t, ok, "Got unexpected label values, lable_a is %v, label_b is %v", aValue, bValue)
|
||||||
|
actualValue := m.GetGauge().GetValue()
|
||||||
|
assert.Equalf(t, expectedValue, actualValue, "Got %v, wanted %v as the gauge while setting label_a to %v and label b to %v", actualValue, expectedValue, aValue, bValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -104,13 +104,20 @@ type HistogramVec struct {
|
|||||||
func NewHistogramVec(opts *HistogramOpts, labels []string) *HistogramVec {
|
func NewHistogramVec(opts *HistogramOpts, labels []string) *HistogramVec {
|
||||||
opts.StabilityLevel.setDefaults()
|
opts.StabilityLevel.setDefaults()
|
||||||
|
|
||||||
|
fqName := BuildFQName(opts.Namespace, opts.Subsystem, opts.Name)
|
||||||
|
allowListLock.RLock()
|
||||||
|
if allowList, ok := labelValueAllowLists[fqName]; ok {
|
||||||
|
opts.LabelValueAllowLists = allowList
|
||||||
|
}
|
||||||
|
allowListLock.RUnlock()
|
||||||
|
|
||||||
v := &HistogramVec{
|
v := &HistogramVec{
|
||||||
HistogramVec: noopHistogramVec,
|
HistogramVec: noopHistogramVec,
|
||||||
HistogramOpts: opts,
|
HistogramOpts: opts,
|
||||||
originalLabels: labels,
|
originalLabels: labels,
|
||||||
lazyMetric: lazyMetric{},
|
lazyMetric: lazyMetric{},
|
||||||
}
|
}
|
||||||
v.lazyInit(v, BuildFQName(opts.Namespace, opts.Subsystem, opts.Name))
|
v.lazyInit(v, fqName)
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,6 +152,9 @@ func (v *HistogramVec) WithLabelValues(lvs ...string) ObserverMetric {
|
|||||||
if !v.IsCreated() {
|
if !v.IsCreated() {
|
||||||
return noop
|
return noop
|
||||||
}
|
}
|
||||||
|
if v.LabelValueAllowLists != nil {
|
||||||
|
v.LabelValueAllowLists.ConstrainToAllowedList(v.originalLabels, lvs)
|
||||||
|
}
|
||||||
return v.HistogramVec.WithLabelValues(lvs...)
|
return v.HistogramVec.WithLabelValues(lvs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,6 +166,9 @@ func (v *HistogramVec) With(labels map[string]string) ObserverMetric {
|
|||||||
if !v.IsCreated() {
|
if !v.IsCreated() {
|
||||||
return noop
|
return noop
|
||||||
}
|
}
|
||||||
|
if v.LabelValueAllowLists != nil {
|
||||||
|
v.LabelValueAllowLists.ConstrainLabelMap(labels)
|
||||||
|
}
|
||||||
return v.HistogramVec.With(labels)
|
return v.HistogramVec.With(labels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,3 +204,80 @@ func TestHistogramVec(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHistogramWithLabelValueAllowList(t *testing.T) {
|
||||||
|
labelAllowValues := map[string]string{
|
||||||
|
"namespace_subsystem_metric_allowlist_test,label_a": "allowed",
|
||||||
|
}
|
||||||
|
labels := []string{"label_a", "label_b"}
|
||||||
|
opts := &HistogramOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_allowlist_test",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
}
|
||||||
|
var tests = []struct {
|
||||||
|
desc string
|
||||||
|
labelValues [][]string
|
||||||
|
expectMetricValues map[string]int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Test no unexpected input",
|
||||||
|
labelValues: [][]string{{"allowed", "b1"}, {"allowed", "b2"}},
|
||||||
|
expectMetricValues: map[string]int{
|
||||||
|
"allowed b1": 1.0,
|
||||||
|
"allowed b2": 1.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Test unexpected input",
|
||||||
|
labelValues: [][]string{{"allowed", "b1"}, {"not_allowed", "b1"}},
|
||||||
|
expectMetricValues: map[string]int{
|
||||||
|
"allowed b1": 1.0,
|
||||||
|
"unexpected b1": 1.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
SetLabelAllowListFromCLI(labelAllowValues)
|
||||||
|
registry := newKubeRegistry(apimachineryversion.Info{
|
||||||
|
Major: "1",
|
||||||
|
Minor: "15",
|
||||||
|
GitVersion: "v1.15.0-alpha-1.12345",
|
||||||
|
})
|
||||||
|
c := NewHistogramVec(opts, labels)
|
||||||
|
registry.MustRegister(c)
|
||||||
|
|
||||||
|
for _, lv := range test.labelValues {
|
||||||
|
c.WithLabelValues(lv...).Observe(1.0)
|
||||||
|
}
|
||||||
|
mfs, err := registry.Gather()
|
||||||
|
assert.Nil(t, err, "Gather failed %v", err)
|
||||||
|
|
||||||
|
for _, mf := range mfs {
|
||||||
|
if *mf.Name != BuildFQName(opts.Namespace, opts.Subsystem, opts.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mfMetric := mf.GetMetric()
|
||||||
|
|
||||||
|
for _, m := range mfMetric {
|
||||||
|
var aValue, bValue string
|
||||||
|
for _, l := range m.Label {
|
||||||
|
if *l.Name == "label_a" {
|
||||||
|
aValue = *l.Value
|
||||||
|
}
|
||||||
|
if *l.Name == "label_b" {
|
||||||
|
bValue = *l.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
labelValuePair := aValue + " " + bValue
|
||||||
|
expectedValue, ok := test.expectMetricValues[labelValuePair]
|
||||||
|
assert.True(t, ok, "Got unexpected label values, lable_a is %v, label_b is %v", aValue, bValue)
|
||||||
|
actualValue := int(m.GetHistogram().GetSampleCount())
|
||||||
|
assert.Equalf(t, expectedValue, actualValue, "Got %v, wanted %v as the count while setting label_a to %v and label b to %v", actualValue, expectedValue, aValue, bValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -148,16 +148,17 @@ func (o *GaugeOpts) toPromGaugeOpts() prometheus.GaugeOpts {
|
|||||||
// and can safely be left at their zero value, although it is strongly
|
// and can safely be left at their zero value, although it is strongly
|
||||||
// encouraged to set a Help string.
|
// encouraged to set a Help string.
|
||||||
type HistogramOpts struct {
|
type HistogramOpts struct {
|
||||||
Namespace string
|
Namespace string
|
||||||
Subsystem string
|
Subsystem string
|
||||||
Name string
|
Name string
|
||||||
Help string
|
Help string
|
||||||
ConstLabels map[string]string
|
ConstLabels map[string]string
|
||||||
Buckets []float64
|
Buckets []float64
|
||||||
DeprecatedVersion string
|
DeprecatedVersion string
|
||||||
deprecateOnce sync.Once
|
deprecateOnce sync.Once
|
||||||
annotateOnce sync.Once
|
annotateOnce sync.Once
|
||||||
StabilityLevel StabilityLevel
|
StabilityLevel StabilityLevel
|
||||||
|
LabelValueAllowLists *MetricLabelAllowList
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modify help description on the metric description.
|
// Modify help description on the metric description.
|
||||||
@ -194,19 +195,20 @@ func (o *HistogramOpts) toPromHistogramOpts() prometheus.HistogramOpts {
|
|||||||
// a help string and to explicitly set the Objectives field to the desired value
|
// a help string and to explicitly set the Objectives field to the desired value
|
||||||
// as the default value will change in the upcoming v0.10 of the library.
|
// as the default value will change in the upcoming v0.10 of the library.
|
||||||
type SummaryOpts struct {
|
type SummaryOpts struct {
|
||||||
Namespace string
|
Namespace string
|
||||||
Subsystem string
|
Subsystem string
|
||||||
Name string
|
Name string
|
||||||
Help string
|
Help string
|
||||||
ConstLabels map[string]string
|
ConstLabels map[string]string
|
||||||
Objectives map[float64]float64
|
Objectives map[float64]float64
|
||||||
MaxAge time.Duration
|
MaxAge time.Duration
|
||||||
AgeBuckets uint32
|
AgeBuckets uint32
|
||||||
BufCap uint32
|
BufCap uint32
|
||||||
DeprecatedVersion string
|
DeprecatedVersion string
|
||||||
deprecateOnce sync.Once
|
deprecateOnce sync.Once
|
||||||
annotateOnce sync.Once
|
annotateOnce sync.Once
|
||||||
StabilityLevel StabilityLevel
|
StabilityLevel StabilityLevel
|
||||||
|
LabelValueAllowLists *MetricLabelAllowList
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modify help description on the metric description.
|
// Modify help description on the metric description.
|
||||||
|
@ -99,12 +99,19 @@ type SummaryVec struct {
|
|||||||
func NewSummaryVec(opts *SummaryOpts, labels []string) *SummaryVec {
|
func NewSummaryVec(opts *SummaryOpts, labels []string) *SummaryVec {
|
||||||
opts.StabilityLevel.setDefaults()
|
opts.StabilityLevel.setDefaults()
|
||||||
|
|
||||||
|
fqName := BuildFQName(opts.Namespace, opts.Subsystem, opts.Name)
|
||||||
|
allowListLock.RLock()
|
||||||
|
if allowList, ok := labelValueAllowLists[fqName]; ok {
|
||||||
|
opts.LabelValueAllowLists = allowList
|
||||||
|
}
|
||||||
|
allowListLock.RUnlock()
|
||||||
|
|
||||||
v := &SummaryVec{
|
v := &SummaryVec{
|
||||||
SummaryOpts: opts,
|
SummaryOpts: opts,
|
||||||
originalLabels: labels,
|
originalLabels: labels,
|
||||||
lazyMetric: lazyMetric{},
|
lazyMetric: lazyMetric{},
|
||||||
}
|
}
|
||||||
v.lazyInit(v, BuildFQName(opts.Namespace, opts.Subsystem, opts.Name))
|
v.lazyInit(v, fqName)
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,6 +146,9 @@ func (v *SummaryVec) WithLabelValues(lvs ...string) ObserverMetric {
|
|||||||
if !v.IsCreated() {
|
if !v.IsCreated() {
|
||||||
return noop
|
return noop
|
||||||
}
|
}
|
||||||
|
if v.LabelValueAllowLists != nil {
|
||||||
|
v.LabelValueAllowLists.ConstrainToAllowedList(v.originalLabels, lvs)
|
||||||
|
}
|
||||||
return v.SummaryVec.WithLabelValues(lvs...)
|
return v.SummaryVec.WithLabelValues(lvs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,6 +160,9 @@ func (v *SummaryVec) With(labels map[string]string) ObserverMetric {
|
|||||||
if !v.IsCreated() {
|
if !v.IsCreated() {
|
||||||
return noop
|
return noop
|
||||||
}
|
}
|
||||||
|
if v.LabelValueAllowLists != nil {
|
||||||
|
v.LabelValueAllowLists.ConstrainLabelMap(labels)
|
||||||
|
}
|
||||||
return v.SummaryVec.With(labels)
|
return v.SummaryVec.With(labels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,3 +198,80 @@ func TestSummaryVec(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSummaryWithLabelValueAllowList(t *testing.T) {
|
||||||
|
labelAllowValues := map[string]string{
|
||||||
|
"namespace_subsystem_metric_allowlist_test,label_a": "allowed",
|
||||||
|
}
|
||||||
|
labels := []string{"label_a", "label_b"}
|
||||||
|
opts := &SummaryOpts{
|
||||||
|
Namespace: "namespace",
|
||||||
|
Name: "metric_allowlist_test",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
}
|
||||||
|
var tests = []struct {
|
||||||
|
desc string
|
||||||
|
labelValues [][]string
|
||||||
|
expectMetricValues map[string]int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Test no unexpected input",
|
||||||
|
labelValues: [][]string{{"allowed", "b1"}, {"allowed", "b2"}},
|
||||||
|
expectMetricValues: map[string]int{
|
||||||
|
"allowed b1": 1,
|
||||||
|
"allowed b2": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Test unexpected input",
|
||||||
|
labelValues: [][]string{{"allowed", "b1"}, {"not_allowed", "b1"}},
|
||||||
|
expectMetricValues: map[string]int{
|
||||||
|
"allowed b1": 1,
|
||||||
|
"unexpected b1": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
SetLabelAllowListFromCLI(labelAllowValues)
|
||||||
|
registry := newKubeRegistry(apimachineryversion.Info{
|
||||||
|
Major: "1",
|
||||||
|
Minor: "15",
|
||||||
|
GitVersion: "v1.15.0-alpha-1.12345",
|
||||||
|
})
|
||||||
|
c := NewSummaryVec(opts, labels)
|
||||||
|
registry.MustRegister(c)
|
||||||
|
|
||||||
|
for _, lv := range test.labelValues {
|
||||||
|
c.WithLabelValues(lv...).Observe(1.0)
|
||||||
|
}
|
||||||
|
mfs, err := registry.Gather()
|
||||||
|
assert.Nil(t, err, "Gather failed %v", err)
|
||||||
|
|
||||||
|
for _, mf := range mfs {
|
||||||
|
if *mf.Name != BuildFQName(opts.Namespace, opts.Subsystem, opts.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mfMetric := mf.GetMetric()
|
||||||
|
|
||||||
|
for _, m := range mfMetric {
|
||||||
|
var aValue, bValue string
|
||||||
|
for _, l := range m.Label {
|
||||||
|
if *l.Name == "label_a" {
|
||||||
|
aValue = *l.Value
|
||||||
|
}
|
||||||
|
if *l.Name == "label_b" {
|
||||||
|
bValue = *l.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
labelValuePair := aValue + " " + bValue
|
||||||
|
expectedValue, ok := test.expectMetricValues[labelValuePair]
|
||||||
|
assert.True(t, ok, "Got unexpected label values, lable_a is %v, label_b is %v", aValue, bValue)
|
||||||
|
actualValue := int(m.GetSummary().GetSampleCount())
|
||||||
|
assert.Equalf(t, expectedValue, actualValue, "Got %v, wanted %v as the count while setting label_a to %v and label b to %v", actualValue, expectedValue, aValue, bValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user