github.com/JFJun/bsc@v1.0.0/les/lespay/client/valuetracker.go (about) 1 // Copyright 2020 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 details. 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 client 18 19 import ( 20 "bytes" 21 "fmt" 22 "math" 23 "sync" 24 "time" 25 26 "github.com/JFJun/bsc/common/mclock" 27 "github.com/JFJun/bsc/ethdb" 28 "github.com/JFJun/bsc/les/utils" 29 "github.com/JFJun/bsc/log" 30 "github.com/JFJun/bsc/p2p/enode" 31 "github.com/JFJun/bsc/rlp" 32 ) 33 34 const ( 35 vtVersion = 1 // database encoding format for ValueTracker 36 nvtVersion = 1 // database encoding format for NodeValueTracker 37 ) 38 39 var ( 40 vtKey = []byte("vt:") 41 vtNodeKey = []byte("vtNode:") 42 ) 43 44 // NodeValueTracker collects service value statistics for a specific server node 45 type NodeValueTracker struct { 46 lock sync.Mutex 47 48 rtStats, lastRtStats ResponseTimeStats 49 lastTransfer mclock.AbsTime 50 basket serverBasket 51 reqCosts []uint64 52 reqValues *[]float64 53 } 54 55 // init initializes a NodeValueTracker. 56 // Note that the contents of the referenced reqValues slice will not change; a new 57 // reference is passed if the values are updated by ValueTracker. 58 func (nv *NodeValueTracker) init(now mclock.AbsTime, reqValues *[]float64) { 59 reqTypeCount := len(*reqValues) 60 nv.reqCosts = make([]uint64, reqTypeCount) 61 nv.lastTransfer = now 62 nv.reqValues = reqValues 63 nv.basket.init(reqTypeCount) 64 } 65 66 // updateCosts updates the request cost table of the server. The request value factor 67 // is also updated based on the given cost table and the current reference basket. 68 // Note that the contents of the referenced reqValues slice will not change; a new 69 // reference is passed if the values are updated by ValueTracker. 70 func (nv *NodeValueTracker) updateCosts(reqCosts []uint64, reqValues *[]float64, rvFactor float64) { 71 nv.lock.Lock() 72 defer nv.lock.Unlock() 73 74 nv.reqCosts = reqCosts 75 nv.reqValues = reqValues 76 nv.basket.updateRvFactor(rvFactor) 77 } 78 79 // transferStats returns request basket and response time statistics that should be 80 // added to the global statistics. The contents of the server's own request basket are 81 // gradually transferred to the main reference basket and removed from the server basket 82 // with the specified transfer rate. 83 // The response time statistics are retained at both places and therefore the global 84 // distribution is always the sum of the individual server distributions. 85 func (nv *NodeValueTracker) transferStats(now mclock.AbsTime, transferRate float64) (requestBasket, ResponseTimeStats) { 86 nv.lock.Lock() 87 defer nv.lock.Unlock() 88 89 dt := now - nv.lastTransfer 90 nv.lastTransfer = now 91 if dt < 0 { 92 dt = 0 93 } 94 recentRtStats := nv.rtStats 95 recentRtStats.SubStats(&nv.lastRtStats) 96 nv.lastRtStats = nv.rtStats 97 return nv.basket.transfer(-math.Expm1(-transferRate * float64(dt))), recentRtStats 98 } 99 100 // RtStats returns the node's own response time distribution statistics 101 func (nv *NodeValueTracker) RtStats() ResponseTimeStats { 102 nv.lock.Lock() 103 defer nv.lock.Unlock() 104 105 return nv.rtStats 106 } 107 108 // ValueTracker coordinates service value calculation for individual servers and updates 109 // global statistics 110 type ValueTracker struct { 111 clock mclock.Clock 112 lock sync.Mutex 113 quit chan chan struct{} 114 db ethdb.KeyValueStore 115 connected map[enode.ID]*NodeValueTracker 116 reqTypeCount int 117 118 refBasket referenceBasket 119 mappings [][]string 120 currentMapping int 121 initRefBasket requestBasket 122 rtStats ResponseTimeStats 123 124 transferRate float64 125 statsExpLock sync.RWMutex 126 statsExpRate, offlineExpRate float64 127 statsExpirer utils.Expirer 128 statsExpFactor utils.ExpirationFactor 129 } 130 131 type valueTrackerEncV1 struct { 132 Mappings [][]string 133 RefBasketMapping uint 134 RefBasket requestBasket 135 RtStats ResponseTimeStats 136 ExpOffset, SavedAt uint64 137 } 138 139 type nodeValueTrackerEncV1 struct { 140 RtStats ResponseTimeStats 141 ServerBasketMapping uint 142 ServerBasket requestBasket 143 } 144 145 // RequestInfo is an initializer structure for the service vector. 146 type RequestInfo struct { 147 // Name identifies the request type and is used for re-mapping the service vector if necessary 148 Name string 149 // InitAmount and InitValue are used to initialize the reference basket 150 InitAmount, InitValue float64 151 } 152 153 // NewValueTracker creates a new ValueTracker and loads its previously saved state from 154 // the database if possible. 155 func NewValueTracker(db ethdb.KeyValueStore, clock mclock.Clock, reqInfo []RequestInfo, updatePeriod time.Duration, transferRate, statsExpRate, offlineExpRate float64) *ValueTracker { 156 now := clock.Now() 157 158 initRefBasket := requestBasket{items: make([]basketItem, len(reqInfo))} 159 mapping := make([]string, len(reqInfo)) 160 161 var sumAmount, sumValue float64 162 for _, req := range reqInfo { 163 sumAmount += req.InitAmount 164 sumValue += req.InitAmount * req.InitValue 165 } 166 scaleValues := sumAmount * basketFactor / sumValue 167 for i, req := range reqInfo { 168 mapping[i] = req.Name 169 initRefBasket.items[i].amount = uint64(req.InitAmount * basketFactor) 170 initRefBasket.items[i].value = uint64(req.InitAmount * req.InitValue * scaleValues) 171 } 172 173 vt := &ValueTracker{ 174 clock: clock, 175 connected: make(map[enode.ID]*NodeValueTracker), 176 quit: make(chan chan struct{}), 177 db: db, 178 reqTypeCount: len(initRefBasket.items), 179 initRefBasket: initRefBasket, 180 transferRate: transferRate, 181 statsExpRate: statsExpRate, 182 offlineExpRate: offlineExpRate, 183 } 184 if vt.loadFromDb(mapping) != nil { 185 // previous state not saved or invalid, init with default values 186 vt.refBasket.basket = initRefBasket 187 vt.mappings = [][]string{mapping} 188 vt.currentMapping = 0 189 } 190 vt.statsExpirer.SetRate(now, statsExpRate) 191 vt.refBasket.init(vt.reqTypeCount) 192 vt.periodicUpdate() 193 194 go func() { 195 for { 196 select { 197 case <-clock.After(updatePeriod): 198 vt.lock.Lock() 199 vt.periodicUpdate() 200 vt.lock.Unlock() 201 case quit := <-vt.quit: 202 close(quit) 203 return 204 } 205 } 206 }() 207 return vt 208 } 209 210 // StatsExpirer returns the statistics expirer so that other values can be expired 211 // with the same rate as the service value statistics. 212 func (vt *ValueTracker) StatsExpirer() *utils.Expirer { 213 return &vt.statsExpirer 214 } 215 216 // loadFromDb loads the value tracker's state from the database and converts saved 217 // request basket index mapping if it does not match the specified index to name mapping. 218 func (vt *ValueTracker) loadFromDb(mapping []string) error { 219 enc, err := vt.db.Get(vtKey) 220 if err != nil { 221 return err 222 } 223 r := bytes.NewReader(enc) 224 var version uint 225 if err := rlp.Decode(r, &version); err != nil { 226 log.Error("Decoding value tracker state failed", "err", err) 227 return err 228 } 229 if version != vtVersion { 230 log.Error("Unknown ValueTracker version", "stored", version, "current", nvtVersion) 231 return fmt.Errorf("Unknown ValueTracker version %d (current version is %d)", version, vtVersion) 232 } 233 var vte valueTrackerEncV1 234 if err := rlp.Decode(r, &vte); err != nil { 235 log.Error("Decoding value tracker state failed", "err", err) 236 return err 237 } 238 logOffset := utils.Fixed64(vte.ExpOffset) 239 dt := time.Now().UnixNano() - int64(vte.SavedAt) 240 if dt > 0 { 241 logOffset += utils.Float64ToFixed64(float64(dt) * vt.offlineExpRate / math.Log(2)) 242 } 243 vt.statsExpirer.SetLogOffset(vt.clock.Now(), logOffset) 244 vt.rtStats = vte.RtStats 245 vt.mappings = vte.Mappings 246 vt.currentMapping = -1 247 loop: 248 for i, m := range vt.mappings { 249 if len(m) != len(mapping) { 250 continue loop 251 } 252 for j, s := range mapping { 253 if m[j] != s { 254 continue loop 255 } 256 } 257 vt.currentMapping = i 258 break 259 } 260 if vt.currentMapping == -1 { 261 vt.currentMapping = len(vt.mappings) 262 vt.mappings = append(vt.mappings, mapping) 263 } 264 if int(vte.RefBasketMapping) == vt.currentMapping { 265 vt.refBasket.basket = vte.RefBasket 266 } else { 267 if vte.RefBasketMapping >= uint(len(vt.mappings)) { 268 log.Error("Unknown request basket mapping", "stored", vte.RefBasketMapping, "current", vt.currentMapping) 269 return fmt.Errorf("Unknown request basket mapping %d (current version is %d)", vte.RefBasketMapping, vt.currentMapping) 270 } 271 vt.refBasket.basket = vte.RefBasket.convertMapping(vt.mappings[vte.RefBasketMapping], mapping, vt.initRefBasket) 272 } 273 return nil 274 } 275 276 // saveToDb saves the value tracker's state to the database 277 func (vt *ValueTracker) saveToDb() { 278 vte := valueTrackerEncV1{ 279 Mappings: vt.mappings, 280 RefBasketMapping: uint(vt.currentMapping), 281 RefBasket: vt.refBasket.basket, 282 RtStats: vt.rtStats, 283 ExpOffset: uint64(vt.statsExpirer.LogOffset(vt.clock.Now())), 284 SavedAt: uint64(time.Now().UnixNano()), 285 } 286 enc1, err := rlp.EncodeToBytes(uint(vtVersion)) 287 if err != nil { 288 log.Error("Encoding value tracker state failed", "err", err) 289 return 290 } 291 enc2, err := rlp.EncodeToBytes(&vte) 292 if err != nil { 293 log.Error("Encoding value tracker state failed", "err", err) 294 return 295 } 296 if err := vt.db.Put(vtKey, append(enc1, enc2...)); err != nil { 297 log.Error("Saving value tracker state failed", "err", err) 298 } 299 } 300 301 // Stop saves the value tracker's state and each loaded node's individual state and 302 // returns after shutting the internal goroutines down. 303 func (vt *ValueTracker) Stop() { 304 quit := make(chan struct{}) 305 vt.quit <- quit 306 <-quit 307 vt.lock.Lock() 308 vt.periodicUpdate() 309 for id, nv := range vt.connected { 310 vt.saveNode(id, nv) 311 } 312 vt.connected = nil 313 vt.saveToDb() 314 vt.lock.Unlock() 315 } 316 317 // Register adds a server node to the value tracker 318 func (vt *ValueTracker) Register(id enode.ID) *NodeValueTracker { 319 vt.lock.Lock() 320 defer vt.lock.Unlock() 321 322 if vt.connected == nil { 323 // ValueTracker has already been stopped 324 return nil 325 } 326 nv := vt.loadOrNewNode(id) 327 nv.init(vt.clock.Now(), &vt.refBasket.reqValues) 328 vt.connected[id] = nv 329 return nv 330 } 331 332 // Unregister removes a server node from the value tracker 333 func (vt *ValueTracker) Unregister(id enode.ID) { 334 vt.lock.Lock() 335 defer vt.lock.Unlock() 336 337 if nv := vt.connected[id]; nv != nil { 338 vt.saveNode(id, nv) 339 delete(vt.connected, id) 340 } 341 } 342 343 // GetNode returns an individual server node's value tracker. If it did not exist before 344 // then a new node is created. 345 func (vt *ValueTracker) GetNode(id enode.ID) *NodeValueTracker { 346 vt.lock.Lock() 347 defer vt.lock.Unlock() 348 349 return vt.loadOrNewNode(id) 350 } 351 352 // loadOrNewNode returns an individual server node's value tracker. If it did not exist before 353 // then a new node is created. 354 func (vt *ValueTracker) loadOrNewNode(id enode.ID) *NodeValueTracker { 355 if nv, ok := vt.connected[id]; ok { 356 return nv 357 } 358 nv := &NodeValueTracker{lastTransfer: vt.clock.Now()} 359 enc, err := vt.db.Get(append(vtNodeKey, id[:]...)) 360 if err != nil { 361 return nv 362 } 363 r := bytes.NewReader(enc) 364 var version uint 365 if err := rlp.Decode(r, &version); err != nil { 366 log.Error("Failed to decode node value tracker", "id", id, "err", err) 367 return nv 368 } 369 if version != nvtVersion { 370 log.Error("Unknown NodeValueTracker version", "stored", version, "current", nvtVersion) 371 return nv 372 } 373 var nve nodeValueTrackerEncV1 374 if err := rlp.Decode(r, &nve); err != nil { 375 log.Error("Failed to decode node value tracker", "id", id, "err", err) 376 return nv 377 } 378 nv.rtStats = nve.RtStats 379 nv.lastRtStats = nve.RtStats 380 if int(nve.ServerBasketMapping) == vt.currentMapping { 381 nv.basket.basket = nve.ServerBasket 382 } else { 383 if nve.ServerBasketMapping >= uint(len(vt.mappings)) { 384 log.Error("Unknown request basket mapping", "stored", nve.ServerBasketMapping, "current", vt.currentMapping) 385 return nv 386 } 387 nv.basket.basket = nve.ServerBasket.convertMapping(vt.mappings[nve.ServerBasketMapping], vt.mappings[vt.currentMapping], vt.initRefBasket) 388 } 389 return nv 390 } 391 392 // saveNode saves a server node's value tracker to the database 393 func (vt *ValueTracker) saveNode(id enode.ID, nv *NodeValueTracker) { 394 recentRtStats := nv.rtStats 395 recentRtStats.SubStats(&nv.lastRtStats) 396 vt.rtStats.AddStats(&recentRtStats) 397 nv.lastRtStats = nv.rtStats 398 399 nve := nodeValueTrackerEncV1{ 400 RtStats: nv.rtStats, 401 ServerBasketMapping: uint(vt.currentMapping), 402 ServerBasket: nv.basket.basket, 403 } 404 enc1, err := rlp.EncodeToBytes(uint(nvtVersion)) 405 if err != nil { 406 log.Error("Failed to encode service value information", "id", id, "err", err) 407 return 408 } 409 enc2, err := rlp.EncodeToBytes(&nve) 410 if err != nil { 411 log.Error("Failed to encode service value information", "id", id, "err", err) 412 return 413 } 414 if err := vt.db.Put(append(vtNodeKey, id[:]...), append(enc1, enc2...)); err != nil { 415 log.Error("Failed to save service value information", "id", id, "err", err) 416 } 417 } 418 419 // UpdateCosts updates the node value tracker's request cost table 420 func (vt *ValueTracker) UpdateCosts(nv *NodeValueTracker, reqCosts []uint64) { 421 vt.lock.Lock() 422 defer vt.lock.Unlock() 423 424 nv.updateCosts(reqCosts, &vt.refBasket.reqValues, vt.refBasket.reqValueFactor(reqCosts)) 425 } 426 427 // RtStats returns the global response time distribution statistics 428 func (vt *ValueTracker) RtStats() ResponseTimeStats { 429 vt.lock.Lock() 430 defer vt.lock.Unlock() 431 432 vt.periodicUpdate() 433 return vt.rtStats 434 } 435 436 // periodicUpdate transfers individual node data to the global statistics, normalizes 437 // the reference basket and updates request values. The global state is also saved to 438 // the database with each update. 439 func (vt *ValueTracker) periodicUpdate() { 440 now := vt.clock.Now() 441 vt.statsExpLock.Lock() 442 vt.statsExpFactor = utils.ExpFactor(vt.statsExpirer.LogOffset(now)) 443 vt.statsExpLock.Unlock() 444 445 for _, nv := range vt.connected { 446 basket, rtStats := nv.transferStats(now, vt.transferRate) 447 vt.refBasket.add(basket) 448 vt.rtStats.AddStats(&rtStats) 449 } 450 vt.refBasket.normalize() 451 vt.refBasket.updateReqValues() 452 for _, nv := range vt.connected { 453 nv.updateCosts(nv.reqCosts, &vt.refBasket.reqValues, vt.refBasket.reqValueFactor(nv.reqCosts)) 454 } 455 vt.saveToDb() 456 } 457 458 type ServedRequest struct { 459 ReqType, Amount uint32 460 } 461 462 // Served adds a served request to the node's statistics. An actual request may be composed 463 // of one or more request types (service vector indices). 464 func (vt *ValueTracker) Served(nv *NodeValueTracker, reqs []ServedRequest, respTime time.Duration) { 465 vt.statsExpLock.RLock() 466 expFactor := vt.statsExpFactor 467 vt.statsExpLock.RUnlock() 468 469 nv.lock.Lock() 470 defer nv.lock.Unlock() 471 472 var value float64 473 for _, r := range reqs { 474 nv.basket.add(r.ReqType, r.Amount, nv.reqCosts[r.ReqType]*uint64(r.Amount), expFactor) 475 value += (*nv.reqValues)[r.ReqType] * float64(r.Amount) 476 } 477 nv.rtStats.Add(respTime, value, vt.statsExpFactor) 478 } 479 480 type RequestStatsItem struct { 481 Name string 482 ReqAmount, ReqValue float64 483 } 484 485 // RequestStats returns the current contents of the reference request basket, with 486 // request values meaning average per request rather than total. 487 func (vt *ValueTracker) RequestStats() []RequestStatsItem { 488 vt.statsExpLock.RLock() 489 expFactor := vt.statsExpFactor 490 vt.statsExpLock.RUnlock() 491 vt.lock.Lock() 492 defer vt.lock.Unlock() 493 494 vt.periodicUpdate() 495 res := make([]RequestStatsItem, len(vt.refBasket.basket.items)) 496 for i, item := range vt.refBasket.basket.items { 497 res[i].Name = vt.mappings[vt.currentMapping][i] 498 res[i].ReqAmount = expFactor.Value(float64(item.amount)/basketFactor, vt.refBasket.basket.exp) 499 res[i].ReqValue = vt.refBasket.reqValues[i] 500 } 501 return res 502 } 503 504 // TotalServiceValue returns the total service value provided by the given node (as 505 // a function of the weights which are calculated from the request timeout value). 506 func (vt *ValueTracker) TotalServiceValue(nv *NodeValueTracker, weights ResponseTimeWeights) float64 { 507 vt.statsExpLock.RLock() 508 expFactor := vt.statsExpFactor 509 vt.statsExpLock.RUnlock() 510 511 nv.lock.Lock() 512 defer nv.lock.Unlock() 513 514 return nv.rtStats.Value(weights, expFactor) 515 }