github.com/cryptogateway/go-paymex@v0.0.0-20210204174735-96277fb1e602/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/cryptogateway/go-paymex/common/mclock" 27 "github.com/cryptogateway/go-paymex/ethdb" 28 "github.com/cryptogateway/go-paymex/les/utils" 29 "github.com/cryptogateway/go-paymex/log" 30 "github.com/cryptogateway/go-paymex/p2p/enode" 31 "github.com/cryptogateway/go-paymex/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 // StatsExpirer returns the current expiration factor so that other values can be expired 217 // with the same rate as the service value statistics. 218 func (vt *ValueTracker) StatsExpFactor() utils.ExpirationFactor { 219 vt.statsExpLock.RLock() 220 defer vt.statsExpLock.RUnlock() 221 222 return vt.statsExpFactor 223 } 224 225 // loadFromDb loads the value tracker's state from the database and converts saved 226 // request basket index mapping if it does not match the specified index to name mapping. 227 func (vt *ValueTracker) loadFromDb(mapping []string) error { 228 enc, err := vt.db.Get(vtKey) 229 if err != nil { 230 return err 231 } 232 r := bytes.NewReader(enc) 233 var version uint 234 if err := rlp.Decode(r, &version); err != nil { 235 log.Error("Decoding value tracker state failed", "err", err) 236 return err 237 } 238 if version != vtVersion { 239 log.Error("Unknown ValueTracker version", "stored", version, "current", nvtVersion) 240 return fmt.Errorf("Unknown ValueTracker version %d (current version is %d)", version, vtVersion) 241 } 242 var vte valueTrackerEncV1 243 if err := rlp.Decode(r, &vte); err != nil { 244 log.Error("Decoding value tracker state failed", "err", err) 245 return err 246 } 247 logOffset := utils.Fixed64(vte.ExpOffset) 248 dt := time.Now().UnixNano() - int64(vte.SavedAt) 249 if dt > 0 { 250 logOffset += utils.Float64ToFixed64(float64(dt) * vt.offlineExpRate / math.Log(2)) 251 } 252 vt.statsExpirer.SetLogOffset(vt.clock.Now(), logOffset) 253 vt.rtStats = vte.RtStats 254 vt.mappings = vte.Mappings 255 vt.currentMapping = -1 256 loop: 257 for i, m := range vt.mappings { 258 if len(m) != len(mapping) { 259 continue loop 260 } 261 for j, s := range mapping { 262 if m[j] != s { 263 continue loop 264 } 265 } 266 vt.currentMapping = i 267 break 268 } 269 if vt.currentMapping == -1 { 270 vt.currentMapping = len(vt.mappings) 271 vt.mappings = append(vt.mappings, mapping) 272 } 273 if int(vte.RefBasketMapping) == vt.currentMapping { 274 vt.refBasket.basket = vte.RefBasket 275 } else { 276 if vte.RefBasketMapping >= uint(len(vt.mappings)) { 277 log.Error("Unknown request basket mapping", "stored", vte.RefBasketMapping, "current", vt.currentMapping) 278 return fmt.Errorf("Unknown request basket mapping %d (current version is %d)", vte.RefBasketMapping, vt.currentMapping) 279 } 280 vt.refBasket.basket = vte.RefBasket.convertMapping(vt.mappings[vte.RefBasketMapping], mapping, vt.initRefBasket) 281 } 282 return nil 283 } 284 285 // saveToDb saves the value tracker's state to the database 286 func (vt *ValueTracker) saveToDb() { 287 vte := valueTrackerEncV1{ 288 Mappings: vt.mappings, 289 RefBasketMapping: uint(vt.currentMapping), 290 RefBasket: vt.refBasket.basket, 291 RtStats: vt.rtStats, 292 ExpOffset: uint64(vt.statsExpirer.LogOffset(vt.clock.Now())), 293 SavedAt: uint64(time.Now().UnixNano()), 294 } 295 enc1, err := rlp.EncodeToBytes(uint(vtVersion)) 296 if err != nil { 297 log.Error("Encoding value tracker state failed", "err", err) 298 return 299 } 300 enc2, err := rlp.EncodeToBytes(&vte) 301 if err != nil { 302 log.Error("Encoding value tracker state failed", "err", err) 303 return 304 } 305 if err := vt.db.Put(vtKey, append(enc1, enc2...)); err != nil { 306 log.Error("Saving value tracker state failed", "err", err) 307 } 308 } 309 310 // Stop saves the value tracker's state and each loaded node's individual state and 311 // returns after shutting the internal goroutines down. 312 func (vt *ValueTracker) Stop() { 313 quit := make(chan struct{}) 314 vt.quit <- quit 315 <-quit 316 vt.lock.Lock() 317 vt.periodicUpdate() 318 for id, nv := range vt.connected { 319 vt.saveNode(id, nv) 320 } 321 vt.connected = nil 322 vt.saveToDb() 323 vt.lock.Unlock() 324 } 325 326 // Register adds a server node to the value tracker 327 func (vt *ValueTracker) Register(id enode.ID) *NodeValueTracker { 328 vt.lock.Lock() 329 defer vt.lock.Unlock() 330 331 if vt.connected == nil { 332 // ValueTracker has already been stopped 333 return nil 334 } 335 nv := vt.loadOrNewNode(id) 336 nv.init(vt.clock.Now(), &vt.refBasket.reqValues) 337 vt.connected[id] = nv 338 return nv 339 } 340 341 // Unregister removes a server node from the value tracker 342 func (vt *ValueTracker) Unregister(id enode.ID) { 343 vt.lock.Lock() 344 defer vt.lock.Unlock() 345 346 if nv := vt.connected[id]; nv != nil { 347 vt.saveNode(id, nv) 348 delete(vt.connected, id) 349 } 350 } 351 352 // GetNode 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) GetNode(id enode.ID) *NodeValueTracker { 355 vt.lock.Lock() 356 defer vt.lock.Unlock() 357 358 return vt.loadOrNewNode(id) 359 } 360 361 // loadOrNewNode returns an individual server node's value tracker. If it did not exist before 362 // then a new node is created. 363 func (vt *ValueTracker) loadOrNewNode(id enode.ID) *NodeValueTracker { 364 if nv, ok := vt.connected[id]; ok { 365 return nv 366 } 367 nv := &NodeValueTracker{lastTransfer: vt.clock.Now()} 368 enc, err := vt.db.Get(append(vtNodeKey, id[:]...)) 369 if err != nil { 370 return nv 371 } 372 r := bytes.NewReader(enc) 373 var version uint 374 if err := rlp.Decode(r, &version); err != nil { 375 log.Error("Failed to decode node value tracker", "id", id, "err", err) 376 return nv 377 } 378 if version != nvtVersion { 379 log.Error("Unknown NodeValueTracker version", "stored", version, "current", nvtVersion) 380 return nv 381 } 382 var nve nodeValueTrackerEncV1 383 if err := rlp.Decode(r, &nve); err != nil { 384 log.Error("Failed to decode node value tracker", "id", id, "err", err) 385 return nv 386 } 387 nv.rtStats = nve.RtStats 388 nv.lastRtStats = nve.RtStats 389 if int(nve.ServerBasketMapping) == vt.currentMapping { 390 nv.basket.basket = nve.ServerBasket 391 } else { 392 if nve.ServerBasketMapping >= uint(len(vt.mappings)) { 393 log.Error("Unknown request basket mapping", "stored", nve.ServerBasketMapping, "current", vt.currentMapping) 394 return nv 395 } 396 nv.basket.basket = nve.ServerBasket.convertMapping(vt.mappings[nve.ServerBasketMapping], vt.mappings[vt.currentMapping], vt.initRefBasket) 397 } 398 return nv 399 } 400 401 // saveNode saves a server node's value tracker to the database 402 func (vt *ValueTracker) saveNode(id enode.ID, nv *NodeValueTracker) { 403 recentRtStats := nv.rtStats 404 recentRtStats.SubStats(&nv.lastRtStats) 405 vt.rtStats.AddStats(&recentRtStats) 406 nv.lastRtStats = nv.rtStats 407 408 nve := nodeValueTrackerEncV1{ 409 RtStats: nv.rtStats, 410 ServerBasketMapping: uint(vt.currentMapping), 411 ServerBasket: nv.basket.basket, 412 } 413 enc1, err := rlp.EncodeToBytes(uint(nvtVersion)) 414 if err != nil { 415 log.Error("Failed to encode service value information", "id", id, "err", err) 416 return 417 } 418 enc2, err := rlp.EncodeToBytes(&nve) 419 if err != nil { 420 log.Error("Failed to encode service value information", "id", id, "err", err) 421 return 422 } 423 if err := vt.db.Put(append(vtNodeKey, id[:]...), append(enc1, enc2...)); err != nil { 424 log.Error("Failed to save service value information", "id", id, "err", err) 425 } 426 } 427 428 // UpdateCosts updates the node value tracker's request cost table 429 func (vt *ValueTracker) UpdateCosts(nv *NodeValueTracker, reqCosts []uint64) { 430 vt.lock.Lock() 431 defer vt.lock.Unlock() 432 433 nv.updateCosts(reqCosts, &vt.refBasket.reqValues, vt.refBasket.reqValueFactor(reqCosts)) 434 } 435 436 // RtStats returns the global response time distribution statistics 437 func (vt *ValueTracker) RtStats() ResponseTimeStats { 438 vt.lock.Lock() 439 defer vt.lock.Unlock() 440 441 vt.periodicUpdate() 442 return vt.rtStats 443 } 444 445 // periodicUpdate transfers individual node data to the global statistics, normalizes 446 // the reference basket and updates request values. The global state is also saved to 447 // the database with each update. 448 func (vt *ValueTracker) periodicUpdate() { 449 now := vt.clock.Now() 450 vt.statsExpLock.Lock() 451 vt.statsExpFactor = utils.ExpFactor(vt.statsExpirer.LogOffset(now)) 452 vt.statsExpLock.Unlock() 453 454 for _, nv := range vt.connected { 455 basket, rtStats := nv.transferStats(now, vt.transferRate) 456 vt.refBasket.add(basket) 457 vt.rtStats.AddStats(&rtStats) 458 } 459 vt.refBasket.normalize() 460 vt.refBasket.updateReqValues() 461 for _, nv := range vt.connected { 462 nv.updateCosts(nv.reqCosts, &vt.refBasket.reqValues, vt.refBasket.reqValueFactor(nv.reqCosts)) 463 } 464 vt.saveToDb() 465 } 466 467 type ServedRequest struct { 468 ReqType, Amount uint32 469 } 470 471 // Served adds a served request to the node's statistics. An actual request may be composed 472 // of one or more request types (service vector indices). 473 func (vt *ValueTracker) Served(nv *NodeValueTracker, reqs []ServedRequest, respTime time.Duration) { 474 vt.statsExpLock.RLock() 475 expFactor := vt.statsExpFactor 476 vt.statsExpLock.RUnlock() 477 478 nv.lock.Lock() 479 defer nv.lock.Unlock() 480 481 var value float64 482 for _, r := range reqs { 483 nv.basket.add(r.ReqType, r.Amount, nv.reqCosts[r.ReqType]*uint64(r.Amount), expFactor) 484 value += (*nv.reqValues)[r.ReqType] * float64(r.Amount) 485 } 486 nv.rtStats.Add(respTime, value, vt.statsExpFactor) 487 } 488 489 type RequestStatsItem struct { 490 Name string 491 ReqAmount, ReqValue float64 492 } 493 494 // RequestStats returns the current contents of the reference request basket, with 495 // request values meaning average per request rather than total. 496 func (vt *ValueTracker) RequestStats() []RequestStatsItem { 497 vt.statsExpLock.RLock() 498 expFactor := vt.statsExpFactor 499 vt.statsExpLock.RUnlock() 500 vt.lock.Lock() 501 defer vt.lock.Unlock() 502 503 vt.periodicUpdate() 504 res := make([]RequestStatsItem, len(vt.refBasket.basket.items)) 505 for i, item := range vt.refBasket.basket.items { 506 res[i].Name = vt.mappings[vt.currentMapping][i] 507 res[i].ReqAmount = expFactor.Value(float64(item.amount)/basketFactor, vt.refBasket.basket.exp) 508 res[i].ReqValue = vt.refBasket.reqValues[i] 509 } 510 return res 511 }