knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/test/vegeta/pacers/steady_up_pacer.go (about) 1 /* 2 Copyright 2019 The Knative 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 pacers 18 19 import ( 20 "errors" 21 "fmt" 22 "math" 23 "time" 24 25 vegeta "github.com/tsenart/vegeta/v12/lib" 26 ) 27 28 // steadyUpPacer is a Pacer that describes attack request rates that increases in the beginning then becomes steady. 29 // 30 // Max | ,---------------- 31 // | / 32 // | / 33 // | / 34 // | / 35 // Min -+------------------------------> t 36 // |<-Up->| 37 type steadyUpPacer struct { 38 // upDuration is the duration that attack request rates increase from Min to Max. 39 // MUST be larger than 0. 40 upDuration time.Duration 41 // min is the attack request rates from the beginning. 42 // MUST be larger than 0. 43 min vegeta.Rate 44 // max is the maximum and final steady attack request rates. 45 // MUST be larger than Min. 46 max vegeta.Rate 47 48 slope float64 49 minHitsPerNs float64 50 maxHitsPerNs float64 51 } 52 53 // NewSteadyUp returns a new SteadyUpPacer with the given config. 54 func NewSteadyUp(min, max vegeta.Rate, upDuration time.Duration) (vegeta.Pacer, error) { 55 if upDuration <= 0 || min.Freq <= 0 || min.Per <= 0 || max.Freq <= 0 || max.Per <= 0 { 56 return nil, errors.New("configuration for this SteadyUpPacer is invalid") 57 } 58 minHitsPerNs := hitsPerNs(min) 59 maxHitsPerNs := hitsPerNs(max) 60 if minHitsPerNs >= maxHitsPerNs { 61 return nil, errors.New("min rate must be smaller than max rate for SteadyUpPacer") 62 } 63 64 pacer := &steadyUpPacer{ 65 min: min, 66 max: max, 67 upDuration: upDuration, 68 slope: (maxHitsPerNs - minHitsPerNs) / float64(upDuration), 69 minHitsPerNs: minHitsPerNs, 70 maxHitsPerNs: maxHitsPerNs, 71 } 72 return pacer, nil 73 } 74 75 // steadyUpPacer satisfies the Pacer interface. 76 var _ vegeta.Pacer = &steadyUpPacer{} 77 78 // String returns a pretty-printed description of the steadyUpPacer's behaviour. 79 func (sup *steadyUpPacer) String() string { 80 return fmt.Sprintf("Up{%s + %s / %s}, then Steady{%s}", sup.min, sup.max, sup.upDuration, sup.max) 81 } 82 83 // Pace determines the length of time to sleep until the next hit is sent. 84 func (sup *steadyUpPacer) Pace(elapsedTime time.Duration, elapsedHits uint64) (time.Duration, bool) { 85 expectedHits := sup.hits(elapsedTime) 86 if elapsedHits < uint64(expectedHits) { 87 // Running behind, send next hit immediately. 88 return 0, false 89 } 90 91 // Re-arranging our hits equation to provide a duration given the number of 92 // requests sent is non-trivial, so we must solve for the duration numerically. 93 nsPerHit := 1 / sup.hitsPerNs(elapsedTime) 94 hitsToWait := float64(elapsedHits+1) - expectedHits 95 nextHitIn := time.Duration(nsPerHit * hitsToWait) 96 97 // If we can't converge to an error of <1e-3 within 10 iterations, bail. 98 // This rarely even loops for any large Period if hitsToWait is small. 99 for range 10 { 100 hitsAtGuess := sup.hits(elapsedTime + nextHitIn) 101 err := float64(elapsedHits+1) - hitsAtGuess 102 if math.Abs(err) < 1e-3 { 103 return nextHitIn, false 104 } 105 nextHitIn = time.Duration(float64(nextHitIn) / (hitsAtGuess - float64(elapsedHits))) 106 } 107 108 return nextHitIn, false 109 } 110 111 // Rate returns a Pacer's instantaneous hit rate (per seconds) at the given elapsed 112 // duration of an attack. 113 func (sup *steadyUpPacer) Rate(elapsedTime time.Duration) float64 { 114 return sup.hitsPerNs(elapsedTime) * 1e9 115 } 116 117 // hits returns the number of expected hits for this pacer during the given time. 118 func (sup *steadyUpPacer) hits(t time.Duration) float64 { 119 // If t is smaller than the upDuration, calculate the hits as a trapezoid. 120 if t <= sup.upDuration { 121 curtHitsPerNs := sup.hitsPerNs(t) 122 return (curtHitsPerNs + sup.minHitsPerNs) / 2.0 * float64(t) 123 } 124 125 // If t is larger than the upDuration, calculate the hits as a trapezoid + a rectangle. 126 upHits := (sup.maxHitsPerNs + sup.minHitsPerNs) / 2.0 * float64(sup.upDuration) 127 steadyHits := sup.maxHitsPerNs * float64(t-sup.upDuration) 128 return upHits + steadyHits 129 } 130 131 // hitsPerNs returns the attack rate for this pacer at a given time. 132 func (sup *steadyUpPacer) hitsPerNs(t time.Duration) float64 { 133 if t <= sup.upDuration { 134 return sup.minHitsPerNs + float64(t)*sup.slope 135 } 136 137 return sup.maxHitsPerNs 138 } 139 140 // hitsPerNs returns the attack rate this ConstantPacer represents, in 141 // fractional hits per nanosecond. 142 func hitsPerNs(cp vegeta.ConstantPacer) float64 { 143 return float64(cp.Freq) / float64(cp.Per) 144 }