/* Copyright 2019 The Kubernetes 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 metrics import ( "github.com/blang/semver" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "k8s.io/klog" "sync" ) /** * This extends the prometheus.Collector interface so that we can customize the metric * registration process. Specifically, we defer metric initialization until ActuallyCreate * is called, which then delegates to the underlying metric's initializeMetric or * initializeDeprecatedMetric method call depending on whether the metric is deprecated or not. */ type KubeCollector interface { Collector LazyMetric GetDeprecatedVersion() *semver.Version // Each collector metric should provide an initialization function // for both deprecated and non-deprecated variants of a metric. This // is necessary since we are now deferring metric instantiation // until the metric is actually registered somewhere. initializeMetric() initializeDeprecatedMetric() } // LazyMetric defines our registration functionality. We expect LazyMetric // objects to lazily instantiate metrics (i.e defer metric instantiation until when // ActuallyCreate is explicitly called). type LazyMetric interface { ActuallyCreate(*semver.Version) bool IsCreated() bool IsHidden() bool IsDeprecated() bool } /* * lazyMetric implements LazyMetric. A lazy metric is lazy because it waits until metric * registration time before instantiation. Add it as an anonymous field to a struct that * implements KubeCollector to get deferred registration behavior. You must call lazyInit * with the KubeCollector itself as an argument. */ type lazyMetric struct { isDeprecated bool isHidden bool isCreated bool markDeprecationOnce sync.Once createOnce sync.Once self KubeCollector } func (r *lazyMetric) IsCreated() bool { return r.isCreated } // lazyInit provides the lazyMetric with a reference to the KubeCollector it is supposed // to allow lazy initialization for. It should be invoked in the factory function which creates new // KubeCollector type objects. func (r *lazyMetric) lazyInit(self KubeCollector) { r.self = self } // determineDeprecationStatus figures out whether our lazy metric should be deprecated or not. It takes // a Version argument which should be the version of the binary in which this code is currently being // executed. func (r *lazyMetric) determineDeprecationStatus(version semver.Version) { selfVersion := r.self.GetDeprecatedVersion() if selfVersion == nil { return } r.markDeprecationOnce.Do(func() { if selfVersion.LTE(version) { r.isDeprecated = true } if selfVersion.LT(version) { klog.Warningf("This metric has been deprecated for more than one release, hiding.") r.isHidden = true } }) } func (r *lazyMetric) IsHidden() bool { return r.isHidden } func (r *lazyMetric) IsDeprecated() bool { return r.isDeprecated } // Defer initialization of metric until we know if we actually need to // register the thing. This wrapper just allows us to consolidate the // syncOnce logic in a single spot and toggle the flag, since this // behavior will be consistent across metrics. // // This no-opts and returns true if metric is already created. func (r *lazyMetric) ActuallyCreate(version *semver.Version) bool { if version != nil { r.determineDeprecationStatus(*version) } // let's not create if this metric is slated to be hidden if r.IsHidden() { return false } r.createOnce.Do(func() { r.isCreated = true if r.IsDeprecated() { r.self.initializeDeprecatedMetric() } else { r.self.initializeMetric() } }) return r.IsCreated() } /** * This code is directly lifted from the prometheus codebase. It's a convenience struct which * allows you satisfy the Collector interface automatically if you already satisfy the Metric interface. * * For reference: https://github.com/prometheus/client_golang/blob/65d3a96fbaa7c8c9535d7133d6d98cd50eed4db8/prometheus/collector.go#L98-L120 */ type selfCollector struct { metric prometheus.Metric } func (c *selfCollector) initSelfCollection(m prometheus.Metric) { c.metric = m } func (c *selfCollector) Describe(ch chan<- *prometheus.Desc) { ch <- c.metric.Desc() } func (c *selfCollector) Collect(ch chan<- prometheus.Metric) { ch <- c.metric } // no-op vecs for convenience var noopCounterVec = &prometheus.CounterVec{} var noopHistogramVec = &prometheus.HistogramVec{} var noopSummaryVec = &prometheus.SummaryVec{} var noopGaugeVec = &prometheus.GaugeVec{} var noopObserverVec = &noopObserverVector{} // just use a convenience struct for all the no-ops var noop = &noopMetric{} type noopMetric struct{} func (noopMetric) Inc() {} func (noopMetric) Add(float64) {} func (noopMetric) Dec() {} func (noopMetric) Set(float64) {} func (noopMetric) Sub(float64) {} func (noopMetric) Observe(float64) {} func (noopMetric) SetToCurrentTime() {} func (noopMetric) Desc() *prometheus.Desc { return nil } func (noopMetric) Write(*dto.Metric) error { return nil } func (noopMetric) Describe(chan<- *prometheus.Desc) {} func (noopMetric) Collect(chan<- prometheus.Metric) {} type noopObserverVector struct{} func (noopObserverVector) GetMetricWith(prometheus.Labels) (prometheus.Observer, error) { return noop, nil } func (noopObserverVector) GetMetricWithLabelValues(...string) (prometheus.Observer, error) { return noop, nil } func (noopObserverVector) With(prometheus.Labels) prometheus.Observer { return noop } func (noopObserverVector) WithLabelValues(...string) prometheus.Observer { return noop } func (noopObserverVector) CurryWith(prometheus.Labels) (prometheus.ObserverVec, error) { return noopObserverVec, nil } func (noopObserverVector) MustCurryWith(prometheus.Labels) prometheus.ObserverVec { return noopObserverVec } func (noopObserverVector) Describe(chan<- *prometheus.Desc) {} func (noopObserverVector) Collect(chan<- prometheus.Metric) {}