github.com/uber/kraken@v0.1.4/utils/bandwidth/limiter_test.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package bandwidth 15 16 import ( 17 "sync" 18 "testing" 19 "time" 20 21 "github.com/stretchr/testify/require" 22 ) 23 24 const ( 25 egress = "egress" 26 ingress = "ingress" 27 ) 28 29 func reserve(l *Limiter, nbytes int64, direction string) error { 30 if direction == egress { 31 return l.ReserveEgress(nbytes) 32 } 33 return l.ReserveIngress(nbytes) 34 } 35 36 func TestLimiterInvalidConfig(t *testing.T) { 37 require := require.New(t) 38 39 bps := uint64(800) // 100 bytes. 40 41 _, err := NewLimiter(Config{ 42 EgressBitsPerSec: 0, 43 IngressBitsPerSec: bps, 44 TokenSize: 1, 45 Enable: true, 46 }) 47 require.Error(err) 48 49 _, err = NewLimiter(Config{ 50 EgressBitsPerSec: bps, 51 IngressBitsPerSec: 0, 52 TokenSize: 1, 53 Enable: true, 54 }) 55 require.Error(err) 56 } 57 58 func TestLimiterDisabled(t *testing.T) { 59 require := require.New(t) 60 61 bps := uint64(800) // 100 bytes. 62 63 l, err := NewLimiter(Config{ 64 EgressBitsPerSec: bps, 65 IngressBitsPerSec: bps, 66 TokenSize: 1, 67 Enable: false, 68 }) 69 require.NoError(err) 70 require.Nil(l.egress) 71 require.Nil(l.ingress) 72 require.NoError(reserve(l, 1, egress)) 73 require.NoError(reserve(l, 1, ingress)) 74 } 75 76 func TestLimiterReserveConcurrency(t *testing.T) { 77 t.Parallel() 78 79 for _, direction := range []string{egress, ingress} { 80 t.Run(direction, func(t *testing.T) { 81 require := require.New(t) 82 83 bps := uint64(800) // 100 bytes. 84 85 l, err := NewLimiter(Config{ 86 EgressBitsPerSec: bps, 87 IngressBitsPerSec: bps, 88 TokenSize: 1, 89 Enable: true, 90 }) 91 require.NoError(err) 92 93 // This test starts a bunch of goroutines and see how many bytes 94 // they can reserve in nsecs. 95 nsecs := 4 96 97 stop := make(chan struct{}) 98 go func() { 99 <-time.After(time.Duration(nsecs) * time.Second) 100 close(stop) 101 }() 102 103 var mu sync.Mutex 104 var nbytes int 105 106 var wg sync.WaitGroup 107 for i := 0; i < 10; i++ { 108 wg.Add(1) 109 go func() { 110 defer wg.Done() 111 for { 112 require.NoError(reserve(l, 1, direction)) 113 select { 114 case <-stop: 115 return 116 default: 117 mu.Lock() 118 nbytes++ 119 mu.Unlock() 120 } 121 } 122 }() 123 } 124 wg.Wait() 125 126 // The bucket is initially full, hence nsecs + 1. 127 require.InDelta(bps*uint64(nsecs+1), 8*nbytes, 10.0) 128 }) 129 } 130 } 131 132 func TestLimiterReserveBytesTokenScaling(t *testing.T) { 133 t.Parallel() 134 135 for _, direction := range []string{egress, ingress} { 136 t.Run(direction, func(t *testing.T) { 137 require := require.New(t) 138 139 bps := uint64(80) // 10 bytes. 140 141 l, err := NewLimiter(Config{ 142 EgressBitsPerSec: bps, 143 IngressBitsPerSec: bps, 144 TokenSize: 10, // Bucket has 8 tokens. 145 Enable: true, 146 }) 147 require.NoError(err) 148 149 start := time.Now() 150 // Reserving two buckets full of tokens should take exactly one second. 151 for i := 0; i < 4; i++ { 152 // 6 bytes -> 48 bits, which is should be equal to 4 tokens. 153 require.NoError(reserve(l, 6, direction)) 154 } 155 require.InDelta(time.Second, time.Since(start), float64(50*time.Millisecond)) 156 }) 157 } 158 } 159 160 func TestLimiterReserveBytesSmallerThanTokenSize(t *testing.T) { 161 t.Parallel() 162 163 for _, direction := range []string{egress, ingress} { 164 t.Run(direction, func(t *testing.T) { 165 require := require.New(t) 166 167 bps := uint64(80) // 10 bytes. 168 169 l, err := NewLimiter(Config{ 170 EgressBitsPerSec: bps, 171 IngressBitsPerSec: bps, 172 TokenSize: 10, // Bucket has 8 tokens. 173 Enable: true, 174 }) 175 require.NoError(err) 176 177 start := time.Now() 178 // Reserving two buckets full of tokens should take exactly one second. 179 for i := 0; i < 16; i++ { 180 // 1 byte -> 8 bits, which is smaller than our token size. Should 181 // be considered to be a single token. 182 require.NoError(reserve(l, 1, direction)) 183 } 184 require.InDelta(time.Second, time.Since(start), float64(50*time.Millisecond)) 185 }) 186 } 187 } 188 189 func TestLimiterReserveErrorWhenBytesLargerThanBucket(t *testing.T) { 190 t.Parallel() 191 192 for _, direction := range []string{egress, ingress} { 193 t.Run(direction, func(t *testing.T) { 194 require := require.New(t) 195 196 bps := uint64(80) // 10 bytes. 197 198 l, err := NewLimiter(Config{ 199 EgressBitsPerSec: bps, 200 IngressBitsPerSec: bps, 201 TokenSize: 10, // Bucket has 8 tokens. 202 Enable: true, 203 }) 204 require.NoError(err) 205 206 require.Error(reserve(l, 12, direction)) 207 }) 208 } 209 } 210 211 func TestLimiterAdjustError(t *testing.T) { 212 require := require.New(t) 213 214 l, err := NewLimiter(Config{ 215 EgressBitsPerSec: 50, 216 IngressBitsPerSec: 10, 217 TokenSize: 1, 218 Enable: true, 219 }) 220 require.NoError(err) 221 require.Error(l.Adjust(0)) 222 } 223 224 func TestLimiterAdjust(t *testing.T) { 225 require := require.New(t) 226 227 l, err := NewLimiter(Config{ 228 EgressBitsPerSec: 50, 229 IngressBitsPerSec: 10, 230 TokenSize: 1, 231 Enable: true, 232 }) 233 require.NoError(err) 234 235 // No subtests since we want to ensure the calls don't affect each other. 236 cases := []struct { 237 denom int 238 egress int64 239 ingress int64 240 }{ 241 {10, 5, 1}, 242 {5, 10, 2}, 243 {100, 1, 1}, 244 } 245 for _, c := range cases { 246 require.NoError(l.Adjust(c.denom)) 247 require.Equal(c.egress, l.EgressLimit()) 248 require.Equal(c.ingress, l.IngressLimit()) 249 } 250 }