github.com/n1ghtfa1l/go-vnt@v0.6.4-alpha.6/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 VNT Subprotocol. 18 package les 19 20 import ( 21 "context" 22 "fmt" 23 "io" 24 "math" 25 "math/rand" 26 "net" 27 "strconv" 28 "sync" 29 "time" 30 31 libp2p "github.com/libp2p/go-libp2p-peer" 32 "github.com/vntchain/go-vnt/common/mclock" 33 "github.com/vntchain/go-vnt/log" 34 "github.com/vntchain/go-vnt/rlp" 35 "github.com/vntchain/go-vnt/vntdb" 36 "github.com/vntchain/go-vnt/vntp2p" 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 pstatReturnToMeanTC = time.Hour 77 // node address selection weight is dropped by a factor of exp(-addrFailDropLn) after 78 // each unsuccessful connection (restored after a successful one) 79 addrFailDropLn = math.Ln2 80 // responseScoreTC and delayScoreTC are exponential decay time constants for 81 // calculating selection chances from response times and block delay times 82 responseScoreTC = time.Millisecond * 100 83 delayScoreTC = time.Second * 5 84 timeoutPow = 10 85 // initStatsWeight is used to initialize previously unknown peers with good 86 // statistics to give a chance to prove themselves 87 initStatsWeight = 1 88 ) 89 90 // serverPool implements a pool for storing and selecting newly discovered and already 91 // known light server nodes. It received discovered nodes, stores statistics about 92 // known nodes and takes care of always having enough good quality servers connected. 93 type serverPool struct { 94 db vntdb.Database 95 dbKey []byte 96 server *vntp2p.Server 97 quit chan struct{} 98 wg *sync.WaitGroup 99 connWg sync.WaitGroup 100 101 // topic discv5.Topic 102 103 discSetPeriod chan time.Duration 104 // discNodes chan *discv5.Node 105 discLookups chan bool 106 107 entries map[libp2p.ID]*poolEntry 108 lock sync.Mutex 109 timeout, enableRetry chan *poolEntry 110 adjustStats chan poolStatAdjust 111 112 knownQueue, newQueue poolEntryQueue 113 knownSelect, newSelect *weightedRandomSelect 114 knownSelected, newSelected int 115 fastDiscover bool 116 } 117 118 // newServerPool creates a new serverPool instance 119 func newServerPool(db vntdb.Database, quit chan struct{}, wg *sync.WaitGroup) *serverPool { 120 pool := &serverPool{ 121 db: db, 122 quit: quit, 123 wg: wg, 124 entries: make(map[libp2p.ID]*poolEntry), 125 timeout: make(chan *poolEntry, 1), 126 adjustStats: make(chan poolStatAdjust, 100), 127 enableRetry: make(chan *poolEntry, 1), 128 knownSelect: newWeightedRandomSelect(), 129 newSelect: newWeightedRandomSelect(), 130 fastDiscover: true, 131 } 132 pool.knownQueue = newPoolEntryQueue(maxKnownEntries, pool.removeEntry) 133 pool.newQueue = newPoolEntryQueue(maxNewEntries, pool.removeEntry) 134 return pool 135 } 136 137 // func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) { 138 func (pool *serverPool) start(server *vntp2p.Server) { 139 pool.server = server 140 141 // pool.topic = topic 142 // pool.dbKey = append([]byte("serverPool/"), []byte(topic)...) 143 144 pool.dbKey = append([]byte("serverPool/")) 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.RemoteID()] 168 if entry == nil { 169 entry = pool.findOrNewNode(p.RemoteID(), 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", "vnode", 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", "vnode", 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 // case node := <-pool.discNodes: 314 // pool.lock.Lock() 315 // entry := pool.findOrNewNode(vntp2p.NodeID(node.ID), node.IP, node.TCP) 316 // pool.updateCheckDial(entry) 317 // pool.lock.Unlock() 318 319 case conv := <-pool.discLookups: 320 if conv { 321 if lookupCnt == 0 { 322 convTime = mclock.Now() 323 } 324 lookupCnt++ 325 if pool.fastDiscover && (lookupCnt == 50 || time.Duration(mclock.Now()-convTime) > time.Minute) { 326 pool.fastDiscover = false 327 if pool.discSetPeriod != nil { 328 pool.discSetPeriod <- time.Minute 329 } 330 } 331 } 332 333 case <-pool.quit: 334 if pool.discSetPeriod != nil { 335 close(pool.discSetPeriod) 336 } 337 pool.connWg.Wait() 338 pool.saveNodes() 339 pool.wg.Done() 340 return 341 342 } 343 } 344 } 345 346 func (pool *serverPool) findOrNewNode(id libp2p.ID, ip net.IP, port uint16) *poolEntry { 347 now := mclock.Now() 348 entry := pool.entries[id] 349 if entry == nil { 350 log.Debug("Discovered new entry", "id", id) 351 entry = &poolEntry{ 352 id: id, 353 addr: make(map[string]*poolEntryAddress), 354 addrSelect: *newWeightedRandomSelect(), 355 shortRetry: shortRetryCnt, 356 } 357 pool.entries[id] = entry 358 // initialize previously unknown peers with good statistics to give a chance to prove themselves 359 entry.connectStats.add(1, initStatsWeight) 360 entry.delayStats.add(0, initStatsWeight) 361 entry.responseStats.add(0, initStatsWeight) 362 entry.timeoutStats.add(0, initStatsWeight) 363 } 364 entry.lastDiscovered = now 365 addr := &poolEntryAddress{ 366 ip: ip, 367 port: port, 368 } 369 if a, ok := entry.addr[addr.strKey()]; ok { 370 addr = a 371 } else { 372 entry.addr[addr.strKey()] = addr 373 } 374 addr.lastSeen = now 375 entry.addrSelect.update(addr) 376 if !entry.known { 377 pool.newQueue.setLatest(entry) 378 } 379 return entry 380 } 381 382 // loadNodes loads known nodes and their statistics from the database 383 func (pool *serverPool) loadNodes() { 384 enc, err := pool.db.Get(pool.dbKey) 385 if err != nil { 386 return 387 } 388 var list []*poolEntry 389 err = rlp.DecodeBytes(enc, &list) 390 if err != nil { 391 log.Debug("Failed to decode node list", "err", err) 392 return 393 } 394 for _, e := range list { 395 log.Debug("Loaded server stats", "id", e.id, "fails", e.lastConnected.fails, 396 "conn", fmt.Sprintf("%v/%v", e.connectStats.avg, e.connectStats.weight), 397 "delay", fmt.Sprintf("%v/%v", time.Duration(e.delayStats.avg), e.delayStats.weight), 398 "response", fmt.Sprintf("%v/%v", time.Duration(e.responseStats.avg), e.responseStats.weight), 399 "timeout", fmt.Sprintf("%v/%v", e.timeoutStats.avg, e.timeoutStats.weight)) 400 pool.entries[e.id] = e 401 pool.knownQueue.setLatest(e) 402 pool.knownSelect.update((*knownEntry)(e)) 403 } 404 } 405 406 // saveNodes saves known nodes and their statistics into the database. Nodes are 407 // ordered from least to most recently connected. 408 func (pool *serverPool) saveNodes() { 409 list := make([]*poolEntry, len(pool.knownQueue.queue)) 410 for i := range list { 411 list[i] = pool.knownQueue.fetchOldest() 412 } 413 enc, err := rlp.EncodeToBytes(list) 414 if err == nil { 415 pool.db.Put(pool.dbKey, enc) 416 } 417 } 418 419 // removeEntry removes a pool entry when the entry count limit is reached. 420 // Note that it is called by the new/known queues from which the entry has already 421 // been removed so removing it from the queues is not necessary. 422 func (pool *serverPool) removeEntry(entry *poolEntry) { 423 pool.newSelect.remove((*discoveredEntry)(entry)) 424 pool.knownSelect.remove((*knownEntry)(entry)) 425 entry.removed = true 426 delete(pool.entries, entry.id) 427 } 428 429 // setRetryDial starts the timer which will enable dialing a certain node again 430 func (pool *serverPool) setRetryDial(entry *poolEntry) { 431 delay := longRetryDelay 432 if entry.shortRetry > 0 { 433 entry.shortRetry-- 434 delay = shortRetryDelay 435 } 436 delay += time.Duration(rand.Int63n(int64(delay) + 1)) 437 entry.delayedRetry = true 438 go func() { 439 select { 440 case <-pool.quit: 441 case <-time.After(delay): 442 select { 443 case <-pool.quit: 444 case pool.enableRetry <- entry: 445 } 446 } 447 }() 448 } 449 450 // updateCheckDial is called when an entry can potentially be dialed again. It updates 451 // its selection weights and checks if new dials can/should be made. 452 func (pool *serverPool) updateCheckDial(entry *poolEntry) { 453 pool.newSelect.update((*discoveredEntry)(entry)) 454 pool.knownSelect.update((*knownEntry)(entry)) 455 pool.checkDial() 456 } 457 458 // checkDial checks if new dials can/should be made. It tries to select servers both 459 // based on good statistics and recent discovery. 460 func (pool *serverPool) checkDial() { 461 fillWithKnownSelects := !pool.fastDiscover 462 for pool.knownSelected < targetKnownSelect { 463 entry := pool.knownSelect.choose() 464 if entry == nil { 465 fillWithKnownSelects = false 466 break 467 } 468 pool.dial((*poolEntry)(entry.(*knownEntry)), true) 469 } 470 for pool.knownSelected+pool.newSelected < targetServerCount { 471 entry := pool.newSelect.choose() 472 if entry == nil { 473 break 474 } 475 pool.dial((*poolEntry)(entry.(*discoveredEntry)), false) 476 } 477 if fillWithKnownSelects { 478 // no more newly discovered nodes to select and since fast discover period 479 // is over, we probably won't find more in the near future so select more 480 // known entries if possible 481 for pool.knownSelected < targetServerCount { 482 entry := pool.knownSelect.choose() 483 if entry == nil { 484 break 485 } 486 pool.dial((*poolEntry)(entry.(*knownEntry)), true) 487 } 488 } 489 } 490 491 // dial initiates a new connection 492 func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) { 493 if pool.server == nil || entry.state != psNotConnected { 494 return 495 } 496 ctx := context.Background() 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(ctx, vntp2p.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 libp2p.ID 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 libp2p.ID 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 } 609 return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst))) 610 } 611 612 // knownEntry implements wrsItem 613 type knownEntry poolEntry 614 615 // Weight calculates random selection weight for known entries 616 func (e *knownEntry) Weight() int64 { 617 if e.state != psNotConnected || !e.known || e.delayedRetry { 618 return 0 619 } 620 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)) 621 } 622 623 // poolEntryAddress is a separate object because currently it is necessary to remember 624 // multiple potential network addresses for a pool entry. This will be removed after 625 // the final implementation of v5 discovery which will retrieve signed and serial 626 // numbered advertisements, making it clear which IP/port is the latest one. 627 type poolEntryAddress struct { 628 ip net.IP 629 port uint16 630 lastSeen mclock.AbsTime // last time it was discovered, connected or loaded from db 631 fails uint // connection failures since last successful connection (persistent) 632 } 633 634 func (a *poolEntryAddress) Weight() int64 { 635 t := time.Duration(mclock.Now() - a.lastSeen) 636 return int64(1000000*math.Exp(-float64(t)/float64(discoverExpireConst)-float64(a.fails)*addrFailDropLn)) + 1 637 } 638 639 func (a *poolEntryAddress) strKey() string { 640 return a.ip.String() + ":" + strconv.Itoa(int(a.port)) 641 } 642 643 // poolStats implement statistics for a certain quantity with a long term average 644 // and a short term value which is adjusted exponentially with a factor of 645 // pstatRecentAdjust with each update and also returned exponentially to the 646 // average with the time constant pstatReturnToMeanTC 647 type poolStats struct { 648 sum, weight, avg, recent float64 649 lastRecalc mclock.AbsTime 650 } 651 652 // init initializes stats with a long term sum/update count pair retrieved from the database 653 func (s *poolStats) init(sum, weight float64) { 654 s.sum = sum 655 s.weight = weight 656 var avg float64 657 if weight > 0 { 658 avg = s.sum / weight 659 } 660 s.avg = avg 661 s.recent = avg 662 s.lastRecalc = mclock.Now() 663 } 664 665 // recalc recalculates recent value return-to-mean and long term average 666 func (s *poolStats) recalc() { 667 now := mclock.Now() 668 s.recent = s.avg + (s.recent-s.avg)*math.Exp(-float64(now-s.lastRecalc)/float64(pstatReturnToMeanTC)) 669 if s.sum == 0 { 670 s.avg = 0 671 } else { 672 if s.sum > s.weight*1e30 { 673 s.avg = 1e30 674 } else { 675 s.avg = s.sum / s.weight 676 } 677 } 678 s.lastRecalc = now 679 } 680 681 // add updates the stats with a new value 682 func (s *poolStats) add(value, weight float64) { 683 s.weight += weight 684 s.sum += value * weight 685 s.recalc() 686 } 687 688 // recentAvg returns the short-term adjusted average 689 func (s *poolStats) recentAvg() float64 { 690 s.recalc() 691 return s.recent 692 } 693 694 func (s *poolStats) EncodeRLP(w io.Writer) error { 695 return rlp.Encode(w, []interface{}{math.Float64bits(s.sum), math.Float64bits(s.weight)}) 696 } 697 698 func (s *poolStats) DecodeRLP(st *rlp.Stream) error { 699 var stats struct { 700 SumUint, WeightUint uint64 701 } 702 if err := st.Decode(&stats); err != nil { 703 return err 704 } 705 s.init(math.Float64frombits(stats.SumUint), math.Float64frombits(stats.WeightUint)) 706 return nil 707 } 708 709 // poolEntryQueue keeps track of its least recently accessed entries and removes 710 // them when the number of entries reaches the limit 711 type poolEntryQueue struct { 712 queue map[int]*poolEntry // known nodes indexed by their latest lastConnCnt value 713 newPtr, oldPtr, maxCnt int 714 removeFromPool func(*poolEntry) 715 } 716 717 // newPoolEntryQueue returns a new poolEntryQueue 718 func newPoolEntryQueue(maxCnt int, removeFromPool func(*poolEntry)) poolEntryQueue { 719 return poolEntryQueue{queue: make(map[int]*poolEntry), maxCnt: maxCnt, removeFromPool: removeFromPool} 720 } 721 722 // fetchOldest returns and removes the least recently accessed entry 723 func (q *poolEntryQueue) fetchOldest() *poolEntry { 724 if len(q.queue) == 0 { 725 return nil 726 } 727 for { 728 if e := q.queue[q.oldPtr]; e != nil { 729 delete(q.queue, q.oldPtr) 730 q.oldPtr++ 731 return e 732 } 733 q.oldPtr++ 734 } 735 } 736 737 // remove removes an entry from the queue 738 func (q *poolEntryQueue) remove(entry *poolEntry) { 739 if q.queue[entry.queueIdx] == entry { 740 delete(q.queue, entry.queueIdx) 741 } 742 } 743 744 // setLatest adds or updates a recently accessed entry. It also checks if an old entry 745 // needs to be removed and removes it from the parent pool too with a callback function. 746 func (q *poolEntryQueue) setLatest(entry *poolEntry) { 747 if q.queue[entry.queueIdx] == entry { 748 delete(q.queue, entry.queueIdx) 749 } else { 750 if len(q.queue) == q.maxCnt { 751 e := q.fetchOldest() 752 q.remove(e) 753 q.removeFromPool(e) 754 } 755 } 756 entry.queueIdx = q.newPtr 757 q.queue[entry.queueIdx] = entry 758 q.newPtr++ 759 }