k8s.io/apiserver@v0.31.1/pkg/util/flowcontrol/dropped_requests_tracker_test.go (about) 1 /* 2 Copyright 2021 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 flowcontrol 18 19 import ( 20 "fmt" 21 "sync" 22 "testing" 23 "time" 24 25 testingclock "k8s.io/utils/clock/testing" 26 ) 27 28 func TestDroppedRequestsTracker(t *testing.T) { 29 fakeClock := testingclock.NewFakeClock(time.Now()) 30 tracker := newDroppedRequestsTracker(fakeClock.Now) 31 32 // The following table represents the list over time of: 33 // - seconds elapsed (as computed since the initial time) 34 // - requests that will be recorded as dropped in a current second 35 steps := []struct { 36 secondsElapsed int 37 // droppedRequests is the number of requests to drop, after 38 // secondsElapsed. 39 droppedRequests int 40 // retryAfter is the expected retryAfter after all dropped 41 // requests are recorded via RecordDroppedRequest. 42 retryAfter int64 43 }{ 44 {secondsElapsed: 0, droppedRequests: 5, retryAfter: 1}, 45 {secondsElapsed: 1, droppedRequests: 11, retryAfter: 2}, 46 // Check that we don't bump immediately after despite 47 // multiple dropped requests. 48 {secondsElapsed: 2, droppedRequests: 1, retryAfter: 2}, 49 {secondsElapsed: 3, droppedRequests: 11, retryAfter: 4}, 50 {secondsElapsed: 4, droppedRequests: 1, retryAfter: 4}, 51 {secondsElapsed: 7, droppedRequests: 1, retryAfter: 8}, 52 {secondsElapsed: 11, droppedRequests: 1, retryAfter: 8}, 53 {secondsElapsed: 15, droppedRequests: 1, retryAfter: 7}, 54 {secondsElapsed: 17, droppedRequests: 1, retryAfter: 6}, 55 {secondsElapsed: 21, droppedRequests: 14, retryAfter: 5}, 56 {secondsElapsed: 22, droppedRequests: 1, retryAfter: 10}, 57 } 58 59 for i, step := range steps { 60 secondsToAdvance := step.secondsElapsed 61 if i > 0 { 62 secondsToAdvance -= steps[i-1].secondsElapsed 63 } 64 fakeClock.Step(time.Duration(secondsToAdvance) * time.Second) 65 66 // Record all droppeded requests and recompute retryAfter. 67 for r := 0; r < step.droppedRequests; r++ { 68 tracker.RecordDroppedRequest("pl") 69 } 70 if retryAfter := tracker.GetRetryAfter("pl"); retryAfter != step.retryAfter { 71 t.Errorf("Unexpected retryAfter: %v, expected: %v", retryAfter, step.retryAfter) 72 } 73 74 } 75 } 76 77 func TestDroppedRequestsTrackerPLIndependent(t *testing.T) { 78 fakeClock := testingclock.NewFakeClock(time.Now()) 79 tracker := newDroppedRequestsTracker(fakeClock.Now) 80 81 // Report single dropped requests in multiple PLs. 82 // Validate if RetryAfter isn't bumped next second. 83 for i := 0; i < 10; i++ { 84 tracker.RecordDroppedRequest(fmt.Sprintf("pl-%d", i)) 85 } 86 fakeClock.Step(time.Second) 87 for i := 0; i < 10; i++ { 88 tracker.RecordDroppedRequest(fmt.Sprintf("pl-%d", i)) 89 retryAfter := tracker.GetRetryAfter(fmt.Sprintf("pl-%d", i)) 90 if retryAfter != 1 { 91 t.Errorf("Unexpected retryAfter for pl-%d: %v", i, retryAfter) 92 } 93 } 94 95 // Record few droped requests on a single PL. 96 // Validate that RetryAfter is bumped only for this PL. 97 for i := 0; i < 5; i++ { 98 tracker.RecordDroppedRequest("pl-0") 99 } 100 fakeClock.Step(time.Second) 101 for i := 0; i < 10; i++ { 102 tracker.RecordDroppedRequest(fmt.Sprintf("pl-%d", i)) 103 retryAfter := tracker.GetRetryAfter(fmt.Sprintf("pl-%d", i)) 104 switch i { 105 case 0: 106 if retryAfter != 2 { 107 t.Errorf("Unexpected retryAfter for pl-0: %v", retryAfter) 108 } 109 default: 110 if retryAfter != 1 { 111 t.Errorf("Unexpected retryAfter for pl-%d: %v", i, retryAfter) 112 } 113 } 114 } 115 // Validate also PL for which no dropped requests was recorded. 116 if retryAfter := tracker.GetRetryAfter("other-pl"); retryAfter != 1 { 117 t.Errorf("Unexpected retryAfter for other-pl: %v", retryAfter) 118 } 119 } 120 121 func BenchmarkDroppedRequestsTracker(b *testing.B) { 122 b.StopTimer() 123 124 fakeClock := testingclock.NewFakeClock(time.Now()) 125 tracker := newDroppedRequestsTracker(fakeClock.Now) 126 127 startCh := make(chan struct{}) 128 wg := sync.WaitGroup{} 129 numPLs := 5 130 // For all `numPLs` priority levels, create b.N workers each 131 // of which will try to record a dropped request every 100ms 132 // with a random jitter. 133 for i := 0; i < numPLs; i++ { 134 plName := fmt.Sprintf("priority-level-%d", i) 135 for i := 0; i < b.N; i++ { 136 wg.Add(1) 137 go func() { 138 defer wg.Done() 139 <-startCh 140 141 for a := 0; a < 5; a++ { 142 tracker.RecordDroppedRequest(plName) 143 time.Sleep(25 * time.Millisecond) 144 } 145 }() 146 } 147 } 148 // Time-advancing goroutine. 149 stopCh := make(chan struct{}) 150 timeWg := sync.WaitGroup{} 151 timeWg.Add(1) 152 go func() { 153 defer timeWg.Done() 154 for { 155 select { 156 case <-stopCh: 157 return 158 case <-time.After(25 * time.Millisecond): 159 fakeClock.Step(time.Second) 160 } 161 } 162 }() 163 164 b.StartTimer() 165 close(startCh) 166 wg.Wait() 167 168 close(stopCh) 169 timeWg.Wait() 170 }