github.com/cloudwego/kitex@v0.9.0/pkg/remote/trans/nphttp2/grpc/bdp_estimator.go (about)

     1  /*
     2   *
     3   * Copyright 2017 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   * This file may have been modified by CloudWeGo authors. All CloudWeGo
    18   * Modifications are Copyright 2021 CloudWeGo Authors.
    19   */
    20  
    21  package grpc
    22  
    23  import (
    24  	"sync"
    25  	"time"
    26  )
    27  
    28  const (
    29  	// bdpLimit is the maximum value the flow control windows will be increased
    30  	// to.  TCP typically limits this to 4MB, but some systems go up to 16MB.
    31  	// Since this is only a limit, it is safe to make it optimistic.
    32  	bdpLimit = (1 << 20) * 16
    33  	// alpha is a constant factor used to keep a moving average
    34  	// of RTTs.
    35  	alpha = 0.9
    36  	// If the current bdp sample is greater than or equal to
    37  	// our beta * our estimated bdp and the current bandwidth
    38  	// sample is the maximum bandwidth observed so far, we
    39  	// increase our bbp estimate by a factor of gamma.
    40  	beta = 0.66
    41  	// To put our bdp to be smaller than or equal to twice the real BDP,
    42  	// we should multiply our current sample with 4/3, however to round things out
    43  	// we use 2 as the multiplication factor.
    44  	gamma = 2
    45  )
    46  
    47  // Adding arbitrary data to ping so that its ack can be identified.
    48  // Easter-egg: what does the ping message say?
    49  var bdpPing = &ping{data: [8]byte{2, 4, 16, 16, 9, 14, 7, 7}}
    50  
    51  type bdpEstimator struct {
    52  	// sentAt is the time when the ping was sent.
    53  	sentAt time.Time
    54  
    55  	mu sync.Mutex
    56  	// bdp is the current bdp estimate.
    57  	bdp uint32
    58  	// sample is the number of bytes received in one measurement cycle.
    59  	sample uint32
    60  	// bwMax is the maximum bandwidth noted so far (bytes/sec).
    61  	bwMax float64
    62  	// bool to keep track of the beginning of a new measurement cycle.
    63  	isSent bool
    64  	// Callback to update the window sizes.
    65  	updateFlowControl func(n uint32)
    66  	// sampleCount is the number of samples taken so far.
    67  	sampleCount uint64
    68  	// round trip time (seconds)
    69  	rtt float64
    70  }
    71  
    72  // timesnap registers the time bdp ping was sent out so that
    73  // network rtt can be calculated when its ack is received.
    74  // It is called (by controller) when the bdpPing is
    75  // being written on the wire.
    76  func (b *bdpEstimator) timesnap(d [8]byte) {
    77  	if bdpPing.data != d {
    78  		return
    79  	}
    80  	// Locking here is to avoid DATA RACE in the unittest.
    81  	// In fact, it would not bring the concurrency problem.
    82  	b.mu.Lock()
    83  	b.sentAt = time.Now()
    84  	b.mu.Unlock()
    85  }
    86  
    87  // add adds bytes to the current sample for calculating bdp.
    88  // It returns true only if a ping must be sent. This can be used
    89  // by the caller (handleData) to make decision about batching
    90  // a window update with it.
    91  func (b *bdpEstimator) add(n uint32) bool {
    92  	b.mu.Lock()
    93  	defer b.mu.Unlock()
    94  	if b.bdp == bdpLimit {
    95  		return false
    96  	}
    97  	if !b.isSent {
    98  		b.isSent = true
    99  		b.sample = n
   100  		b.sentAt = time.Time{}
   101  		b.sampleCount++
   102  		return true
   103  	}
   104  	b.sample += n
   105  	return false
   106  }
   107  
   108  // calculate is called when an ack for a bdp ping is received.
   109  // Here we calculate the current bdp and bandwidth sample and
   110  // decide if the flow control windows should go up.
   111  func (b *bdpEstimator) calculate(d [8]byte) {
   112  	// Check if the ping acked for was the bdp ping.
   113  	if bdpPing.data != d {
   114  		return
   115  	}
   116  	b.mu.Lock()
   117  	rttSample := time.Since(b.sentAt).Seconds()
   118  	if b.sampleCount < 10 {
   119  		// Bootstrap rtt with an average of first 10 rtt samples.
   120  		b.rtt += (rttSample - b.rtt) / float64(b.sampleCount)
   121  	} else {
   122  		// Heed to the recent past more.
   123  		b.rtt += (rttSample - b.rtt) * float64(alpha)
   124  	}
   125  	b.isSent = false
   126  	// The number of bytes accumulated so far in the sample is smaller
   127  	// than or equal to 1.5 times the real BDP on a saturated connection.
   128  	bwCurrent := float64(b.sample) / (b.rtt * float64(1.5))
   129  	if bwCurrent > b.bwMax {
   130  		b.bwMax = bwCurrent
   131  	}
   132  	// If the current sample (which is smaller than or equal to the 1.5 times the real BDP) is
   133  	// greater than or equal to 2/3rd our perceived bdp AND this is the maximum bandwidth seen so far, we
   134  	// should update our perception of the network BDP.
   135  	if float64(b.sample) >= beta*float64(b.bdp) && bwCurrent == b.bwMax && b.bdp != bdpLimit {
   136  		sampleFloat := float64(b.sample)
   137  		b.bdp = uint32(gamma * sampleFloat)
   138  		if b.bdp > bdpLimit {
   139  			b.bdp = bdpLimit
   140  		}
   141  		bdp := b.bdp
   142  		b.mu.Unlock()
   143  		b.updateFlowControl(bdp)
   144  		return
   145  	}
   146  	b.mu.Unlock()
   147  }