...

Source file src/k8s.io/component-base/metrics/opts.go

Documentation: k8s.io/component-base/metrics

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package metrics
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/prometheus/client_golang/prometheus"
    28  	"gopkg.in/yaml.v2"
    29  
    30  	"k8s.io/apimachinery/pkg/util/sets"
    31  	promext "k8s.io/component-base/metrics/prometheusextension"
    32  	"k8s.io/klog/v2"
    33  )
    34  
    35  var (
    36  	labelValueAllowLists = map[string]*MetricLabelAllowList{}
    37  	allowListLock        sync.RWMutex
    38  )
    39  
    40  // KubeOpts is superset struct for prometheus.Opts. The prometheus Opts structure
    41  // is purposefully not embedded here because that would change struct initialization
    42  // in the manner which people are currently accustomed.
    43  //
    44  // Name must be set to a non-empty string. DeprecatedVersion is defined only
    45  // if the metric for which this options applies is, in fact, deprecated.
    46  type KubeOpts struct {
    47  	Namespace            string
    48  	Subsystem            string
    49  	Name                 string
    50  	Help                 string
    51  	ConstLabels          map[string]string
    52  	DeprecatedVersion    string
    53  	deprecateOnce        sync.Once
    54  	annotateOnce         sync.Once
    55  	StabilityLevel       StabilityLevel
    56  	LabelValueAllowLists *MetricLabelAllowList
    57  }
    58  
    59  // BuildFQName joins the given three name components by "_". Empty name
    60  // components are ignored. If the name parameter itself is empty, an empty
    61  // string is returned, no matter what. Metric implementations included in this
    62  // library use this function internally to generate the fully-qualified metric
    63  // name from the name component in their Opts. Users of the library will only
    64  // need this function if they implement their own Metric or instantiate a Desc
    65  // (with NewDesc) directly.
    66  func BuildFQName(namespace, subsystem, name string) string {
    67  	return prometheus.BuildFQName(namespace, subsystem, name)
    68  }
    69  
    70  // StabilityLevel represents the API guarantees for a given defined metric.
    71  type StabilityLevel string
    72  
    73  const (
    74  	// INTERNAL metrics have no stability guarantees, as such, labels may
    75  	// be arbitrarily added/removed and the metric may be deleted at any time.
    76  	INTERNAL StabilityLevel = "INTERNAL"
    77  	// ALPHA metrics have no stability guarantees, as such, labels may
    78  	// be arbitrarily added/removed and the metric may be deleted at any time.
    79  	ALPHA StabilityLevel = "ALPHA"
    80  	// BETA metrics are governed by the deprecation policy outlined in by
    81  	// the control plane metrics stability KEP.
    82  	BETA StabilityLevel = "BETA"
    83  	// STABLE metrics are guaranteed not be mutated and removal is governed by
    84  	// the deprecation policy outlined in by the control plane metrics stability KEP.
    85  	STABLE StabilityLevel = "STABLE"
    86  )
    87  
    88  // setDefaults takes 'ALPHA' in case of empty.
    89  func (sl *StabilityLevel) setDefaults() {
    90  	switch *sl {
    91  	case "":
    92  		*sl = ALPHA
    93  	default:
    94  		// no-op, since we have a StabilityLevel already
    95  	}
    96  }
    97  
    98  // CounterOpts is an alias for Opts. See there for doc comments.
    99  type CounterOpts KubeOpts
   100  
   101  // Modify help description on the metric description.
   102  func (o *CounterOpts) markDeprecated() {
   103  	o.deprecateOnce.Do(func() {
   104  		o.Help = fmt.Sprintf("(Deprecated since %v) %v", o.DeprecatedVersion, o.Help)
   105  	})
   106  }
   107  
   108  // annotateStabilityLevel annotates help description on the metric description with the stability level
   109  // of the metric
   110  func (o *CounterOpts) annotateStabilityLevel() {
   111  	o.annotateOnce.Do(func() {
   112  		o.Help = fmt.Sprintf("[%v] %v", o.StabilityLevel, o.Help)
   113  	})
   114  }
   115  
   116  // convenience function to allow easy transformation to the prometheus
   117  // counterpart. This will do more once we have a proper label abstraction
   118  func (o *CounterOpts) toPromCounterOpts() prometheus.CounterOpts {
   119  	return prometheus.CounterOpts{
   120  		Namespace:   o.Namespace,
   121  		Subsystem:   o.Subsystem,
   122  		Name:        o.Name,
   123  		Help:        o.Help,
   124  		ConstLabels: o.ConstLabels,
   125  	}
   126  }
   127  
   128  // GaugeOpts is an alias for Opts. See there for doc comments.
   129  type GaugeOpts KubeOpts
   130  
   131  // Modify help description on the metric description.
   132  func (o *GaugeOpts) markDeprecated() {
   133  	o.deprecateOnce.Do(func() {
   134  		o.Help = fmt.Sprintf("(Deprecated since %v) %v", o.DeprecatedVersion, o.Help)
   135  	})
   136  }
   137  
   138  // annotateStabilityLevel annotates help description on the metric description with the stability level
   139  // of the metric
   140  func (o *GaugeOpts) annotateStabilityLevel() {
   141  	o.annotateOnce.Do(func() {
   142  		o.Help = fmt.Sprintf("[%v] %v", o.StabilityLevel, o.Help)
   143  	})
   144  }
   145  
   146  // convenience function to allow easy transformation to the prometheus
   147  // counterpart. This will do more once we have a proper label abstraction
   148  func (o *GaugeOpts) toPromGaugeOpts() prometheus.GaugeOpts {
   149  	return prometheus.GaugeOpts{
   150  		Namespace:   o.Namespace,
   151  		Subsystem:   o.Subsystem,
   152  		Name:        o.Name,
   153  		Help:        o.Help,
   154  		ConstLabels: o.ConstLabels,
   155  	}
   156  }
   157  
   158  // HistogramOpts bundles the options for creating a Histogram metric. It is
   159  // mandatory to set Name to a non-empty string. All other fields are optional
   160  // and can safely be left at their zero value, although it is strongly
   161  // encouraged to set a Help string.
   162  type HistogramOpts struct {
   163  	Namespace            string
   164  	Subsystem            string
   165  	Name                 string
   166  	Help                 string
   167  	ConstLabels          map[string]string
   168  	Buckets              []float64
   169  	DeprecatedVersion    string
   170  	deprecateOnce        sync.Once
   171  	annotateOnce         sync.Once
   172  	StabilityLevel       StabilityLevel
   173  	LabelValueAllowLists *MetricLabelAllowList
   174  }
   175  
   176  // Modify help description on the metric description.
   177  func (o *HistogramOpts) markDeprecated() {
   178  	o.deprecateOnce.Do(func() {
   179  		o.Help = fmt.Sprintf("(Deprecated since %v) %v", o.DeprecatedVersion, o.Help)
   180  	})
   181  }
   182  
   183  // annotateStabilityLevel annotates help description on the metric description with the stability level
   184  // of the metric
   185  func (o *HistogramOpts) annotateStabilityLevel() {
   186  	o.annotateOnce.Do(func() {
   187  		o.Help = fmt.Sprintf("[%v] %v", o.StabilityLevel, o.Help)
   188  	})
   189  }
   190  
   191  // convenience function to allow easy transformation to the prometheus
   192  // counterpart. This will do more once we have a proper label abstraction
   193  func (o *HistogramOpts) toPromHistogramOpts() prometheus.HistogramOpts {
   194  	return prometheus.HistogramOpts{
   195  		Namespace:   o.Namespace,
   196  		Subsystem:   o.Subsystem,
   197  		Name:        o.Name,
   198  		Help:        o.Help,
   199  		ConstLabels: o.ConstLabels,
   200  		Buckets:     o.Buckets,
   201  	}
   202  }
   203  
   204  // TimingHistogramOpts bundles the options for creating a TimingHistogram metric. It is
   205  // mandatory to set Name to a non-empty string. All other fields are optional
   206  // and can safely be left at their zero value, although it is strongly
   207  // encouraged to set a Help string.
   208  type TimingHistogramOpts struct {
   209  	Namespace            string
   210  	Subsystem            string
   211  	Name                 string
   212  	Help                 string
   213  	ConstLabels          map[string]string
   214  	Buckets              []float64
   215  	InitialValue         float64
   216  	DeprecatedVersion    string
   217  	deprecateOnce        sync.Once
   218  	annotateOnce         sync.Once
   219  	StabilityLevel       StabilityLevel
   220  	LabelValueAllowLists *MetricLabelAllowList
   221  }
   222  
   223  // Modify help description on the metric description.
   224  func (o *TimingHistogramOpts) markDeprecated() {
   225  	o.deprecateOnce.Do(func() {
   226  		o.Help = fmt.Sprintf("(Deprecated since %v) %v", o.DeprecatedVersion, o.Help)
   227  	})
   228  }
   229  
   230  // annotateStabilityLevel annotates help description on the metric description with the stability level
   231  // of the metric
   232  func (o *TimingHistogramOpts) annotateStabilityLevel() {
   233  	o.annotateOnce.Do(func() {
   234  		o.Help = fmt.Sprintf("[%v] %v", o.StabilityLevel, o.Help)
   235  	})
   236  }
   237  
   238  // convenience function to allow easy transformation to the prometheus
   239  // counterpart. This will do more once we have a proper label abstraction
   240  func (o *TimingHistogramOpts) toPromHistogramOpts() promext.TimingHistogramOpts {
   241  	return promext.TimingHistogramOpts{
   242  		Namespace:    o.Namespace,
   243  		Subsystem:    o.Subsystem,
   244  		Name:         o.Name,
   245  		Help:         o.Help,
   246  		ConstLabels:  o.ConstLabels,
   247  		Buckets:      o.Buckets,
   248  		InitialValue: o.InitialValue,
   249  	}
   250  }
   251  
   252  // SummaryOpts bundles the options for creating a Summary metric. It is
   253  // mandatory to set Name to a non-empty string. While all other fields are
   254  // optional and can safely be left at their zero value, it is recommended to set
   255  // a help string and to explicitly set the Objectives field to the desired value
   256  // as the default value will change in the upcoming v0.10 of the library.
   257  type SummaryOpts struct {
   258  	Namespace            string
   259  	Subsystem            string
   260  	Name                 string
   261  	Help                 string
   262  	ConstLabels          map[string]string
   263  	Objectives           map[float64]float64
   264  	MaxAge               time.Duration
   265  	AgeBuckets           uint32
   266  	BufCap               uint32
   267  	DeprecatedVersion    string
   268  	deprecateOnce        sync.Once
   269  	annotateOnce         sync.Once
   270  	StabilityLevel       StabilityLevel
   271  	LabelValueAllowLists *MetricLabelAllowList
   272  }
   273  
   274  // Modify help description on the metric description.
   275  func (o *SummaryOpts) markDeprecated() {
   276  	o.deprecateOnce.Do(func() {
   277  		o.Help = fmt.Sprintf("(Deprecated since %v) %v", o.DeprecatedVersion, o.Help)
   278  	})
   279  }
   280  
   281  // annotateStabilityLevel annotates help description on the metric description with the stability level
   282  // of the metric
   283  func (o *SummaryOpts) annotateStabilityLevel() {
   284  	o.annotateOnce.Do(func() {
   285  		o.Help = fmt.Sprintf("[%v] %v", o.StabilityLevel, o.Help)
   286  	})
   287  }
   288  
   289  // Deprecated: DefObjectives will not be used as the default objectives in
   290  // v1.0.0 of the library. The default Summary will have no quantiles then.
   291  var (
   292  	defObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}
   293  )
   294  
   295  // convenience function to allow easy transformation to the prometheus
   296  // counterpart. This will do more once we have a proper label abstraction
   297  func (o *SummaryOpts) toPromSummaryOpts() prometheus.SummaryOpts {
   298  	// we need to retain existing quantile behavior for backwards compatibility,
   299  	// so let's do what prometheus used to do prior to v1.
   300  	objectives := o.Objectives
   301  	if objectives == nil {
   302  		objectives = defObjectives
   303  	}
   304  	return prometheus.SummaryOpts{
   305  		Namespace:   o.Namespace,
   306  		Subsystem:   o.Subsystem,
   307  		Name:        o.Name,
   308  		Help:        o.Help,
   309  		ConstLabels: o.ConstLabels,
   310  		Objectives:  objectives,
   311  		MaxAge:      o.MaxAge,
   312  		AgeBuckets:  o.AgeBuckets,
   313  		BufCap:      o.BufCap,
   314  	}
   315  }
   316  
   317  type MetricLabelAllowList struct {
   318  	labelToAllowList map[string]sets.String
   319  }
   320  
   321  func (allowList *MetricLabelAllowList) ConstrainToAllowedList(labelNameList, labelValueList []string) {
   322  	for index, value := range labelValueList {
   323  		name := labelNameList[index]
   324  		if allowValues, ok := allowList.labelToAllowList[name]; ok {
   325  			if !allowValues.Has(value) {
   326  				labelValueList[index] = "unexpected"
   327  				cardinalityEnforcementUnexpectedCategorizationsTotal.Inc()
   328  			}
   329  		}
   330  	}
   331  }
   332  
   333  func (allowList *MetricLabelAllowList) ConstrainLabelMap(labels map[string]string) {
   334  	for name, value := range labels {
   335  		if allowValues, ok := allowList.labelToAllowList[name]; ok {
   336  			if !allowValues.Has(value) {
   337  				labels[name] = "unexpected"
   338  				cardinalityEnforcementUnexpectedCategorizationsTotal.Inc()
   339  			}
   340  		}
   341  	}
   342  }
   343  
   344  func SetLabelAllowListFromCLI(allowListMapping map[string]string) {
   345  	allowListLock.Lock()
   346  	defer allowListLock.Unlock()
   347  	for metricLabelName, labelValues := range allowListMapping {
   348  		metricName := strings.Split(metricLabelName, ",")[0]
   349  		labelName := strings.Split(metricLabelName, ",")[1]
   350  		valueSet := sets.NewString(strings.Split(labelValues, ",")...)
   351  
   352  		allowList, ok := labelValueAllowLists[metricName]
   353  		if ok {
   354  			allowList.labelToAllowList[labelName] = valueSet
   355  		} else {
   356  			labelToAllowList := make(map[string]sets.String)
   357  			labelToAllowList[labelName] = valueSet
   358  			labelValueAllowLists[metricName] = &MetricLabelAllowList{
   359  				labelToAllowList,
   360  			}
   361  		}
   362  	}
   363  }
   364  
   365  func SetLabelAllowListFromManifest(manifest string) {
   366  	allowListLock.Lock()
   367  	defer allowListLock.Unlock()
   368  	allowListMapping := make(map[string]string)
   369  	data, err := os.ReadFile(filepath.Clean(manifest))
   370  	if err != nil {
   371  		klog.Errorf("Failed to read allow list manifest: %v", err)
   372  		return
   373  	}
   374  	err = yaml.Unmarshal(data, &allowListMapping)
   375  	if err != nil {
   376  		klog.Errorf("Failed to parse allow list manifest: %v", err)
   377  		return
   378  	}
   379  	SetLabelAllowListFromCLI(allowListMapping)
   380  }
   381  

View as plain text