github.com/jincm/wesharechain@v0.0.0-20210122032815-1537409ce26a/chain/les/costtracker.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more detailct. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package les 18 19 import ( 20 "encoding/binary" 21 "math" 22 "sync" 23 "sync/atomic" 24 "time" 25 26 "github.com/ethereum/go-ethereum/common/mclock" 27 "github.com/ethereum/go-ethereum/eth" 28 "github.com/ethereum/go-ethereum/ethdb" 29 "github.com/ethereum/go-ethereum/les/flowcontrol" 30 "github.com/ethereum/go-ethereum/log" 31 ) 32 33 const makeCostStats = false // make request cost statistics during operation 34 35 var ( 36 // average request cost estimates based on serving time 37 reqAvgTimeCost = requestCostTable{ 38 GetBlockHeadersMsg: {150000, 30000}, 39 GetBlockBodiesMsg: {0, 700000}, 40 GetReceiptsMsg: {0, 1000000}, 41 GetCodeMsg: {0, 450000}, 42 GetProofsV1Msg: {0, 600000}, 43 GetProofsV2Msg: {0, 600000}, 44 GetHeaderProofsMsg: {0, 1000000}, 45 GetHelperTrieProofsMsg: {0, 1000000}, 46 SendTxMsg: {0, 450000}, 47 SendTxV2Msg: {0, 450000}, 48 GetTxStatusMsg: {0, 250000}, 49 } 50 // maximum incoming message size estimates 51 reqMaxInSize = requestCostTable{ 52 GetBlockHeadersMsg: {40, 0}, 53 GetBlockBodiesMsg: {0, 40}, 54 GetReceiptsMsg: {0, 40}, 55 GetCodeMsg: {0, 80}, 56 GetProofsV1Msg: {0, 80}, 57 GetProofsV2Msg: {0, 80}, 58 GetHeaderProofsMsg: {0, 20}, 59 GetHelperTrieProofsMsg: {0, 20}, 60 SendTxMsg: {0, 66000}, 61 SendTxV2Msg: {0, 66000}, 62 GetTxStatusMsg: {0, 50}, 63 } 64 // maximum outgoing message size estimates 65 reqMaxOutSize = requestCostTable{ 66 GetBlockHeadersMsg: {0, 556}, 67 GetBlockBodiesMsg: {0, 100000}, 68 GetReceiptsMsg: {0, 200000}, 69 GetCodeMsg: {0, 50000}, 70 GetProofsV1Msg: {0, 4000}, 71 GetProofsV2Msg: {0, 4000}, 72 GetHeaderProofsMsg: {0, 4000}, 73 GetHelperTrieProofsMsg: {0, 4000}, 74 SendTxMsg: {0, 0}, 75 SendTxV2Msg: {0, 100}, 76 GetTxStatusMsg: {0, 100}, 77 } 78 minBufLimit = uint64(50000000 * maxCostFactor) // minimum buffer limit allowed for a client 79 minCapacity = (minBufLimit-1)/bufLimitRatio + 1 // minimum capacity allowed for a client 80 ) 81 82 const ( 83 maxCostFactor = 2 // ratio of maximum and average cost estimates 84 gfInitWeight = time.Second * 10 85 gfMaxWeight = time.Hour 86 gfUsageThreshold = 0.5 87 gfUsageTC = time.Second 88 gfDbKey = "_globalCostFactor" 89 ) 90 91 // costTracker is responsible for calculating costs and cost estimates on the 92 // server side. It continuously updates the global cost factor which is defined 93 // as the number of cost units per nanosecond of serving time in a single thread. 94 // It is based on statistics collected during serving requests in high-load periods 95 // and practically acts as a one-dimension request price scaling factor over the 96 // pre-defined cost estimate table. Instead of scaling the cost values, the real 97 // value of cost units is changed by applying the factor to the serving times. This 98 // is more convenient because the changes in the cost factor can be applied immediately 99 // without always notifying the clients about the changed cost tables. 100 type costTracker struct { 101 db ethdb.Database 102 stopCh chan chan struct{} 103 104 inSizeFactor, outSizeFactor float64 105 gf, utilTarget float64 106 107 gfUpdateCh chan gfUpdate 108 gfLock sync.RWMutex 109 totalRechargeCh chan uint64 110 111 stats map[uint64][]uint64 112 } 113 114 // newCostTracker creates a cost tracker and loads the cost factor statistics from the database 115 func newCostTracker(db ethdb.Database, config *eth.Config) *costTracker { 116 utilTarget := float64(config.LightServ) * flowcontrol.FixedPointMultiplier / 100 117 ct := &costTracker{ 118 db: db, 119 stopCh: make(chan chan struct{}), 120 utilTarget: utilTarget, 121 } 122 if config.LightBandwidthIn > 0 { 123 ct.inSizeFactor = utilTarget / float64(config.LightBandwidthIn) 124 } 125 if config.LightBandwidthOut > 0 { 126 ct.outSizeFactor = utilTarget / float64(config.LightBandwidthOut) 127 } 128 if makeCostStats { 129 ct.stats = make(map[uint64][]uint64) 130 for code := range reqAvgTimeCost { 131 ct.stats[code] = make([]uint64, 10) 132 } 133 } 134 ct.gfLoop() 135 return ct 136 } 137 138 // stop stops the cost tracker and saves the cost factor statistics to the database 139 func (ct *costTracker) stop() { 140 stopCh := make(chan struct{}) 141 ct.stopCh <- stopCh 142 <-stopCh 143 if makeCostStats { 144 ct.printStats() 145 } 146 } 147 148 // makeCostList returns upper cost estimates based on the hardcoded cost estimate 149 // tables and the optionally specified incoming/outgoing bandwidth limits 150 func (ct *costTracker) makeCostList() RequestCostList { 151 maxCost := func(avgTime, inSize, outSize uint64) uint64 { 152 globalFactor := ct.globalFactor() 153 154 cost := avgTime * maxCostFactor 155 inSizeCost := uint64(float64(inSize) * ct.inSizeFactor * globalFactor * maxCostFactor) 156 if inSizeCost > cost { 157 cost = inSizeCost 158 } 159 outSizeCost := uint64(float64(outSize) * ct.outSizeFactor * globalFactor * maxCostFactor) 160 if outSizeCost > cost { 161 cost = outSizeCost 162 } 163 return cost 164 } 165 var list RequestCostList 166 for code, data := range reqAvgTimeCost { 167 list = append(list, requestCostListItem{ 168 MsgCode: code, 169 BaseCost: maxCost(data.baseCost, reqMaxInSize[code].baseCost, reqMaxOutSize[code].baseCost), 170 ReqCost: maxCost(data.reqCost, reqMaxInSize[code].reqCost, reqMaxOutSize[code].reqCost), 171 }) 172 } 173 return list 174 } 175 176 type gfUpdate struct { 177 avgTime, servingTime float64 178 } 179 180 // gfLoop starts an event loop which updates the global cost factor which is 181 // calculated as a weighted average of the average estimate / serving time ratio. 182 // The applied weight equals the serving time if gfUsage is over a threshold, 183 // zero otherwise. gfUsage is the recent average serving time per time unit in 184 // an exponential moving window. This ensures that statistics are collected only 185 // under high-load circumstances where the measured serving times are relevant. 186 // The total recharge parameter of the flow control system which controls the 187 // total allowed serving time per second but nominated in cost units, should 188 // also be scaled with the cost factor and is also updated by this loop. 189 func (ct *costTracker) gfLoop() { 190 var gfUsage, gfSum, gfWeight float64 191 lastUpdate := mclock.Now() 192 expUpdate := lastUpdate 193 194 data, _ := ct.db.Get([]byte(gfDbKey)) 195 if len(data) == 16 { 196 gfSum = math.Float64frombits(binary.BigEndian.Uint64(data[0:8])) 197 gfWeight = math.Float64frombits(binary.BigEndian.Uint64(data[8:16])) 198 } 199 if gfWeight < float64(gfInitWeight) { 200 gfSum = float64(gfInitWeight) 201 gfWeight = float64(gfInitWeight) 202 } 203 gf := gfSum / gfWeight 204 ct.gf = gf 205 ct.gfUpdateCh = make(chan gfUpdate, 100) 206 207 go func() { 208 for { 209 select { 210 case r := <-ct.gfUpdateCh: 211 now := mclock.Now() 212 max := r.servingTime * gf 213 if r.avgTime > max { 214 max = r.avgTime 215 } 216 dt := float64(now - expUpdate) 217 expUpdate = now 218 gfUsage = gfUsage*math.Exp(-dt/float64(gfUsageTC)) + max*1000000/float64(gfUsageTC) 219 220 if gfUsage >= gfUsageThreshold*ct.utilTarget*gf { 221 gfSum += r.avgTime 222 gfWeight += r.servingTime 223 if time.Duration(now-lastUpdate) > time.Second { 224 gf = gfSum / gfWeight 225 if gfWeight >= float64(gfMaxWeight) { 226 gfSum = gf * float64(gfMaxWeight) 227 gfWeight = float64(gfMaxWeight) 228 } 229 lastUpdate = now 230 ct.gfLock.Lock() 231 ct.gf = gf 232 ch := ct.totalRechargeCh 233 ct.gfLock.Unlock() 234 if ch != nil { 235 select { 236 case ct.totalRechargeCh <- uint64(ct.utilTarget * gf): 237 default: 238 } 239 } 240 log.Debug("global cost factor updated", "gf", gf, "weight", time.Duration(gfWeight)) 241 } 242 } 243 case stopCh := <-ct.stopCh: 244 var data [16]byte 245 binary.BigEndian.PutUint64(data[0:8], math.Float64bits(gfSum)) 246 binary.BigEndian.PutUint64(data[8:16], math.Float64bits(gfWeight)) 247 ct.db.Put([]byte(gfDbKey), data[:]) 248 log.Debug("global cost factor saved", "sum", time.Duration(gfSum), "weight", time.Duration(gfWeight)) 249 close(stopCh) 250 return 251 } 252 } 253 }() 254 } 255 256 // globalFactor returns the current value of the global cost factor 257 func (ct *costTracker) globalFactor() float64 { 258 ct.gfLock.RLock() 259 defer ct.gfLock.RUnlock() 260 261 return ct.gf 262 } 263 264 // totalRecharge returns the current total recharge parameter which is used by 265 // flowcontrol.ClientManager and is scaled by the global cost factor 266 func (ct *costTracker) totalRecharge() uint64 { 267 ct.gfLock.RLock() 268 defer ct.gfLock.RUnlock() 269 270 return uint64(ct.gf * ct.utilTarget) 271 } 272 273 // subscribeTotalRecharge returns all future updates to the total recharge value 274 // through a channel and also returns the current value 275 func (ct *costTracker) subscribeTotalRecharge(ch chan uint64) uint64 { 276 ct.gfLock.Lock() 277 defer ct.gfLock.Unlock() 278 279 ct.totalRechargeCh = ch 280 return uint64(ct.gf * ct.utilTarget) 281 } 282 283 // updateStats updates the global cost factor and (if enabled) the real cost vs. 284 // average estimate statistics 285 func (ct *costTracker) updateStats(code, amount, servingTime, realCost uint64) { 286 avg := reqAvgTimeCost[code] 287 avgTime := avg.baseCost + amount*avg.reqCost 288 select { 289 case ct.gfUpdateCh <- gfUpdate{float64(avgTime), float64(servingTime)}: 290 default: 291 } 292 if makeCostStats { 293 realCost <<= 4 294 l := 0 295 for l < 9 && realCost > avgTime { 296 l++ 297 realCost >>= 1 298 } 299 atomic.AddUint64(&ct.stats[code][l], 1) 300 } 301 } 302 303 // realCost calculates the final cost of a request based on actual serving time, 304 // incoming and outgoing message size 305 // 306 // Note: message size is only taken into account if bandwidth limitation is applied 307 // and the cost based on either message size is greater than the cost based on 308 // serving time. A maximum of the three costs is applied instead of their sum 309 // because the three limited resources (serving thread time and i/o bandwidth) can 310 // also be maxed out simultaneously. 311 func (ct *costTracker) realCost(servingTime uint64, inSize, outSize uint32) uint64 { 312 cost := float64(servingTime) 313 inSizeCost := float64(inSize) * ct.inSizeFactor 314 if inSizeCost > cost { 315 cost = inSizeCost 316 } 317 outSizeCost := float64(outSize) * ct.outSizeFactor 318 if outSizeCost > cost { 319 cost = outSizeCost 320 } 321 return uint64(cost * ct.globalFactor()) 322 } 323 324 // printStats prints the distribution of real request cost relative to the average estimates 325 func (ct *costTracker) printStats() { 326 if ct.stats == nil { 327 return 328 } 329 for code, arr := range ct.stats { 330 log.Info("Request cost statistics", "code", code, "1/16", arr[0], "1/8", arr[1], "1/4", arr[2], "1/2", arr[3], "1", arr[4], "2", arr[5], "4", arr[6], "8", arr[7], "16", arr[8], ">16", arr[9]) 331 } 332 } 333 334 type ( 335 // requestCostTable assigns a cost estimate function to each request type 336 // which is a linear function of the requested amount 337 // (cost = baseCost + reqCost * amount) 338 requestCostTable map[uint64]*requestCosts 339 requestCosts struct { 340 baseCost, reqCost uint64 341 } 342 343 // RequestCostList is a list representation of request costs which is used for 344 // database storage and communication through the network 345 RequestCostList []requestCostListItem 346 requestCostListItem struct { 347 MsgCode, BaseCost, ReqCost uint64 348 } 349 ) 350 351 // getCost calculates the estimated cost for a given request type and amount 352 func (table requestCostTable) getCost(code, amount uint64) uint64 { 353 costs := table[code] 354 return costs.baseCost + amount*costs.reqCost 355 } 356 357 // decode converts a cost list to a cost table 358 func (list RequestCostList) decode() requestCostTable { 359 table := make(requestCostTable) 360 for _, e := range list { 361 table[e.MsgCode] = &requestCosts{ 362 baseCost: e.BaseCost, 363 reqCost: e.ReqCost, 364 } 365 } 366 return table 367 } 368 369 // testCostList returns a dummy request cost list used by tests 370 func testCostList() RequestCostList { 371 cl := make(RequestCostList, len(reqAvgTimeCost)) 372 var max uint64 373 for code := range reqAvgTimeCost { 374 if code > max { 375 max = code 376 } 377 } 378 i := 0 379 for code := uint64(0); code <= max; code++ { 380 if _, ok := reqAvgTimeCost[code]; ok { 381 cl[i].MsgCode = code 382 cl[i].BaseCost = 0 383 cl[i].ReqCost = 0 384 i++ 385 } 386 } 387 return cl 388 }