github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/les/serverpool.go (about) 1 // Copyright 2016 The Spectrum Authors 2 // This file is part of the Spectrum library. 3 // 4 // The Spectrum 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 Spectrum 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 Spectrum library. If not, see <http://www.gnu.org/licenses/>. 16 17 // Package les implements the Light Ethereum Subprotocol. 18 package les 19 20 import ( 21 "fmt" 22 "io" 23 "math" 24 "math/rand" 25 "net" 26 "strconv" 27 "sync" 28 "time" 29 30 "github.com/SmartMeshFoundation/Spectrum/common/mclock" 31 "github.com/SmartMeshFoundation/Spectrum/ethdb" 32 "github.com/SmartMeshFoundation/Spectrum/log" 33 "github.com/SmartMeshFoundation/Spectrum/p2p" 34 "github.com/SmartMeshFoundation/Spectrum/p2p/discover" 35 "github.com/SmartMeshFoundation/Spectrum/p2p/discv5" 36 "github.com/SmartMeshFoundation/Spectrum/rlp" 37 ) 38 39 const ( 40 // After a connection has been ended or timed out, there is a waiting period 41 // before it can be selected for connection again. 42 // waiting period = base delay * (1 + random(1)) 43 // base delay = shortRetryDelay for the first shortRetryCnt times after a 44 // successful connection, after that longRetryDelay is applied 45 shortRetryCnt = 5 46 shortRetryDelay = time.Second * 5 47 longRetryDelay = time.Minute * 10 48 // maxNewEntries is the maximum number of newly discovered (never connected) nodes. 49 // If the limit is reached, the least recently discovered one is thrown out. 50 maxNewEntries = 1000 51 // maxKnownEntries is the maximum number of known (already connected) nodes. 52 // If the limit is reached, the least recently connected one is thrown out. 53 // (not that unlike new entries, known entries are persistent) 54 maxKnownEntries = 1000 55 // target for simultaneously connected servers 56 targetServerCount = 5 57 // target for servers selected from the known table 58 // (we leave room for trying new ones if there is any) 59 targetKnownSelect = 3 60 // after dialTimeout, consider the server unavailable and adjust statistics 61 dialTimeout = time.Second * 30 62 // targetConnTime is the minimum expected connection duration before a server 63 // drops a client without any specific reason 64 targetConnTime = time.Minute * 10 65 // new entry selection weight calculation based on most recent discovery time: 66 // unity until discoverExpireStart, then exponential decay with discoverExpireConst 67 discoverExpireStart = time.Minute * 20 68 discoverExpireConst = time.Minute * 20 69 // known entry selection weight is dropped by a factor of exp(-failDropLn) after 70 // each unsuccessful connection (restored after a successful one) 71 failDropLn = 0.1 72 // known node connection success and quality statistics have a long term average 73 // and a short term value which is adjusted exponentially with a factor of 74 // pstatRecentAdjust with each dial/connection and also returned exponentially 75 // to the average with the time constant pstatReturnToMeanTC 76 pstatRecentAdjust = 0.1 77 pstatReturnToMeanTC = time.Hour 78 // node address selection weight is dropped by a factor of exp(-addrFailDropLn) after 79 // each unsuccessful connection (restored after a successful one) 80 addrFailDropLn = math.Ln2 81 // responseScoreTC and delayScoreTC are exponential decay time constants for 82 // calculating selection chances from response times and block delay times 83 responseScoreTC = time.Millisecond * 100 84 delayScoreTC = time.Second * 5 85 timeoutPow = 10 86 // peerSelectMinWeight is added to calculated weights at request peer selection 87 // to give poorly performing peers a little chance of coming back 88 peerSelectMinWeight = 0.005 89 // initStatsWeight is used to initialize previously unknown peers with good 90 // statistics to give a chance to prove themselves 91 initStatsWeight = 1 92 ) 93 94 // serverPool implements a pool for storing and selecting newly discovered and already 95 // known light server nodes. It received discovered nodes, stores statistics about 96 // known nodes and takes care of always having enough good quality servers connected. 97 type serverPool struct { 98 db ethdb.Database 99 dbKey []byte 100 server *p2p.Server 101 quit chan struct{} 102 wg *sync.WaitGroup 103 connWg sync.WaitGroup 104 105 topic discv5.Topic 106 107 discSetPeriod chan time.Duration 108 discNodes chan *discv5.Node 109 discLookups chan bool 110 111 entries map[discover.NodeID]*poolEntry 112 lock sync.Mutex 113 timeout, enableRetry chan *poolEntry 114 adjustStats chan poolStatAdjust 115 116 knownQueue, newQueue poolEntryQueue 117 knownSelect, newSelect *weightedRandomSelect 118 knownSelected, newSelected int 119 fastDiscover bool 120 } 121 122 // newServerPool creates a new serverPool instance 123 func newServerPool(db ethdb.Database, quit chan struct{}, wg *sync.WaitGroup) *serverPool { 124 pool := &serverPool{ 125 db: db, 126 quit: quit, 127 wg: wg, 128 entries: make(map[discover.NodeID]*poolEntry), 129 timeout: make(chan *poolEntry, 1), 130 adjustStats: make(chan poolStatAdjust, 100), 131 enableRetry: make(chan *poolEntry, 1), 132 knownSelect: newWeightedRandomSelect(), 133 newSelect: newWeightedRandomSelect(), 134 fastDiscover: true, 135 } 136 pool.knownQueue = newPoolEntryQueue(maxKnownEntries, pool.removeEntry) 137 pool.newQueue = newPoolEntryQueue(maxNewEntries, pool.removeEntry) 138 return pool 139 } 140 141 func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) { 142 pool.server = server 143 pool.topic = topic 144 pool.dbKey = append([]byte("serverPool/"), []byte(topic)...) 145 pool.wg.Add(1) 146 pool.loadNodes() 147 148 if pool.server.DiscV5 != nil { 149 pool.discSetPeriod = make(chan time.Duration, 1) 150 pool.discNodes = make(chan *discv5.Node, 100) 151 pool.discLookups = make(chan bool, 100) 152 go pool.server.DiscV5.SearchTopic(pool.topic, pool.discSetPeriod, pool.discNodes, pool.discLookups) 153 } 154 155 go pool.eventLoop() 156 pool.checkDial() 157 } 158 159 // connect should be called upon any incoming connection. If the connection has been 160 // dialed by the server pool recently, the appropriate pool entry is returned. 161 // Otherwise, the connection should be rejected. 162 // Note that whenever a connection has been accepted and a pool entry has been returned, 163 // disconnect should also always be called. 164 func (pool *serverPool) connect(p *peer, ip net.IP, port uint16) *poolEntry { 165 pool.lock.Lock() 166 defer pool.lock.Unlock() 167 entry := pool.entries[p.ID()] 168 if entry == nil { 169 entry = pool.findOrNewNode(p.ID(), ip, port) 170 } 171 p.Log().Debug("Connecting to new peer", "state", entry.state) 172 if entry.state == psConnected || entry.state == psRegistered { 173 return nil 174 } 175 pool.connWg.Add(1) 176 entry.peer = p 177 entry.state = psConnected 178 addr := &poolEntryAddress{ 179 ip: ip, 180 port: port, 181 lastSeen: mclock.Now(), 182 } 183 entry.lastConnected = addr 184 entry.addr = make(map[string]*poolEntryAddress) 185 entry.addr[addr.strKey()] = addr 186 entry.addrSelect = *newWeightedRandomSelect() 187 entry.addrSelect.update(addr) 188 return entry 189 } 190 191 // registered should be called after a successful handshake 192 func (pool *serverPool) registered(entry *poolEntry) { 193 log.Debug("Registered new entry", "enode", entry.id) 194 pool.lock.Lock() 195 defer pool.lock.Unlock() 196 197 entry.state = psRegistered 198 entry.regTime = mclock.Now() 199 if !entry.known { 200 pool.newQueue.remove(entry) 201 entry.known = true 202 } 203 pool.knownQueue.setLatest(entry) 204 entry.shortRetry = shortRetryCnt 205 } 206 207 // disconnect should be called when ending a connection. Service quality statistics 208 // can be updated optionally (not updated if no registration happened, in this case 209 // only connection statistics are updated, just like in case of timeout) 210 func (pool *serverPool) disconnect(entry *poolEntry) { 211 log.Debug("Disconnected old entry", "enode", entry.id) 212 pool.lock.Lock() 213 defer pool.lock.Unlock() 214 215 if entry.state == psRegistered { 216 connTime := mclock.Now() - entry.regTime 217 connAdjust := float64(connTime) / float64(targetConnTime) 218 if connAdjust > 1 { 219 connAdjust = 1 220 } 221 stopped := false 222 select { 223 case <-pool.quit: 224 stopped = true 225 default: 226 } 227 if stopped { 228 entry.connectStats.add(1, connAdjust) 229 } else { 230 entry.connectStats.add(connAdjust, 1) 231 } 232 } 233 234 entry.state = psNotConnected 235 if entry.knownSelected { 236 pool.knownSelected-- 237 } else { 238 pool.newSelected-- 239 } 240 pool.setRetryDial(entry) 241 pool.connWg.Done() 242 } 243 244 const ( 245 pseBlockDelay = iota 246 pseResponseTime 247 pseResponseTimeout 248 ) 249 250 // poolStatAdjust records are sent to adjust peer block delay/response time statistics 251 type poolStatAdjust struct { 252 adjustType int 253 entry *poolEntry 254 time time.Duration 255 } 256 257 // adjustBlockDelay adjusts the block announce delay statistics of a node 258 func (pool *serverPool) adjustBlockDelay(entry *poolEntry, time time.Duration) { 259 if entry == nil { 260 return 261 } 262 pool.adjustStats <- poolStatAdjust{pseBlockDelay, entry, time} 263 } 264 265 // adjustResponseTime adjusts the request response time statistics of a node 266 func (pool *serverPool) adjustResponseTime(entry *poolEntry, time time.Duration, timeout bool) { 267 if entry == nil { 268 return 269 } 270 if timeout { 271 pool.adjustStats <- poolStatAdjust{pseResponseTimeout, entry, time} 272 } else { 273 pool.adjustStats <- poolStatAdjust{pseResponseTime, entry, time} 274 } 275 } 276 277 // eventLoop handles pool events and mutex locking for all internal functions 278 func (pool *serverPool) eventLoop() { 279 lookupCnt := 0 280 var convTime mclock.AbsTime 281 if pool.discSetPeriod != nil { 282 pool.discSetPeriod <- time.Millisecond * 100 283 } 284 for { 285 select { 286 case entry := <-pool.timeout: 287 pool.lock.Lock() 288 if !entry.removed { 289 pool.checkDialTimeout(entry) 290 } 291 pool.lock.Unlock() 292 293 case entry := <-pool.enableRetry: 294 pool.lock.Lock() 295 if !entry.removed { 296 entry.delayedRetry = false 297 pool.updateCheckDial(entry) 298 } 299 pool.lock.Unlock() 300 301 case adj := <-pool.adjustStats: 302 pool.lock.Lock() 303 switch adj.adjustType { 304 case pseBlockDelay: 305 adj.entry.delayStats.add(float64(adj.time), 1) 306 case pseResponseTime: 307 adj.entry.responseStats.add(float64(adj.time), 1) 308 adj.entry.timeoutStats.add(0, 1) 309 case pseResponseTimeout: 310 adj.entry.timeoutStats.add(1, 1) 311 } 312 pool.lock.Unlock() 313 314 case node := <-pool.discNodes: 315 pool.lock.Lock() 316 entry := pool.findOrNewNode(discover.NodeID(node.ID), node.IP, node.TCP) 317 pool.updateCheckDial(entry) 318 pool.lock.Unlock() 319 320 case conv := <-pool.discLookups: 321 if conv { 322 if lookupCnt == 0 { 323 convTime = mclock.Now() 324 } 325 lookupCnt++ 326 if pool.fastDiscover && (lookupCnt == 50 || time.Duration(mclock.Now()-convTime) > time.Minute) { 327 pool.fastDiscover = false 328 if pool.discSetPeriod != nil { 329 pool.discSetPeriod <- time.Minute 330 } 331 } 332 } 333 334 case <-pool.quit: 335 if pool.discSetPeriod != nil { 336 close(pool.discSetPeriod) 337 } 338 pool.connWg.Wait() 339 pool.saveNodes() 340 pool.wg.Done() 341 return 342 343 } 344 } 345 } 346 347 func (pool *serverPool) findOrNewNode(id discover.NodeID, ip net.IP, port uint16) *poolEntry { 348 now := mclock.Now() 349 entry := pool.entries[id] 350 if entry == nil { 351 log.Debug("Discovered new entry", "id", id) 352 entry = &poolEntry{ 353 id: id, 354 addr: make(map[string]*poolEntryAddress), 355 addrSelect: *newWeightedRandomSelect(), 356 shortRetry: shortRetryCnt, 357 } 358 pool.entries[id] = entry 359 // initialize previously unknown peers with good statistics to give a chance to prove themselves 360 entry.connectStats.add(1, initStatsWeight) 361 entry.delayStats.add(0, initStatsWeight) 362 entry.responseStats.add(0, initStatsWeight) 363 entry.timeoutStats.add(0, initStatsWeight) 364 } 365 entry.lastDiscovered = now 366 addr := &poolEntryAddress{ 367 ip: ip, 368 port: port, 369 } 370 if a, ok := entry.addr[addr.strKey()]; ok { 371 addr = a 372 } else { 373 entry.addr[addr.strKey()] = addr 374 } 375 addr.lastSeen = now 376 entry.addrSelect.update(addr) 377 if !entry.known { 378 pool.newQueue.setLatest(entry) 379 } 380 return entry 381 } 382 383 // loadNodes loads known nodes and their statistics from the database 384 func (pool *serverPool) loadNodes() { 385 enc, err := pool.db.Get(pool.dbKey) 386 if err != nil { 387 return 388 } 389 var list []*poolEntry 390 err = rlp.DecodeBytes(enc, &list) 391 if err != nil { 392 log.Debug("Failed to decode node list", "err", err) 393 return 394 } 395 for _, e := range list { 396 log.Debug("Loaded server stats", "id", e.id, "fails", e.lastConnected.fails, 397 "conn", fmt.Sprintf("%v/%v", e.connectStats.avg, e.connectStats.weight), 398 "delay", fmt.Sprintf("%v/%v", time.Duration(e.delayStats.avg), e.delayStats.weight), 399 "response", fmt.Sprintf("%v/%v", time.Duration(e.responseStats.avg), e.responseStats.weight), 400 "timeout", fmt.Sprintf("%v/%v", e.timeoutStats.avg, e.timeoutStats.weight)) 401 pool.entries[e.id] = e 402 pool.knownQueue.setLatest(e) 403 pool.knownSelect.update((*knownEntry)(e)) 404 } 405 } 406 407 // saveNodes saves known nodes and their statistics into the database. Nodes are 408 // ordered from least to most recently connected. 409 func (pool *serverPool) saveNodes() { 410 list := make([]*poolEntry, len(pool.knownQueue.queue)) 411 for i := range list { 412 list[i] = pool.knownQueue.fetchOldest() 413 } 414 enc, err := rlp.EncodeToBytes(list) 415 if err == nil { 416 pool.db.Put(pool.dbKey, enc) 417 } 418 } 419 420 // removeEntry removes a pool entry when the entry count limit is reached. 421 // Note that it is called by the new/known queues from which the entry has already 422 // been removed so removing it from the queues is not necessary. 423 func (pool *serverPool) removeEntry(entry *poolEntry) { 424 pool.newSelect.remove((*discoveredEntry)(entry)) 425 pool.knownSelect.remove((*knownEntry)(entry)) 426 entry.removed = true 427 delete(pool.entries, entry.id) 428 } 429 430 // setRetryDial starts the timer which will enable dialing a certain node again 431 func (pool *serverPool) setRetryDial(entry *poolEntry) { 432 delay := longRetryDelay 433 if entry.shortRetry > 0 { 434 entry.shortRetry-- 435 delay = shortRetryDelay 436 } 437 delay += time.Duration(rand.Int63n(int64(delay) + 1)) 438 entry.delayedRetry = true 439 go func() { 440 select { 441 case <-pool.quit: 442 case <-time.After(delay): 443 select { 444 case <-pool.quit: 445 case pool.enableRetry <- entry: 446 } 447 } 448 }() 449 } 450 451 // updateCheckDial is called when an entry can potentially be dialed again. It updates 452 // its selection weights and checks if new dials can/should be made. 453 func (pool *serverPool) updateCheckDial(entry *poolEntry) { 454 pool.newSelect.update((*discoveredEntry)(entry)) 455 pool.knownSelect.update((*knownEntry)(entry)) 456 pool.checkDial() 457 } 458 459 // checkDial checks if new dials can/should be made. It tries to select servers both 460 // based on good statistics and recent discovery. 461 func (pool *serverPool) checkDial() { 462 fillWithKnownSelects := !pool.fastDiscover 463 for pool.knownSelected < targetKnownSelect { 464 entry := pool.knownSelect.choose() 465 if entry == nil { 466 fillWithKnownSelects = false 467 break 468 } 469 pool.dial((*poolEntry)(entry.(*knownEntry)), true) 470 } 471 for pool.knownSelected+pool.newSelected < targetServerCount { 472 entry := pool.newSelect.choose() 473 if entry == nil { 474 break 475 } 476 pool.dial((*poolEntry)(entry.(*discoveredEntry)), false) 477 } 478 if fillWithKnownSelects { 479 // no more newly discovered nodes to select and since fast discover period 480 // is over, we probably won't find more in the near future so select more 481 // known entries if possible 482 for pool.knownSelected < targetServerCount { 483 entry := pool.knownSelect.choose() 484 if entry == nil { 485 break 486 } 487 pool.dial((*poolEntry)(entry.(*knownEntry)), true) 488 } 489 } 490 } 491 492 // dial initiates a new connection 493 func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) { 494 if pool.server == nil || entry.state != psNotConnected { 495 return 496 } 497 entry.state = psDialed 498 entry.knownSelected = knownSelected 499 if knownSelected { 500 pool.knownSelected++ 501 } else { 502 pool.newSelected++ 503 } 504 addr := entry.addrSelect.choose().(*poolEntryAddress) 505 log.Debug("Dialing new peer", "lesaddr", entry.id.String()+"@"+addr.strKey(), "set", len(entry.addr), "known", knownSelected) 506 entry.dialed = addr 507 go func() { 508 pool.server.AddPeer(discover.NewNode(entry.id, addr.ip, addr.port, addr.port)) 509 select { 510 case <-pool.quit: 511 case <-time.After(dialTimeout): 512 select { 513 case <-pool.quit: 514 case pool.timeout <- entry: 515 } 516 } 517 }() 518 } 519 520 // checkDialTimeout checks if the node is still in dialed state and if so, resets it 521 // and adjusts connection statistics accordingly. 522 func (pool *serverPool) checkDialTimeout(entry *poolEntry) { 523 if entry.state != psDialed { 524 return 525 } 526 log.Debug("Dial timeout", "lesaddr", entry.id.String()+"@"+entry.dialed.strKey()) 527 entry.state = psNotConnected 528 if entry.knownSelected { 529 pool.knownSelected-- 530 } else { 531 pool.newSelected-- 532 } 533 entry.connectStats.add(0, 1) 534 entry.dialed.fails++ 535 pool.setRetryDial(entry) 536 } 537 538 const ( 539 psNotConnected = iota 540 psDialed 541 psConnected 542 psRegistered 543 ) 544 545 // poolEntry represents a server node and stores its current state and statistics. 546 type poolEntry struct { 547 peer *peer 548 id discover.NodeID 549 addr map[string]*poolEntryAddress 550 lastConnected, dialed *poolEntryAddress 551 addrSelect weightedRandomSelect 552 553 lastDiscovered mclock.AbsTime 554 known, knownSelected bool 555 connectStats, delayStats poolStats 556 responseStats, timeoutStats poolStats 557 state int 558 regTime mclock.AbsTime 559 queueIdx int 560 removed bool 561 562 delayedRetry bool 563 shortRetry int 564 } 565 566 func (e *poolEntry) EncodeRLP(w io.Writer) error { 567 return rlp.Encode(w, []interface{}{e.id, e.lastConnected.ip, e.lastConnected.port, e.lastConnected.fails, &e.connectStats, &e.delayStats, &e.responseStats, &e.timeoutStats}) 568 } 569 570 func (e *poolEntry) DecodeRLP(s *rlp.Stream) error { 571 var entry struct { 572 ID discover.NodeID 573 IP net.IP 574 Port uint16 575 Fails uint 576 CStat, DStat, RStat, TStat poolStats 577 } 578 if err := s.Decode(&entry); err != nil { 579 return err 580 } 581 addr := &poolEntryAddress{ip: entry.IP, port: entry.Port, fails: entry.Fails, lastSeen: mclock.Now()} 582 e.id = entry.ID 583 e.addr = make(map[string]*poolEntryAddress) 584 e.addr[addr.strKey()] = addr 585 e.addrSelect = *newWeightedRandomSelect() 586 e.addrSelect.update(addr) 587 e.lastConnected = addr 588 e.connectStats = entry.CStat 589 e.delayStats = entry.DStat 590 e.responseStats = entry.RStat 591 e.timeoutStats = entry.TStat 592 e.shortRetry = shortRetryCnt 593 e.known = true 594 return nil 595 } 596 597 // discoveredEntry implements wrsItem 598 type discoveredEntry poolEntry 599 600 // Weight calculates random selection weight for newly discovered entries 601 func (e *discoveredEntry) Weight() int64 { 602 if e.state != psNotConnected || e.delayedRetry { 603 return 0 604 } 605 t := time.Duration(mclock.Now() - e.lastDiscovered) 606 if t <= discoverExpireStart { 607 return 1000000000 608 } else { 609 return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst))) 610 } 611 } 612 613 // knownEntry implements wrsItem 614 type knownEntry poolEntry 615 616 // Weight calculates random selection weight for known entries 617 func (e *knownEntry) Weight() int64 { 618 if e.state != psNotConnected || !e.known || e.delayedRetry { 619 return 0 620 } 621 return int64(1000000000 * e.connectStats.recentAvg() * math.Exp(-float64(e.lastConnected.fails)*failDropLn-e.responseStats.recentAvg()/float64(responseScoreTC)-e.delayStats.recentAvg()/float64(delayScoreTC)) * math.Pow((1-e.timeoutStats.recentAvg()), timeoutPow)) 622 } 623 624 // poolEntryAddress is a separate object because currently it is necessary to remember 625 // multiple potential network addresses for a pool entry. This will be removed after 626 // the final implementation of v5 discovery which will retrieve signed and serial 627 // numbered advertisements, making it clear which IP/port is the latest one. 628 type poolEntryAddress struct { 629 ip net.IP 630 port uint16 631 lastSeen mclock.AbsTime // last time it was discovered, connected or loaded from db 632 fails uint // connection failures since last successful connection (persistent) 633 } 634 635 func (a *poolEntryAddress) Weight() int64 { 636 t := time.Duration(mclock.Now() - a.lastSeen) 637 return int64(1000000*math.Exp(-float64(t)/float64(discoverExpireConst)-float64(a.fails)*addrFailDropLn)) + 1 638 } 639 640 func (a *poolEntryAddress) strKey() string { 641 return a.ip.String() + ":" + strconv.Itoa(int(a.port)) 642 } 643 644 // poolStats implement statistics for a certain quantity with a long term average 645 // and a short term value which is adjusted exponentially with a factor of 646 // pstatRecentAdjust with each update and also returned exponentially to the 647 // average with the time constant pstatReturnToMeanTC 648 type poolStats struct { 649 sum, weight, avg, recent float64 650 lastRecalc mclock.AbsTime 651 } 652 653 // init initializes stats with a long term sum/update count pair retrieved from the database 654 func (s *poolStats) init(sum, weight float64) { 655 s.sum = sum 656 s.weight = weight 657 var avg float64 658 if weight > 0 { 659 avg = s.sum / weight 660 } 661 s.avg = avg 662 s.recent = avg 663 s.lastRecalc = mclock.Now() 664 } 665 666 // recalc recalculates recent value return-to-mean and long term average 667 func (s *poolStats) recalc() { 668 now := mclock.Now() 669 s.recent = s.avg + (s.recent-s.avg)*math.Exp(-float64(now-s.lastRecalc)/float64(pstatReturnToMeanTC)) 670 if s.sum == 0 { 671 s.avg = 0 672 } else { 673 if s.sum > s.weight*1e30 { 674 s.avg = 1e30 675 } else { 676 s.avg = s.sum / s.weight 677 } 678 } 679 s.lastRecalc = now 680 } 681 682 // add updates the stats with a new value 683 func (s *poolStats) add(value, weight float64) { 684 s.weight += weight 685 s.sum += value * weight 686 s.recalc() 687 } 688 689 // recentAvg returns the short-term adjusted average 690 func (s *poolStats) recentAvg() float64 { 691 s.recalc() 692 return s.recent 693 } 694 695 func (s *poolStats) EncodeRLP(w io.Writer) error { 696 return rlp.Encode(w, []interface{}{math.Float64bits(s.sum), math.Float64bits(s.weight)}) 697 } 698 699 func (s *poolStats) DecodeRLP(st *rlp.Stream) error { 700 var stats struct { 701 SumUint, WeightUint uint64 702 } 703 if err := st.Decode(&stats); err != nil { 704 return err 705 } 706 s.init(math.Float64frombits(stats.SumUint), math.Float64frombits(stats.WeightUint)) 707 return nil 708 } 709 710 // poolEntryQueue keeps track of its least recently accessed entries and removes 711 // them when the number of entries reaches the limit 712 type poolEntryQueue struct { 713 queue map[int]*poolEntry // known nodes indexed by their latest lastConnCnt value 714 newPtr, oldPtr, maxCnt int 715 removeFromPool func(*poolEntry) 716 } 717 718 // newPoolEntryQueue returns a new poolEntryQueue 719 func newPoolEntryQueue(maxCnt int, removeFromPool func(*poolEntry)) poolEntryQueue { 720 return poolEntryQueue{queue: make(map[int]*poolEntry), maxCnt: maxCnt, removeFromPool: removeFromPool} 721 } 722 723 // fetchOldest returns and removes the least recently accessed entry 724 func (q *poolEntryQueue) fetchOldest() *poolEntry { 725 if len(q.queue) == 0 { 726 return nil 727 } 728 for { 729 if e := q.queue[q.oldPtr]; e != nil { 730 delete(q.queue, q.oldPtr) 731 q.oldPtr++ 732 return e 733 } 734 q.oldPtr++ 735 } 736 } 737 738 // remove removes an entry from the queue 739 func (q *poolEntryQueue) remove(entry *poolEntry) { 740 if q.queue[entry.queueIdx] == entry { 741 delete(q.queue, entry.queueIdx) 742 } 743 } 744 745 // setLatest adds or updates a recently accessed entry. It also checks if an old entry 746 // needs to be removed and removes it from the parent pool too with a callback function. 747 func (q *poolEntryQueue) setLatest(entry *poolEntry) { 748 if q.queue[entry.queueIdx] == entry { 749 delete(q.queue, entry.queueIdx) 750 } else { 751 if len(q.queue) == q.maxCnt { 752 e := q.fetchOldest() 753 q.remove(e) 754 q.removeFromPool(e) 755 } 756 } 757 entry.queueIdx = q.newPtr 758 q.queue[entry.queueIdx] = entry 759 q.newPtr++ 760 }