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