eth, p2p/msgrate: move peer QoS tracking to its own package and use it for snap (#22876)
This change extracts the peer QoS tracking logic from eth/downloader, moving it into the new package p2p/msgrate. The job of msgrate.Tracker is determining suitable timeout values and request sizes per peer. The snap sync scheduler now uses msgrate.Tracker instead of the hard-coded 15s timeout. This should make the sync work better on network links with high latency.
This commit is contained in:
@ -47,16 +47,6 @@ var (
|
||||
MaxReceiptFetch = 256 // Amount of transaction receipts to allow fetching per request
|
||||
MaxStateFetch = 384 // Amount of node state values to allow fetching per request
|
||||
|
||||
rttMinEstimate = 2 * time.Second // Minimum round-trip time to target for download requests
|
||||
rttMaxEstimate = 20 * time.Second // Maximum round-trip time to target for download requests
|
||||
rttMinConfidence = 0.1 // Worse confidence factor in our estimated RTT value
|
||||
ttlScaling = 3 // Constant scaling factor for RTT -> TTL conversion
|
||||
ttlLimit = time.Minute // Maximum TTL allowance to prevent reaching crazy timeouts
|
||||
|
||||
qosTuningPeers = 5 // Number of peers to tune based on (best peers)
|
||||
qosConfidenceCap = 10 // Number of peers above which not to modify RTT confidence
|
||||
qosTuningImpact = 0.25 // Impact that a new tuning target has on the previous value
|
||||
|
||||
maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection)
|
||||
maxHeadersProcess = 2048 // Number of header download results to import at once into the chain
|
||||
maxResultsProcess = 2048 // Number of content download results to import at once into the chain
|
||||
@ -96,13 +86,6 @@ var (
|
||||
)
|
||||
|
||||
type Downloader struct {
|
||||
// WARNING: The `rttEstimate` and `rttConfidence` fields are accessed atomically.
|
||||
// On 32 bit platforms, only 64-bit aligned fields can be atomic. The struct is
|
||||
// guaranteed to be so aligned, so take advantage of that. For more information,
|
||||
// see https://golang.org/pkg/sync/atomic/#pkg-note-BUG.
|
||||
rttEstimate uint64 // Round trip time to target for download requests
|
||||
rttConfidence uint64 // Confidence in the estimated RTT (unit: millionths to allow atomic ops)
|
||||
|
||||
mode uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode
|
||||
mux *event.TypeMux // Event multiplexer to announce sync operation events
|
||||
|
||||
@ -232,8 +215,6 @@ func New(checkpoint uint64, stateDb ethdb.Database, stateBloom *trie.SyncBloom,
|
||||
checkpoint: checkpoint,
|
||||
queue: newQueue(blockCacheMaxItems, blockCacheInitialItems),
|
||||
peers: newPeerSet(),
|
||||
rttEstimate: uint64(rttMaxEstimate),
|
||||
rttConfidence: uint64(1000000),
|
||||
blockchain: chain,
|
||||
lightchain: lightchain,
|
||||
dropPeer: dropPeer,
|
||||
@ -252,7 +233,6 @@ func New(checkpoint uint64, stateDb ethdb.Database, stateBloom *trie.SyncBloom,
|
||||
},
|
||||
trackStateReq: make(chan *stateReq),
|
||||
}
|
||||
go dl.qosTuner()
|
||||
go dl.stateFetcher()
|
||||
return dl
|
||||
}
|
||||
@ -310,8 +290,6 @@ func (d *Downloader) RegisterPeer(id string, version uint, peer Peer) error {
|
||||
logger.Error("Failed to register sync peer", "err", err)
|
||||
return err
|
||||
}
|
||||
d.qosReduceConfidence()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -670,7 +648,7 @@ func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *ty
|
||||
}
|
||||
go p.peer.RequestHeadersByHash(latest, fetch, fsMinFullBlocks-1, true)
|
||||
|
||||
ttl := d.requestTTL()
|
||||
ttl := d.peers.rates.TargetTimeout()
|
||||
timeout := time.After(ttl)
|
||||
for {
|
||||
select {
|
||||
@ -853,7 +831,7 @@ func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, re
|
||||
// Wait for the remote response to the head fetch
|
||||
number, hash := uint64(0), common.Hash{}
|
||||
|
||||
ttl := d.requestTTL()
|
||||
ttl := d.peers.rates.TargetTimeout()
|
||||
timeout := time.After(ttl)
|
||||
|
||||
for finished := false; !finished; {
|
||||
@ -942,7 +920,7 @@ func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode,
|
||||
// Split our chain interval in two, and request the hash to cross check
|
||||
check := (start + end) / 2
|
||||
|
||||
ttl := d.requestTTL()
|
||||
ttl := d.peers.rates.TargetTimeout()
|
||||
timeout := time.After(ttl)
|
||||
|
||||
go p.peer.RequestHeadersByNumber(check, 1, 0, false)
|
||||
@ -1035,7 +1013,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error {
|
||||
getHeaders := func(from uint64) {
|
||||
request = time.Now()
|
||||
|
||||
ttl = d.requestTTL()
|
||||
ttl = d.peers.rates.TargetTimeout()
|
||||
timeout.Reset(ttl)
|
||||
|
||||
if skeleton {
|
||||
@ -1050,7 +1028,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error {
|
||||
pivoting = true
|
||||
request = time.Now()
|
||||
|
||||
ttl = d.requestTTL()
|
||||
ttl = d.peers.rates.TargetTimeout()
|
||||
timeout.Reset(ttl)
|
||||
|
||||
d.pivotLock.RLock()
|
||||
@ -1262,12 +1240,12 @@ func (d *Downloader) fillHeaderSkeleton(from uint64, skeleton []*types.Header) (
|
||||
pack := packet.(*headerPack)
|
||||
return d.queue.DeliverHeaders(pack.peerID, pack.headers, d.headerProcCh)
|
||||
}
|
||||
expire = func() map[string]int { return d.queue.ExpireHeaders(d.requestTTL()) }
|
||||
expire = func() map[string]int { return d.queue.ExpireHeaders(d.peers.rates.TargetTimeout()) }
|
||||
reserve = func(p *peerConnection, count int) (*fetchRequest, bool, bool) {
|
||||
return d.queue.ReserveHeaders(p, count), false, false
|
||||
}
|
||||
fetch = func(p *peerConnection, req *fetchRequest) error { return p.FetchHeaders(req.From, MaxHeaderFetch) }
|
||||
capacity = func(p *peerConnection) int { return p.HeaderCapacity(d.requestRTT()) }
|
||||
capacity = func(p *peerConnection) int { return p.HeaderCapacity(d.peers.rates.TargetRoundTrip()) }
|
||||
setIdle = func(p *peerConnection, accepted int, deliveryTime time.Time) {
|
||||
p.SetHeadersIdle(accepted, deliveryTime)
|
||||
}
|
||||
@ -1293,9 +1271,9 @@ func (d *Downloader) fetchBodies(from uint64) error {
|
||||
pack := packet.(*bodyPack)
|
||||
return d.queue.DeliverBodies(pack.peerID, pack.transactions, pack.uncles)
|
||||
}
|
||||
expire = func() map[string]int { return d.queue.ExpireBodies(d.requestTTL()) }
|
||||
expire = func() map[string]int { return d.queue.ExpireBodies(d.peers.rates.TargetTimeout()) }
|
||||
fetch = func(p *peerConnection, req *fetchRequest) error { return p.FetchBodies(req) }
|
||||
capacity = func(p *peerConnection) int { return p.BlockCapacity(d.requestRTT()) }
|
||||
capacity = func(p *peerConnection) int { return p.BlockCapacity(d.peers.rates.TargetRoundTrip()) }
|
||||
setIdle = func(p *peerConnection, accepted int, deliveryTime time.Time) { p.SetBodiesIdle(accepted, deliveryTime) }
|
||||
)
|
||||
err := d.fetchParts(d.bodyCh, deliver, d.bodyWakeCh, expire,
|
||||
@ -1317,9 +1295,9 @@ func (d *Downloader) fetchReceipts(from uint64) error {
|
||||
pack := packet.(*receiptPack)
|
||||
return d.queue.DeliverReceipts(pack.peerID, pack.receipts)
|
||||
}
|
||||
expire = func() map[string]int { return d.queue.ExpireReceipts(d.requestTTL()) }
|
||||
expire = func() map[string]int { return d.queue.ExpireReceipts(d.peers.rates.TargetTimeout()) }
|
||||
fetch = func(p *peerConnection, req *fetchRequest) error { return p.FetchReceipts(req) }
|
||||
capacity = func(p *peerConnection) int { return p.ReceiptCapacity(d.requestRTT()) }
|
||||
capacity = func(p *peerConnection) int { return p.ReceiptCapacity(d.peers.rates.TargetRoundTrip()) }
|
||||
setIdle = func(p *peerConnection, accepted int, deliveryTime time.Time) {
|
||||
p.SetReceiptsIdle(accepted, deliveryTime)
|
||||
}
|
||||
@ -2031,78 +2009,3 @@ func (d *Downloader) deliver(destCh chan dataPack, packet dataPack, inMeter, dro
|
||||
return errNoSyncActive
|
||||
}
|
||||
}
|
||||
|
||||
// qosTuner is the quality of service tuning loop that occasionally gathers the
|
||||
// peer latency statistics and updates the estimated request round trip time.
|
||||
func (d *Downloader) qosTuner() {
|
||||
for {
|
||||
// Retrieve the current median RTT and integrate into the previoust target RTT
|
||||
rtt := time.Duration((1-qosTuningImpact)*float64(atomic.LoadUint64(&d.rttEstimate)) + qosTuningImpact*float64(d.peers.medianRTT()))
|
||||
atomic.StoreUint64(&d.rttEstimate, uint64(rtt))
|
||||
|
||||
// A new RTT cycle passed, increase our confidence in the estimated RTT
|
||||
conf := atomic.LoadUint64(&d.rttConfidence)
|
||||
conf = conf + (1000000-conf)/2
|
||||
atomic.StoreUint64(&d.rttConfidence, conf)
|
||||
|
||||
// Log the new QoS values and sleep until the next RTT
|
||||
log.Debug("Recalculated downloader QoS values", "rtt", rtt, "confidence", float64(conf)/1000000.0, "ttl", d.requestTTL())
|
||||
select {
|
||||
case <-d.quitCh:
|
||||
return
|
||||
case <-time.After(rtt):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// qosReduceConfidence is meant to be called when a new peer joins the downloader's
|
||||
// peer set, needing to reduce the confidence we have in out QoS estimates.
|
||||
func (d *Downloader) qosReduceConfidence() {
|
||||
// If we have a single peer, confidence is always 1
|
||||
peers := uint64(d.peers.Len())
|
||||
if peers == 0 {
|
||||
// Ensure peer connectivity races don't catch us off guard
|
||||
return
|
||||
}
|
||||
if peers == 1 {
|
||||
atomic.StoreUint64(&d.rttConfidence, 1000000)
|
||||
return
|
||||
}
|
||||
// If we have a ton of peers, don't drop confidence)
|
||||
if peers >= uint64(qosConfidenceCap) {
|
||||
return
|
||||
}
|
||||
// Otherwise drop the confidence factor
|
||||
conf := atomic.LoadUint64(&d.rttConfidence) * (peers - 1) / peers
|
||||
if float64(conf)/1000000 < rttMinConfidence {
|
||||
conf = uint64(rttMinConfidence * 1000000)
|
||||
}
|
||||
atomic.StoreUint64(&d.rttConfidence, conf)
|
||||
|
||||
rtt := time.Duration(atomic.LoadUint64(&d.rttEstimate))
|
||||
log.Debug("Relaxed downloader QoS values", "rtt", rtt, "confidence", float64(conf)/1000000.0, "ttl", d.requestTTL())
|
||||
}
|
||||
|
||||
// requestRTT returns the current target round trip time for a download request
|
||||
// to complete in.
|
||||
//
|
||||
// Note, the returned RTT is .9 of the actually estimated RTT. The reason is that
|
||||
// the downloader tries to adapt queries to the RTT, so multiple RTT values can
|
||||
// be adapted to, but smaller ones are preferred (stabler download stream).
|
||||
func (d *Downloader) requestRTT() time.Duration {
|
||||
return time.Duration(atomic.LoadUint64(&d.rttEstimate)) * 9 / 10
|
||||
}
|
||||
|
||||
// requestTTL returns the current timeout allowance for a single download request
|
||||
// to finish under.
|
||||
func (d *Downloader) requestTTL() time.Duration {
|
||||
var (
|
||||
rtt = time.Duration(atomic.LoadUint64(&d.rttEstimate))
|
||||
conf = float64(atomic.LoadUint64(&d.rttConfidence)) / 1000000.0
|
||||
)
|
||||
ttl := time.Duration(ttlScaling) * time.Duration(float64(rtt)/conf)
|
||||
if ttl > ttlLimit {
|
||||
ttl = ttlLimit
|
||||
}
|
||||
return ttl
|
||||
}
|
||||
|
@ -32,11 +32,11 @@ import (
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/msgrate"
|
||||
)
|
||||
|
||||
const (
|
||||
maxLackingHashes = 4096 // Maximum number of entries allowed on the list or lacking items
|
||||
measurementImpact = 0.1 // The impact a single measurement has on a peer's final throughput value.
|
||||
maxLackingHashes = 4096 // Maximum number of entries allowed on the list or lacking items
|
||||
)
|
||||
|
||||
var (
|
||||
@ -54,18 +54,12 @@ type peerConnection struct {
|
||||
receiptIdle int32 // Current receipt activity state of the peer (idle = 0, active = 1)
|
||||
stateIdle int32 // Current node data activity state of the peer (idle = 0, active = 1)
|
||||
|
||||
headerThroughput float64 // Number of headers measured to be retrievable per second
|
||||
blockThroughput float64 // Number of blocks (bodies) measured to be retrievable per second
|
||||
receiptThroughput float64 // Number of receipts measured to be retrievable per second
|
||||
stateThroughput float64 // Number of node data pieces measured to be retrievable per second
|
||||
|
||||
rtt time.Duration // Request round trip time to track responsiveness (QoS)
|
||||
|
||||
headerStarted time.Time // Time instance when the last header fetch was started
|
||||
blockStarted time.Time // Time instance when the last block (body) fetch was started
|
||||
receiptStarted time.Time // Time instance when the last receipt fetch was started
|
||||
stateStarted time.Time // Time instance when the last node data fetch was started
|
||||
|
||||
rates *msgrate.Tracker // Tracker to hone in on the number of items retrievable per second
|
||||
lacking map[common.Hash]struct{} // Set of hashes not to request (didn't have previously)
|
||||
|
||||
peer Peer
|
||||
@ -133,11 +127,6 @@ func (p *peerConnection) Reset() {
|
||||
atomic.StoreInt32(&p.receiptIdle, 0)
|
||||
atomic.StoreInt32(&p.stateIdle, 0)
|
||||
|
||||
p.headerThroughput = 0
|
||||
p.blockThroughput = 0
|
||||
p.receiptThroughput = 0
|
||||
p.stateThroughput = 0
|
||||
|
||||
p.lacking = make(map[common.Hash]struct{})
|
||||
}
|
||||
|
||||
@ -212,93 +201,72 @@ func (p *peerConnection) FetchNodeData(hashes []common.Hash) error {
|
||||
// requests. Its estimated header retrieval throughput is updated with that measured
|
||||
// just now.
|
||||
func (p *peerConnection) SetHeadersIdle(delivered int, deliveryTime time.Time) {
|
||||
p.setIdle(deliveryTime.Sub(p.headerStarted), delivered, &p.headerThroughput, &p.headerIdle)
|
||||
p.rates.Update(eth.BlockHeadersMsg, deliveryTime.Sub(p.headerStarted), delivered)
|
||||
atomic.StoreInt32(&p.headerIdle, 0)
|
||||
}
|
||||
|
||||
// SetBodiesIdle sets the peer to idle, allowing it to execute block body retrieval
|
||||
// requests. Its estimated body retrieval throughput is updated with that measured
|
||||
// just now.
|
||||
func (p *peerConnection) SetBodiesIdle(delivered int, deliveryTime time.Time) {
|
||||
p.setIdle(deliveryTime.Sub(p.blockStarted), delivered, &p.blockThroughput, &p.blockIdle)
|
||||
p.rates.Update(eth.BlockBodiesMsg, deliveryTime.Sub(p.blockStarted), delivered)
|
||||
atomic.StoreInt32(&p.blockIdle, 0)
|
||||
}
|
||||
|
||||
// SetReceiptsIdle sets the peer to idle, allowing it to execute new receipt
|
||||
// retrieval requests. Its estimated receipt retrieval throughput is updated
|
||||
// with that measured just now.
|
||||
func (p *peerConnection) SetReceiptsIdle(delivered int, deliveryTime time.Time) {
|
||||
p.setIdle(deliveryTime.Sub(p.receiptStarted), delivered, &p.receiptThroughput, &p.receiptIdle)
|
||||
p.rates.Update(eth.ReceiptsMsg, deliveryTime.Sub(p.receiptStarted), delivered)
|
||||
atomic.StoreInt32(&p.receiptIdle, 0)
|
||||
}
|
||||
|
||||
// SetNodeDataIdle sets the peer to idle, allowing it to execute new state trie
|
||||
// data retrieval requests. Its estimated state retrieval throughput is updated
|
||||
// with that measured just now.
|
||||
func (p *peerConnection) SetNodeDataIdle(delivered int, deliveryTime time.Time) {
|
||||
p.setIdle(deliveryTime.Sub(p.stateStarted), delivered, &p.stateThroughput, &p.stateIdle)
|
||||
}
|
||||
|
||||
// setIdle sets the peer to idle, allowing it to execute new retrieval requests.
|
||||
// Its estimated retrieval throughput is updated with that measured just now.
|
||||
func (p *peerConnection) setIdle(elapsed time.Duration, delivered int, throughput *float64, idle *int32) {
|
||||
// Irrelevant of the scaling, make sure the peer ends up idle
|
||||
defer atomic.StoreInt32(idle, 0)
|
||||
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
// If nothing was delivered (hard timeout / unavailable data), reduce throughput to minimum
|
||||
if delivered == 0 {
|
||||
*throughput = 0
|
||||
return
|
||||
}
|
||||
// Otherwise update the throughput with a new measurement
|
||||
if elapsed <= 0 {
|
||||
elapsed = 1 // +1 (ns) to ensure non-zero divisor
|
||||
}
|
||||
measured := float64(delivered) / (float64(elapsed) / float64(time.Second))
|
||||
|
||||
*throughput = (1-measurementImpact)*(*throughput) + measurementImpact*measured
|
||||
p.rtt = time.Duration((1-measurementImpact)*float64(p.rtt) + measurementImpact*float64(elapsed))
|
||||
|
||||
p.log.Trace("Peer throughput measurements updated",
|
||||
"hps", p.headerThroughput, "bps", p.blockThroughput,
|
||||
"rps", p.receiptThroughput, "sps", p.stateThroughput,
|
||||
"miss", len(p.lacking), "rtt", p.rtt)
|
||||
p.rates.Update(eth.NodeDataMsg, deliveryTime.Sub(p.stateStarted), delivered)
|
||||
atomic.StoreInt32(&p.stateIdle, 0)
|
||||
}
|
||||
|
||||
// HeaderCapacity retrieves the peers header download allowance based on its
|
||||
// previously discovered throughput.
|
||||
func (p *peerConnection) HeaderCapacity(targetRTT time.Duration) int {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return int(math.Min(1+math.Max(1, p.headerThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxHeaderFetch)))
|
||||
cap := int(math.Ceil(p.rates.Capacity(eth.BlockHeadersMsg, targetRTT)))
|
||||
if cap > MaxHeaderFetch {
|
||||
cap = MaxHeaderFetch
|
||||
}
|
||||
return cap
|
||||
}
|
||||
|
||||
// BlockCapacity retrieves the peers block download allowance based on its
|
||||
// previously discovered throughput.
|
||||
func (p *peerConnection) BlockCapacity(targetRTT time.Duration) int {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return int(math.Min(1+math.Max(1, p.blockThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxBlockFetch)))
|
||||
cap := int(math.Ceil(p.rates.Capacity(eth.BlockBodiesMsg, targetRTT)))
|
||||
if cap > MaxBlockFetch {
|
||||
cap = MaxBlockFetch
|
||||
}
|
||||
return cap
|
||||
}
|
||||
|
||||
// ReceiptCapacity retrieves the peers receipt download allowance based on its
|
||||
// previously discovered throughput.
|
||||
func (p *peerConnection) ReceiptCapacity(targetRTT time.Duration) int {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return int(math.Min(1+math.Max(1, p.receiptThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxReceiptFetch)))
|
||||
cap := int(math.Ceil(p.rates.Capacity(eth.ReceiptsMsg, targetRTT)))
|
||||
if cap > MaxReceiptFetch {
|
||||
cap = MaxReceiptFetch
|
||||
}
|
||||
return cap
|
||||
}
|
||||
|
||||
// NodeDataCapacity retrieves the peers state download allowance based on its
|
||||
// previously discovered throughput.
|
||||
func (p *peerConnection) NodeDataCapacity(targetRTT time.Duration) int {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return int(math.Min(1+math.Max(1, p.stateThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxStateFetch)))
|
||||
cap := int(math.Ceil(p.rates.Capacity(eth.NodeDataMsg, targetRTT)))
|
||||
if cap > MaxStateFetch {
|
||||
cap = MaxStateFetch
|
||||
}
|
||||
return cap
|
||||
}
|
||||
|
||||
// MarkLacking appends a new entity to the set of items (blocks, receipts, states)
|
||||
@ -330,16 +298,20 @@ func (p *peerConnection) Lacks(hash common.Hash) bool {
|
||||
// peerSet represents the collection of active peer participating in the chain
|
||||
// download procedure.
|
||||
type peerSet struct {
|
||||
peers map[string]*peerConnection
|
||||
peers map[string]*peerConnection
|
||||
rates *msgrate.Trackers // Set of rate trackers to give the sync a common beat
|
||||
|
||||
newPeerFeed event.Feed
|
||||
peerDropFeed event.Feed
|
||||
lock sync.RWMutex
|
||||
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// newPeerSet creates a new peer set top track the active download sources.
|
||||
func newPeerSet() *peerSet {
|
||||
return &peerSet{
|
||||
peers: make(map[string]*peerConnection),
|
||||
rates: msgrate.NewTrackers(log.New("proto", "eth")),
|
||||
}
|
||||
}
|
||||
|
||||
@ -371,30 +343,15 @@ func (ps *peerSet) Reset() {
|
||||
// average of all existing peers, to give it a realistic chance of being used
|
||||
// for data retrievals.
|
||||
func (ps *peerSet) Register(p *peerConnection) error {
|
||||
// Retrieve the current median RTT as a sane default
|
||||
p.rtt = ps.medianRTT()
|
||||
|
||||
// Register the new peer with some meaningful defaults
|
||||
ps.lock.Lock()
|
||||
if _, ok := ps.peers[p.id]; ok {
|
||||
ps.lock.Unlock()
|
||||
return errAlreadyRegistered
|
||||
}
|
||||
if len(ps.peers) > 0 {
|
||||
p.headerThroughput, p.blockThroughput, p.receiptThroughput, p.stateThroughput = 0, 0, 0, 0
|
||||
|
||||
for _, peer := range ps.peers {
|
||||
peer.lock.RLock()
|
||||
p.headerThroughput += peer.headerThroughput
|
||||
p.blockThroughput += peer.blockThroughput
|
||||
p.receiptThroughput += peer.receiptThroughput
|
||||
p.stateThroughput += peer.stateThroughput
|
||||
peer.lock.RUnlock()
|
||||
}
|
||||
p.headerThroughput /= float64(len(ps.peers))
|
||||
p.blockThroughput /= float64(len(ps.peers))
|
||||
p.receiptThroughput /= float64(len(ps.peers))
|
||||
p.stateThroughput /= float64(len(ps.peers))
|
||||
p.rates = msgrate.NewTracker(ps.rates.MeanCapacities(), ps.rates.MedianRoundTrip())
|
||||
if err := ps.rates.Track(p.id, p.rates); err != nil {
|
||||
return err
|
||||
}
|
||||
ps.peers[p.id] = p
|
||||
ps.lock.Unlock()
|
||||
@ -413,6 +370,7 @@ func (ps *peerSet) Unregister(id string) error {
|
||||
return errNotRegistered
|
||||
}
|
||||
delete(ps.peers, id)
|
||||
ps.rates.Untrack(id)
|
||||
ps.lock.Unlock()
|
||||
|
||||
ps.peerDropFeed.Send(p)
|
||||
@ -454,9 +412,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) {
|
||||
return atomic.LoadInt32(&p.headerIdle) == 0
|
||||
}
|
||||
throughput := func(p *peerConnection) float64 {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
return p.headerThroughput
|
||||
return p.rates.Capacity(eth.BlockHeadersMsg, time.Second)
|
||||
}
|
||||
return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput)
|
||||
}
|
||||
@ -468,9 +424,7 @@ func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) {
|
||||
return atomic.LoadInt32(&p.blockIdle) == 0
|
||||
}
|
||||
throughput := func(p *peerConnection) float64 {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
return p.blockThroughput
|
||||
return p.rates.Capacity(eth.BlockBodiesMsg, time.Second)
|
||||
}
|
||||
return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput)
|
||||
}
|
||||
@ -482,9 +436,7 @@ func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) {
|
||||
return atomic.LoadInt32(&p.receiptIdle) == 0
|
||||
}
|
||||
throughput := func(p *peerConnection) float64 {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
return p.receiptThroughput
|
||||
return p.rates.Capacity(eth.ReceiptsMsg, time.Second)
|
||||
}
|
||||
return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput)
|
||||
}
|
||||
@ -496,9 +448,7 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) {
|
||||
return atomic.LoadInt32(&p.stateIdle) == 0
|
||||
}
|
||||
throughput := func(p *peerConnection) float64 {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
return p.stateThroughput
|
||||
return p.rates.Capacity(eth.NodeDataMsg, time.Second)
|
||||
}
|
||||
return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput)
|
||||
}
|
||||
@ -527,37 +477,6 @@ func (ps *peerSet) idlePeers(minProtocol, maxProtocol uint, idleCheck func(*peer
|
||||
return sortPeers.p, total
|
||||
}
|
||||
|
||||
// medianRTT returns the median RTT of the peerset, considering only the tuning
|
||||
// peers if there are more peers available.
|
||||
func (ps *peerSet) medianRTT() time.Duration {
|
||||
// Gather all the currently measured round trip times
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
rtts := make([]float64, 0, len(ps.peers))
|
||||
for _, p := range ps.peers {
|
||||
p.lock.RLock()
|
||||
rtts = append(rtts, float64(p.rtt))
|
||||
p.lock.RUnlock()
|
||||
}
|
||||
sort.Float64s(rtts)
|
||||
|
||||
median := rttMaxEstimate
|
||||
if qosTuningPeers <= len(rtts) {
|
||||
median = time.Duration(rtts[qosTuningPeers/2]) // Median of our tuning peers
|
||||
} else if len(rtts) > 0 {
|
||||
median = time.Duration(rtts[len(rtts)/2]) // Median of our connected peers (maintain even like this some baseline qos)
|
||||
}
|
||||
// Restrict the RTT into some QoS defaults, irrelevant of true RTT
|
||||
if median < rttMinEstimate {
|
||||
median = rttMinEstimate
|
||||
}
|
||||
if median > rttMaxEstimate {
|
||||
median = rttMaxEstimate
|
||||
}
|
||||
return median
|
||||
}
|
||||
|
||||
// peerThroughputSort implements the Sort interface, and allows for
|
||||
// sorting a set of peers by their throughput
|
||||
// The sorted data is with the _highest_ throughput first
|
||||
|
@ -1,53 +0,0 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPeerThroughputSorting(t *testing.T) {
|
||||
a := &peerConnection{
|
||||
id: "a",
|
||||
headerThroughput: 1.25,
|
||||
}
|
||||
b := &peerConnection{
|
||||
id: "b",
|
||||
headerThroughput: 1.21,
|
||||
}
|
||||
c := &peerConnection{
|
||||
id: "c",
|
||||
headerThroughput: 1.23,
|
||||
}
|
||||
|
||||
peers := []*peerConnection{a, b, c}
|
||||
tps := []float64{a.headerThroughput,
|
||||
b.headerThroughput, c.headerThroughput}
|
||||
sortPeers := &peerThroughputSort{peers, tps}
|
||||
sort.Sort(sortPeers)
|
||||
if got, exp := sortPeers.p[0].id, "a"; got != exp {
|
||||
t.Errorf("sort fail, got %v exp %v", got, exp)
|
||||
}
|
||||
if got, exp := sortPeers.p[1].id, "c"; got != exp {
|
||||
t.Errorf("sort fail, got %v exp %v", got, exp)
|
||||
}
|
||||
if got, exp := sortPeers.p[2].id, "b"; got != exp {
|
||||
t.Errorf("sort fail, got %v exp %v", got, exp)
|
||||
}
|
||||
|
||||
}
|
@ -433,8 +433,8 @@ func (s *stateSync) assignTasks() {
|
||||
peers, _ := s.d.peers.NodeDataIdlePeers()
|
||||
for _, p := range peers {
|
||||
// Assign a batch of fetches proportional to the estimated latency/bandwidth
|
||||
cap := p.NodeDataCapacity(s.d.requestRTT())
|
||||
req := &stateReq{peer: p, timeout: s.d.requestTTL()}
|
||||
cap := p.NodeDataCapacity(s.d.peers.rates.TargetRoundTrip())
|
||||
req := &stateReq{peer: p, timeout: s.d.peers.rates.TargetTimeout()}
|
||||
|
||||
nodes, _, codes := s.fillTasks(cap, req)
|
||||
|
||||
|
Reference in New Issue
Block a user