k8s.io/apiserver@v0.31.1/pkg/util/flowcontrol/metrics/timing_ratio_histogram.go (about) 1 /* 2 Copyright 2022 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 "context" 21 "sync" 22 "time" 23 24 compbasemetrics "k8s.io/component-base/metrics" 25 "k8s.io/klog/v2" 26 ) 27 28 // TimingRatioHistogram is essentially a gauge for a ratio where the client 29 // independently controls the numerator and denominator. 30 // When scraped it produces a histogram of samples of the ratio 31 // taken at the end of every nanosecond. 32 // `*TimingRatioHistogram` implements both Registerable and RatioedGauge. 33 type TimingRatioHistogram struct { 34 // The implementation is layered on TimingHistogram, 35 // adding the division by an occasionally adjusted denominator. 36 37 // Registerable is the registerable aspect. 38 // That is the registerable aspect of the underlying TimingHistogram. 39 compbasemetrics.Registerable 40 41 // timingRatioHistogramInner implements the RatioedGauge aspect. 42 timingRatioHistogramInner 43 } 44 45 // TimingRatioHistogramOpts is the constructor parameters of a TimingRatioHistogram. 46 // The `TimingHistogramOpts.InitialValue` is the initial numerator. 47 type TimingRatioHistogramOpts struct { 48 compbasemetrics.TimingHistogramOpts 49 InitialDenominator float64 50 } 51 52 // timingRatioHistogramInner implements the instrumentation aspect 53 type timingRatioHistogramInner struct { 54 nowFunc func() time.Time 55 getGaugeOfRatio func() Gauge 56 sync.Mutex 57 // access only with mutex locked 58 numerator, denominator float64 59 } 60 61 var _ RatioedGauge = &timingRatioHistogramInner{} 62 var _ RatioedGauge = &TimingRatioHistogram{} 63 var _ compbasemetrics.Registerable = &TimingRatioHistogram{} 64 65 // NewTimingHistogram returns an object which is TimingHistogram-like. However, nothing 66 // will be measured until the histogram is registered in at least one registry. 67 func NewTimingRatioHistogram(opts *TimingRatioHistogramOpts) *TimingRatioHistogram { 68 return NewTestableTimingRatioHistogram(time.Now, opts) 69 } 70 71 // NewTestableTimingHistogram adds injection of the clock 72 func NewTestableTimingRatioHistogram(nowFunc func() time.Time, opts *TimingRatioHistogramOpts) *TimingRatioHistogram { 73 //nolint:govet // copylocks: assignment copies lock value to ratioedOpts: k8s.io/component-base/metrics.TimingHistogramOpts contains sync.Once contains sync.Mutex 74 ratioedOpts := opts.TimingHistogramOpts 75 ratioedOpts.InitialValue /= opts.InitialDenominator 76 th := compbasemetrics.NewTestableTimingHistogram(nowFunc, &ratioedOpts) 77 return &TimingRatioHistogram{ 78 Registerable: th, 79 timingRatioHistogramInner: timingRatioHistogramInner{ 80 nowFunc: nowFunc, 81 getGaugeOfRatio: func() Gauge { return th }, 82 numerator: opts.InitialValue, 83 denominator: opts.InitialDenominator, 84 }} 85 } 86 87 func (trh *timingRatioHistogramInner) Set(numerator float64) { 88 trh.Lock() 89 defer trh.Unlock() 90 trh.numerator = numerator 91 ratio := numerator / trh.denominator 92 trh.getGaugeOfRatio().Set(ratio) 93 } 94 95 func (trh *timingRatioHistogramInner) Add(deltaNumerator float64) { 96 trh.Lock() 97 defer trh.Unlock() 98 numerator := trh.numerator + deltaNumerator 99 trh.numerator = numerator 100 ratio := numerator / trh.denominator 101 trh.getGaugeOfRatio().Set(ratio) 102 } 103 104 func (trh *timingRatioHistogramInner) Sub(deltaNumerator float64) { 105 trh.Add(-deltaNumerator) 106 } 107 108 func (trh *timingRatioHistogramInner) Inc() { 109 trh.Add(1) 110 } 111 112 func (trh *timingRatioHistogramInner) Dec() { 113 trh.Add(-1) 114 } 115 116 func (trh *timingRatioHistogramInner) SetToCurrentTime() { 117 trh.Set(float64(trh.nowFunc().Sub(time.Unix(0, 0)))) 118 } 119 120 func (trh *timingRatioHistogramInner) SetDenominator(denominator float64) { 121 trh.Lock() 122 defer trh.Unlock() 123 trh.denominator = denominator 124 ratio := trh.numerator / denominator 125 trh.getGaugeOfRatio().Set(ratio) 126 } 127 128 // WithContext allows the normal TimingHistogram metric to pass in context. 129 // The context is no-op at the current level of development. 130 func (trh *timingRatioHistogramInner) WithContext(ctx context.Context) RatioedGauge { 131 return trh 132 } 133 134 // TimingRatioHistogramVec is a collection of TimingRatioHistograms that differ 135 // only in label values. 136 // `*TimingRatioHistogramVec` implements both Registerable and RatioedGaugeVec. 137 type TimingRatioHistogramVec struct { 138 // promote only the Registerable methods 139 compbasemetrics.Registerable 140 // delegate is TimingHistograms of the ratio 141 delegate compbasemetrics.GaugeVecMetric 142 } 143 144 var _ RatioedGaugeVec = &TimingRatioHistogramVec{} 145 var _ compbasemetrics.Registerable = &TimingRatioHistogramVec{} 146 147 // NewTimingHistogramVec constructs a new vector. 148 // `opts.InitialValue` is the initial ratio, but this applies 149 // only for the tiny period of time until NewForLabelValuesSafe sets 150 // the ratio based on the given initial numerator and denominator. 151 // Thus there is a tiny splinter of time during member construction when 152 // its underlying TimingHistogram is given the initial numerator rather than 153 // the initial ratio (which is obviously a non-issue when both are zero). 154 // Note the difficulties associated with extracting a member 155 // before registering the vector. 156 func NewTimingRatioHistogramVec(opts *compbasemetrics.TimingHistogramOpts, labelNames ...string) *TimingRatioHistogramVec { 157 return NewTestableTimingRatioHistogramVec(time.Now, opts, labelNames...) 158 } 159 160 // NewTestableTimingHistogramVec adds injection of the clock. 161 func NewTestableTimingRatioHistogramVec(nowFunc func() time.Time, opts *compbasemetrics.TimingHistogramOpts, labelNames ...string) *TimingRatioHistogramVec { 162 delegate := compbasemetrics.NewTestableTimingHistogramVec(nowFunc, opts, labelNames) 163 return &TimingRatioHistogramVec{ 164 Registerable: delegate, 165 delegate: delegate, 166 } 167 } 168 169 func (v *TimingRatioHistogramVec) metrics() Registerables { 170 return Registerables{v} 171 } 172 173 // NewForLabelValuesChecked will return an error if this vec is not hidden and not yet registered 174 // or there is a syntactic problem with the labelValues. 175 func (v *TimingRatioHistogramVec) NewForLabelValuesChecked(initialNumerator, initialDenominator float64, labelValues []string) (RatioedGauge, error) { 176 underMember, err := v.delegate.WithLabelValuesChecked(labelValues...) 177 if err != nil { 178 return noopRatioed{}, err 179 } 180 underMember.Set(initialNumerator / initialDenominator) 181 return &timingRatioHistogramInner{ 182 getGaugeOfRatio: func() Gauge { return underMember }, 183 numerator: initialNumerator, 184 denominator: initialDenominator, 185 }, nil 186 } 187 188 // NewForLabelValuesSafe is the same as NewForLabelValuesChecked in cases where that does not 189 // return an error. When the unsafe version returns an error due to the vector not being 190 // registered yet, the safe version returns an object that implements its methods 191 // by looking up the relevant vector member in each call (thus getting a non-noop after registration). 192 // In the other error cases the object returned here is a noop. 193 func (v *TimingRatioHistogramVec) NewForLabelValuesSafe(initialNumerator, initialDenominator float64, labelValues []string) RatioedGauge { 194 tro, err := v.NewForLabelValuesChecked(initialNumerator, initialDenominator, labelValues) 195 if err == nil { 196 klog.V(3).InfoS("TimingRatioHistogramVec.NewForLabelValuesSafe hit the efficient case", "fqName", v.FQName(), "labelValues", labelValues) 197 return tro 198 } 199 if !compbasemetrics.ErrIsNotRegistered(err) { 200 klog.ErrorS(err, "Failed to extract TimingRatioHistogramVec member, using noop instead", "vectorname", v.FQName(), "labelValues", labelValues) 201 return tro 202 } 203 klog.V(3).InfoS("TimingRatioHistogramVec.NewForLabelValuesSafe hit the inefficient case", "fqName", v.FQName(), "labelValues", labelValues) 204 // At this point we know v.NewForLabelValuesChecked(..) returns a permanent noop, 205 // which we precisely want to avoid using. Instead, make our own gauge that 206 // fetches the element on every Set. 207 return &timingRatioHistogramInner{ 208 getGaugeOfRatio: func() Gauge { return v.delegate.WithLabelValues(labelValues...) }, 209 numerator: initialNumerator, 210 denominator: initialDenominator, 211 } 212 } 213 214 type noopRatioed struct{} 215 216 func (noopRatioed) Set(float64) {} 217 func (noopRatioed) Add(float64) {} 218 func (noopRatioed) Sub(float64) {} 219 func (noopRatioed) Inc() {} 220 func (noopRatioed) Dec() {} 221 func (noopRatioed) SetToCurrentTime() {} 222 func (noopRatioed) SetDenominator(float64) {} 223 224 func (v *TimingRatioHistogramVec) Reset() { 225 v.delegate.Reset() 226 }