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  }