k8s.io/apiserver@v0.31.1/pkg/util/flowcontrol/metrics/timing_ratio_histogram_test.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 "errors" 21 "fmt" 22 "math/rand" 23 "testing" 24 "time" 25 26 compbasemetrics "k8s.io/component-base/metrics" 27 "k8s.io/klog/v2" 28 testclock "k8s.io/utils/clock/testing" 29 ) 30 31 const ( 32 ddtRangeCentiPeriods = 300 33 ddtOffsetCentiPeriods = 50 34 numIterations = 100 35 ) 36 37 var errMetricNotFound = errors.New("not found") 38 39 func TestTimingRatioHistogramVecElementSimple(t *testing.T) { 40 testHistogramName := "vec_element_simple" 41 t0 := time.Now() 42 clk := testclock.NewFakePassiveClock(t0) 43 buckets := []float64{0, 1} 44 vec := NewTestableTimingRatioHistogramVec(clk.Now, 45 &compbasemetrics.TimingHistogramOpts{Name: testHistogramName, Buckets: buckets}, 46 "alabel", 47 ) 48 toRegister := vec.metrics() 49 registry := compbasemetrics.NewKubeRegistry() 50 for _, reg := range toRegister { 51 registry.MustRegister(reg) 52 } 53 tro, err := vec.NewForLabelValuesChecked(0, 1, []string{"avalue"}) 54 if err != nil { 55 t.Error(err) 56 } 57 exerciseTimingRatioHistogram(t, testHistogramName, t0, clk, registry, tro) 58 } 59 60 func TestTimingRatioHistogramVecElementSafeEarly(t *testing.T) { 61 testHistogramName := "vec_element_safe_early" 62 t0 := time.Now() 63 clk := testclock.NewFakePassiveClock(t0) 64 buckets := []float64{0, 1} 65 vec := NewTestableTimingRatioHistogramVec(clk.Now, 66 &compbasemetrics.TimingHistogramOpts{Name: testHistogramName, Buckets: buckets}, 67 "alabel", 68 ) 69 tro := vec.NewForLabelValuesSafe(0, 1, []string{"avalue"}) 70 toRegister := vec.metrics() 71 registry := compbasemetrics.NewKubeRegistry() 72 for _, reg := range toRegister { 73 registry.MustRegister(reg) 74 } 75 exerciseTimingRatioHistogram(t, testHistogramName, t0, clk, registry, tro) 76 } 77 78 func TestTimingRatioHistogramVecElementSafeLate(t *testing.T) { 79 testHistogramName := "vec_element_safe_late" 80 t0 := time.Now() 81 clk := testclock.NewFakePassiveClock(t0) 82 buckets := []float64{0, 1} 83 vec := NewTestableTimingRatioHistogramVec(clk.Now, 84 &compbasemetrics.TimingHistogramOpts{Name: testHistogramName, Buckets: buckets}, 85 "alabel", 86 ) 87 toRegister := vec.metrics() 88 registry := compbasemetrics.NewKubeRegistry() 89 for _, reg := range toRegister { 90 registry.MustRegister(reg) 91 } 92 tro := vec.NewForLabelValuesSafe(0, 1, []string{"avalue"}) 93 exerciseTimingRatioHistogram(t, testHistogramName, t0, clk, registry, tro) 94 } 95 96 // exerciseTimingRatioHistogram does a rough behavioral test of a 97 // RatioedObserver. A fake clock is used, and the exercise consists 98 // of repeatedly changing that fake clock by an amount of time chosen 99 // uniformly at random from a range that goes from a little negative 100 // to somewhat more than two milliseconds. The negative changes are 101 // included because small negative changes have been observed in real 102 // monotonic clock readings (see issue #96459) and we want to test 103 // that they are properly tolerated. The designed toleration is to 104 // pretend that the clock did not change, until it resumes net forward 105 // progress. The exercise checks that the count in the observer is 106 // correct at each step. The observer is expected to get one 107 // observation at the end of each nanosecond. 108 func exerciseTimingRatioHistogram(t *testing.T, histogramName string, t0 time.Time, clk *testclock.FakePassiveClock, registry compbasemetrics.KubeRegistry, tro RatioedGauge) { 109 samplingPeriod := time.Nanosecond 110 steppingPeriod := time.Millisecond 111 tro.Set(1) 112 // `dt` is the admitted cumulative difference in fake time 113 // since the start of the test. "admitted" means this is 114 // never allowed to decrease, which matches the designed 115 // toleration for negative monotonic clock changes. 116 var dt time.Duration 117 // `t1` is the current fake time 118 t1 := t0.Add(dt) 119 klog.Infof("Expect about %v warnings about time going backwards; this is fake time deliberately misbehaving.", (numIterations*ddtOffsetCentiPeriods)/ddtRangeCentiPeriods) 120 t.Logf("t0=%s", t0) 121 for i := 0; i < numIterations; i++ { 122 // `ddt` is the next step to take in fake time 123 ddt := time.Duration(rand.Intn(ddtRangeCentiPeriods)-ddtOffsetCentiPeriods) * steppingPeriod / 100 124 t1 = t1.Add(ddt) 125 diff := t1.Sub(t0) 126 if diff > dt { 127 dt = diff 128 } 129 clk.SetTime(t1) 130 tro.Set(1) 131 expectedCount := int64(dt / samplingPeriod) 132 actualCount, err := getHistogramCount(registry, histogramName) 133 if err != nil && !(err == errMetricNotFound && expectedCount == 0) { 134 t.Fatalf("For t0=%s, t1=%s, failed to getHistogramCount: %#+v", t0, t1, err) 135 } 136 t.Logf("For i=%d, ddt=%s, t1=%s, diff=%s, dt=%s, count=%d", i, ddt, t1, diff, dt, actualCount) 137 if expectedCount != actualCount { 138 t.Errorf("For i=%d, t0=%s, ddt=%s, t1=%s, expectedCount=%d, actualCount=%d", i, t0, ddt, t1, expectedCount, actualCount) 139 } 140 } 141 } 142 143 /* getHistogramCount returns the count of the named histogram or an error (if any) */ 144 func getHistogramCount(registry compbasemetrics.KubeRegistry, metricName string) (int64, error) { 145 mfs, err := registry.Gather() 146 if err != nil { 147 return 0, fmt.Errorf("failed to gather metrics: %w", err) 148 } 149 for _, mf := range mfs { 150 thisName := mf.GetName() 151 if thisName != metricName { 152 continue 153 } 154 metric := mf.GetMetric()[0] 155 hist := metric.GetHistogram() 156 if hist == nil { 157 return 0, errors.New("dto.Metric has nil Histogram") 158 } 159 if hist.SampleCount == nil { 160 return 0, errors.New("dto.Histogram has nil SampleCount") 161 } 162 return int64(*hist.SampleCount), nil 163 } 164 return 0, errMetricNotFound 165 } 166 167 func BenchmarkTimingRatioHistogram(b *testing.B) { 168 b.StopTimer() 169 now := time.Now() 170 clk := testclock.NewFakePassiveClock(now) 171 th := NewTestableTimingRatioHistogram(clk.Now, 172 &TimingRatioHistogramOpts{ 173 compbasemetrics.TimingHistogramOpts{ 174 Namespace: "testns", 175 Subsystem: "testsubsys", 176 Name: "testhist", 177 Help: "Me", 178 Buckets: []float64{1, 2, 4, 8, 16, 32}, 179 }, 180 1}) 181 registry := compbasemetrics.NewKubeRegistry() 182 registry.MustRegister(th) 183 var x int 184 b.StartTimer() 185 for i := 0; i < b.N; i++ { 186 delta := (i % 6) + 1 187 now = now.Add(time.Duration(delta) * time.Millisecond) 188 clk.SetTime(now) 189 th.Set(float64(x)) 190 x = (x + i) % 60 191 } 192 } 193 194 func BenchmarkTimingRatioHistogramVecElementSimple(b *testing.B) { 195 b.StopTimer() 196 now := time.Now() 197 clk := testclock.NewFakePassiveClock(now) 198 thv := NewTestableTimingRatioHistogramVec(clk.Now, 199 &compbasemetrics.TimingHistogramOpts{ 200 Namespace: "testns", 201 Subsystem: "testsubsys", 202 Name: "testhist", 203 Help: "Me", 204 Buckets: []float64{1, 2, 4, 8, 16, 32}, 205 }, 206 "labelname") 207 registry := compbasemetrics.NewKubeRegistry() 208 registry.MustRegister(thv.metrics()...) 209 th, err := thv.NewForLabelValuesChecked(0, 3, []string{"labelvalue"}) 210 if err != nil { 211 b.Error(err) 212 } 213 var x int 214 b.StartTimer() 215 for i := 0; i < b.N; i++ { 216 delta := (i % 6) + 1 217 now = now.Add(time.Duration(delta) * time.Millisecond) 218 clk.SetTime(now) 219 th.Set(float64(x)) 220 x = (x + i) % 60 221 } 222 } 223 224 func BenchmarkTimingRatioHistogramVecElementSafeEarly(b *testing.B) { 225 b.StopTimer() 226 now := time.Now() 227 clk := testclock.NewFakePassiveClock(now) 228 thv := NewTestableTimingRatioHistogramVec(clk.Now, 229 &compbasemetrics.TimingHistogramOpts{ 230 Namespace: "testns", 231 Subsystem: "testsubsys", 232 Name: "testhist", 233 Help: "Me", 234 Buckets: []float64{1, 2, 4, 8, 16, 32}, 235 }, 236 "labelname") 237 th := thv.NewForLabelValuesSafe(0, 3, []string{"labelvalue"}) 238 registry := compbasemetrics.NewKubeRegistry() 239 registry.MustRegister(thv.metrics()...) 240 var x int 241 b.StartTimer() 242 for i := 0; i < b.N; i++ { 243 delta := (i % 6) + 1 244 now = now.Add(time.Duration(delta) * time.Millisecond) 245 clk.SetTime(now) 246 th.Set(float64(x)) 247 x = (x + i) % 60 248 } 249 } 250 251 func BenchmarkTimingRatioHistogramVecElementSafeLate(b *testing.B) { 252 b.StopTimer() 253 now := time.Now() 254 clk := testclock.NewFakePassiveClock(now) 255 thv := NewTestableTimingRatioHistogramVec(clk.Now, 256 &compbasemetrics.TimingHistogramOpts{ 257 Namespace: "testns", 258 Subsystem: "testsubsys", 259 Name: "testhist", 260 Help: "Me", 261 Buckets: []float64{1, 2, 4, 8, 16, 32}, 262 }, 263 "labelname") 264 registry := compbasemetrics.NewKubeRegistry() 265 registry.MustRegister(thv.metrics()...) 266 th := thv.NewForLabelValuesSafe(0, 3, []string{"labelvalue"}) 267 var x int 268 b.StartTimer() 269 for i := 0; i < b.N; i++ { 270 delta := (i % 6) + 1 271 now = now.Add(time.Duration(delta) * time.Millisecond) 272 clk.SetTime(now) 273 th.Set(float64(x)) 274 x = (x + i) % 60 275 } 276 }