gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/grpc/balancer/rls/internal/adaptive/adaptive_test.go (about)

     1  /*
     2   *
     3   * Copyright 2020 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  package adaptive
    20  
    21  import (
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  )
    26  
    27  // stats returns a tuple with accepts, throttles for the current time.
    28  func (th *Throttler) stats() (int64, int64) {
    29  	now := timeNowFunc()
    30  
    31  	th.mu.Lock()
    32  	a, t := th.accepts.sum(now), th.throttles.sum(now)
    33  	th.mu.Unlock()
    34  	return a, t
    35  }
    36  
    37  // Enums for responses.
    38  const (
    39  	E = iota // No response
    40  	A        // Accepted
    41  	T        // Throttled
    42  )
    43  
    44  func TestRegisterBackendResponse(t *testing.T) {
    45  	testcases := []struct {
    46  		desc          string
    47  		bins          int64
    48  		ticks         []int64
    49  		responses     []int64
    50  		wantAccepts   []int64
    51  		wantThrottled []int64
    52  	}{
    53  		{
    54  			"Accumulate",
    55  			3,
    56  			[]int64{0, 1, 2}, // Ticks
    57  			[]int64{A, T, E}, // Responses
    58  			[]int64{1, 1, 1}, // Accepts
    59  			[]int64{0, 1, 1}, // Throttled
    60  		},
    61  		{
    62  			"LightTimeTravel",
    63  			3,
    64  			[]int64{1, 0, 2}, // Ticks
    65  			[]int64{A, T, E}, // Response
    66  			[]int64{1, 1, 1}, // Accepts
    67  			[]int64{0, 1, 1}, // Throttled
    68  		},
    69  		{
    70  			"HeavyTimeTravel",
    71  			3,
    72  			[]int64{8, 0, 9}, // Ticks
    73  			[]int64{A, A, A}, // Response
    74  			[]int64{1, 1, 2}, // Accepts
    75  			[]int64{0, 0, 0}, // Throttled
    76  		},
    77  		{
    78  			"Rollover",
    79  			1,
    80  			[]int64{0, 1, 2}, // Ticks
    81  			[]int64{A, T, E}, // Responses
    82  			[]int64{1, 0, 0}, // Accepts
    83  			[]int64{0, 1, 0}, // Throttled
    84  		},
    85  	}
    86  
    87  	m := mockClock{}
    88  	oldTimeNowFunc := timeNowFunc
    89  	timeNowFunc = m.Now
    90  	defer func() { timeNowFunc = oldTimeNowFunc }()
    91  
    92  	for _, test := range testcases {
    93  		t.Run(test.desc, func(t *testing.T) {
    94  			th := newWithArgs(time.Duration(test.bins), test.bins, 2.0, 8)
    95  			for i, tick := range test.ticks {
    96  				m.SetNanos(tick)
    97  
    98  				if test.responses[i] != E {
    99  					th.RegisterBackendResponse(test.responses[i] == T)
   100  				}
   101  
   102  				if gotAccepts, gotThrottled := th.stats(); gotAccepts != test.wantAccepts[i] || gotThrottled != test.wantThrottled[i] {
   103  					t.Errorf("th.stats() = {%d, %d} for index %d, want {%d, %d}", i, gotAccepts, gotThrottled, test.wantAccepts[i], test.wantThrottled[i])
   104  				}
   105  			}
   106  		})
   107  	}
   108  }
   109  
   110  func TestShouldThrottleOptions(t *testing.T) {
   111  	// ShouldThrottle should return true iff
   112  	//    (requests - RatioForAccepts * accepts) / (requests + RequestsPadding) <= p
   113  	// where p is a random number. For the purposes of this test it's fixed
   114  	// to 0.5.
   115  	responses := []int64{T, T, T, T, T, T, T, T, T, A, A, A, A, A, A, T, T, T, T}
   116  
   117  	n := false
   118  	y := true
   119  
   120  	testcases := []struct {
   121  		desc            string
   122  		ratioForAccepts float64
   123  		requestsPadding float64
   124  		want            []bool
   125  	}{
   126  		{
   127  			"Baseline",
   128  			1.1,
   129  			8,
   130  			[]bool{n, n, n, n, n, n, n, n, y, y, y, y, y, n, n, n, y, y, y},
   131  		},
   132  		{
   133  			"ChangePadding",
   134  			1.1,
   135  			7,
   136  			[]bool{n, n, n, n, n, n, n, y, y, y, y, y, y, y, y, y, y, y, y},
   137  		},
   138  		{
   139  			"ChangeRatioForAccepts",
   140  			1.4,
   141  			8,
   142  			[]bool{n, n, n, n, n, n, n, n, y, y, n, n, n, n, n, n, n, n, n},
   143  		},
   144  	}
   145  
   146  	m := mockClock{}
   147  	oldTimeNowFunc := timeNowFunc
   148  	timeNowFunc = m.Now
   149  	oldRandFunc := randFunc
   150  	randFunc = func() float64 { return 0.5 }
   151  	defer func() {
   152  		timeNowFunc = oldTimeNowFunc
   153  		randFunc = oldRandFunc
   154  	}()
   155  
   156  	for _, test := range testcases {
   157  		t.Run(test.desc, func(t *testing.T) {
   158  			m.SetNanos(0)
   159  			th := newWithArgs(time.Nanosecond, 1, test.ratioForAccepts, test.requestsPadding)
   160  			for i, response := range responses {
   161  				if response != E {
   162  					th.RegisterBackendResponse(response == T)
   163  				}
   164  				if got := th.ShouldThrottle(); got != test.want[i] {
   165  					t.Errorf("ShouldThrottle for index %d: got %v, want %v", i, got, test.want[i])
   166  				}
   167  			}
   168  		})
   169  	}
   170  }
   171  
   172  func TestParallel(t *testing.T) {
   173  	// Uses all the defaults which comes with a 30 second duration.
   174  	th := New()
   175  
   176  	testDuration := 2 * time.Second
   177  	numRoutines := 10
   178  	accepts := make([]int64, numRoutines)
   179  	throttles := make([]int64, numRoutines)
   180  	var wg sync.WaitGroup
   181  	for i := 0; i < numRoutines; i++ {
   182  		wg.Add(1)
   183  		go func(num int) {
   184  			defer wg.Done()
   185  
   186  			ticker := time.NewTicker(testDuration)
   187  			var accept int64
   188  			var throttle int64
   189  			for i := 0; ; i++ {
   190  				select {
   191  				case <-ticker.C:
   192  					ticker.Stop()
   193  					accepts[num] = accept
   194  					throttles[num] = throttle
   195  					return
   196  				default:
   197  					if i%2 == 0 {
   198  						th.RegisterBackendResponse(true)
   199  						throttle++
   200  					} else {
   201  						th.RegisterBackendResponse(false)
   202  						accept++
   203  					}
   204  				}
   205  			}
   206  		}(i)
   207  	}
   208  	wg.Wait()
   209  
   210  	var wantAccepts, wantThrottles int64
   211  	for i := 0; i < numRoutines; i++ {
   212  		wantAccepts += accepts[i]
   213  		wantThrottles += throttles[i]
   214  	}
   215  
   216  	if gotAccepts, gotThrottles := th.stats(); gotAccepts != wantAccepts || gotThrottles != wantThrottles {
   217  		t.Errorf("th.stats() = {%d, %d}, want {%d, %d}", gotAccepts, gotThrottles, wantAccepts, wantThrottles)
   218  	}
   219  }
   220  
   221  type mockClock struct {
   222  	t time.Time
   223  }
   224  
   225  func (m *mockClock) Now() time.Time {
   226  	return m.t
   227  }
   228  
   229  func (m *mockClock) SetNanos(n int64) {
   230  	m.t = time.Unix(0, n)
   231  }