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 }