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  }