github.com/m3shine/gochain@v2.2.26+incompatible/les/serverpool.go (about) 1 // Copyright 2016 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 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/gochain-io/gochain/common" 31 "github.com/gochain-io/gochain/common/mclock" 32 "github.com/gochain-io/gochain/log" 33 "github.com/gochain-io/gochain/p2p" 34 "github.com/gochain-io/gochain/p2p/discover" 35 "github.com/gochain-io/gochain/p2p/discv5" 36 "github.com/gochain-io/gochain/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 common.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 common.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.GlobalTable().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 log.Error("Cannot encode server pool known queue", "err", err) 417 return 418 } 419 if err := pool.db.GlobalTable().Put(pool.dbKey, enc); err != nil { 420 log.Error("Cannot put server pool known queue", "err", err) 421 } 422 } 423 424 // removeEntry removes a pool entry when the entry count limit is reached. 425 // Note that it is called by the new/known queues from which the entry has already 426 // been removed so removing it from the queues is not necessary. 427 func (pool *serverPool) removeEntry(entry *poolEntry) { 428 pool.newSelect.remove((*discoveredEntry)(entry)) 429 pool.knownSelect.remove((*knownEntry)(entry)) 430 entry.removed = true 431 delete(pool.entries, entry.id) 432 } 433 434 // setRetryDial starts the timer which will enable dialing a certain node again 435 func (pool *serverPool) setRetryDial(entry *poolEntry) { 436 delay := longRetryDelay 437 if entry.shortRetry > 0 { 438 entry.shortRetry-- 439 delay = shortRetryDelay 440 } 441 delay += time.Duration(rand.Int63n(int64(delay) + 1)) 442 entry.delayedRetry = true 443 go func() { 444 select { 445 case <-pool.quit: 446 case <-time.After(delay): 447 select { 448 case <-pool.quit: 449 case pool.enableRetry <- entry: 450 } 451 } 452 }() 453 } 454 455 // updateCheckDial is called when an entry can potentially be dialed again. It updates 456 // its selection weights and checks if new dials can/should be made. 457 func (pool *serverPool) updateCheckDial(entry *poolEntry) { 458 pool.newSelect.update((*discoveredEntry)(entry)) 459 pool.knownSelect.update((*knownEntry)(entry)) 460 pool.checkDial() 461 } 462 463 // checkDial checks if new dials can/should be made. It tries to select servers both 464 // based on good statistics and recent discovery. 465 func (pool *serverPool) checkDial() { 466 fillWithKnownSelects := !pool.fastDiscover 467 for pool.knownSelected < targetKnownSelect { 468 entry := pool.knownSelect.choose() 469 if entry == nil { 470 fillWithKnownSelects = false 471 break 472 } 473 pool.dial((*poolEntry)(entry.(*knownEntry)), true) 474 } 475 for pool.knownSelected+pool.newSelected < targetServerCount { 476 entry := pool.newSelect.choose() 477 if entry == nil { 478 break 479 } 480 pool.dial((*poolEntry)(entry.(*discoveredEntry)), false) 481 } 482 if fillWithKnownSelects { 483 // no more newly discovered nodes to select and since fast discover period 484 // is over, we probably won't find more in the near future so select more 485 // known entries if possible 486 for pool.knownSelected < targetServerCount { 487 entry := pool.knownSelect.choose() 488 if entry == nil { 489 break 490 } 491 pool.dial((*poolEntry)(entry.(*knownEntry)), true) 492 } 493 } 494 } 495 496 // dial initiates a new connection 497 func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) { 498 if pool.server == nil || entry.state != psNotConnected { 499 return 500 } 501 entry.state = psDialed 502 entry.knownSelected = knownSelected 503 if knownSelected { 504 pool.knownSelected++ 505 } else { 506 pool.newSelected++ 507 } 508 addr := entry.addrSelect.choose().(*poolEntryAddress) 509 log.Debug("Dialing new peer", "lesaddr", entry.id.String()+"@"+addr.strKey(), "set", len(entry.addr), "known", knownSelected) 510 entry.dialed = addr 511 go func() { 512 pool.server.AddPeer(discover.NewNode(entry.id, addr.ip, addr.port, addr.port)) 513 select { 514 case <-pool.quit: 515 case <-time.After(dialTimeout): 516 select { 517 case <-pool.quit: 518 case pool.timeout <- entry: 519 } 520 } 521 }() 522 } 523 524 // checkDialTimeout checks if the node is still in dialed state and if so, resets it 525 // and adjusts connection statistics accordingly. 526 func (pool *serverPool) checkDialTimeout(entry *poolEntry) { 527 if entry.state != psDialed { 528 return 529 } 530 log.Debug("Dial timeout", "lesaddr", entry.id.String()+"@"+entry.dialed.strKey()) 531 entry.state = psNotConnected 532 if entry.knownSelected { 533 pool.knownSelected-- 534 } else { 535 pool.newSelected-- 536 } 537 entry.connectStats.add(0, 1) 538 entry.dialed.fails++ 539 pool.setRetryDial(entry) 540 } 541 542 const ( 543 psNotConnected = iota 544 psDialed 545 psConnected 546 psRegistered 547 ) 548 549 // poolEntry represents a server node and stores its current state and statistics. 550 type poolEntry struct { 551 peer *peer 552 id discover.NodeID 553 addr map[string]*poolEntryAddress 554 lastConnected, dialed *poolEntryAddress 555 addrSelect weightedRandomSelect 556 557 lastDiscovered mclock.AbsTime 558 known, knownSelected bool 559 connectStats, delayStats poolStats 560 responseStats, timeoutStats poolStats 561 state int 562 regTime mclock.AbsTime 563 queueIdx int 564 removed bool 565 566 delayedRetry bool 567 shortRetry int 568 } 569 570 func (e *poolEntry) EncodeRLP(w io.Writer) error { 571 return rlp.Encode(w, []interface{}{e.id, e.lastConnected.ip, e.lastConnected.port, e.lastConnected.fails, &e.connectStats, &e.delayStats, &e.responseStats, &e.timeoutStats}) 572 } 573 574 func (e *poolEntry) DecodeRLP(s *rlp.Stream) error { 575 var entry struct { 576 ID discover.NodeID 577 IP net.IP 578 Port uint16 579 Fails uint 580 CStat, DStat, RStat, TStat poolStats 581 } 582 if err := s.Decode(&entry); err != nil { 583 return err 584 } 585 addr := &poolEntryAddress{ip: entry.IP, port: entry.Port, fails: entry.Fails, lastSeen: mclock.Now()} 586 e.id = entry.ID 587 e.addr = make(map[string]*poolEntryAddress) 588 e.addr[addr.strKey()] = addr 589 e.addrSelect = *newWeightedRandomSelect() 590 e.addrSelect.update(addr) 591 e.lastConnected = addr 592 e.connectStats = entry.CStat 593 e.delayStats = entry.DStat 594 e.responseStats = entry.RStat 595 e.timeoutStats = entry.TStat 596 e.shortRetry = shortRetryCnt 597 e.known = true 598 return nil 599 } 600 601 // discoveredEntry implements wrsItem 602 type discoveredEntry poolEntry 603 604 // Weight calculates random selection weight for newly discovered entries 605 func (e *discoveredEntry) Weight() int64 { 606 if e.state != psNotConnected || e.delayedRetry { 607 return 0 608 } 609 t := time.Duration(mclock.Now() - e.lastDiscovered) 610 if t <= discoverExpireStart { 611 return 1000000000 612 } else { 613 return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst))) 614 } 615 } 616 617 // knownEntry implements wrsItem 618 type knownEntry poolEntry 619 620 // Weight calculates random selection weight for known entries 621 func (e *knownEntry) Weight() int64 { 622 if e.state != psNotConnected || !e.known || e.delayedRetry { 623 return 0 624 } 625 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)) 626 } 627 628 // poolEntryAddress is a separate object because currently it is necessary to remember 629 // multiple potential network addresses for a pool entry. This will be removed after 630 // the final implementation of v5 discovery which will retrieve signed and serial 631 // numbered advertisements, making it clear which IP/port is the latest one. 632 type poolEntryAddress struct { 633 ip net.IP 634 port uint16 635 lastSeen mclock.AbsTime // last time it was discovered, connected or loaded from db 636 fails uint // connection failures since last successful connection (persistent) 637 } 638 639 func (a *poolEntryAddress) Weight() int64 { 640 t := time.Duration(mclock.Now() - a.lastSeen) 641 return int64(1000000*math.Exp(-float64(t)/float64(discoverExpireConst)-float64(a.fails)*addrFailDropLn)) + 1 642 } 643 644 func (a *poolEntryAddress) strKey() string { 645 return a.ip.String() + ":" + strconv.Itoa(int(a.port)) 646 } 647 648 // poolStats implement statistics for a certain quantity with a long term average 649 // and a short term value which is adjusted exponentially with a factor of 650 // pstatRecentAdjust with each update and also returned exponentially to the 651 // average with the time constant pstatReturnToMeanTC 652 type poolStats struct { 653 sum, weight, avg, recent float64 654 lastRecalc mclock.AbsTime 655 } 656 657 // init initializes stats with a long term sum/update count pair retrieved from the database 658 func (s *poolStats) init(sum, weight float64) { 659 s.sum = sum 660 s.weight = weight 661 var avg float64 662 if weight > 0 { 663 avg = s.sum / weight 664 } 665 s.avg = avg 666 s.recent = avg 667 s.lastRecalc = mclock.Now() 668 } 669 670 // recalc recalculates recent value return-to-mean and long term average 671 func (s *poolStats) recalc() { 672 now := mclock.Now() 673 s.recent = s.avg + (s.recent-s.avg)*math.Exp(-float64(now-s.lastRecalc)/float64(pstatReturnToMeanTC)) 674 if s.sum == 0 { 675 s.avg = 0 676 } else { 677 if s.sum > s.weight*1e30 { 678 s.avg = 1e30 679 } else { 680 s.avg = s.sum / s.weight 681 } 682 } 683 s.lastRecalc = now 684 } 685 686 // add updates the stats with a new value 687 func (s *poolStats) add(value, weight float64) { 688 s.weight += weight 689 s.sum += value * weight 690 s.recalc() 691 } 692 693 // recentAvg returns the short-term adjusted average 694 func (s *poolStats) recentAvg() float64 { 695 s.recalc() 696 return s.recent 697 } 698 699 func (s *poolStats) EncodeRLP(w io.Writer) error { 700 return rlp.Encode(w, []interface{}{math.Float64bits(s.sum), math.Float64bits(s.weight)}) 701 } 702 703 func (s *poolStats) DecodeRLP(st *rlp.Stream) error { 704 var stats struct { 705 SumUint, WeightUint uint64 706 } 707 if err := st.Decode(&stats); err != nil { 708 return err 709 } 710 s.init(math.Float64frombits(stats.SumUint), math.Float64frombits(stats.WeightUint)) 711 return nil 712 } 713 714 // poolEntryQueue keeps track of its least recently accessed entries and removes 715 // them when the number of entries reaches the limit 716 type poolEntryQueue struct { 717 queue map[int]*poolEntry // known nodes indexed by their latest lastConnCnt value 718 newPtr, oldPtr, maxCnt int 719 removeFromPool func(*poolEntry) 720 } 721 722 // newPoolEntryQueue returns a new poolEntryQueue 723 func newPoolEntryQueue(maxCnt int, removeFromPool func(*poolEntry)) poolEntryQueue { 724 return poolEntryQueue{queue: make(map[int]*poolEntry), maxCnt: maxCnt, removeFromPool: removeFromPool} 725 } 726 727 // fetchOldest returns and removes the least recently accessed entry 728 func (q *poolEntryQueue) fetchOldest() *poolEntry { 729 if len(q.queue) == 0 { 730 return nil 731 } 732 for { 733 if e := q.queue[q.oldPtr]; e != nil { 734 delete(q.queue, q.oldPtr) 735 q.oldPtr++ 736 return e 737 } 738 q.oldPtr++ 739 } 740 } 741 742 // remove removes an entry from the queue 743 func (q *poolEntryQueue) remove(entry *poolEntry) { 744 if q.queue[entry.queueIdx] == entry { 745 delete(q.queue, entry.queueIdx) 746 } 747 } 748 749 // setLatest adds or updates a recently accessed entry. It also checks if an old entry 750 // needs to be removed and removes it from the parent pool too with a callback function. 751 func (q *poolEntryQueue) setLatest(entry *poolEntry) { 752 if q.queue[entry.queueIdx] == entry { 753 delete(q.queue, entry.queueIdx) 754 } else { 755 if len(q.queue) == q.maxCnt { 756 e := q.fetchOldest() 757 q.remove(e) 758 q.removeFromPool(e) 759 } 760 } 761 entry.queueIdx = q.newPtr 762 q.queue[entry.queueIdx] = entry 763 q.newPtr++ 764 }