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