les: fixed cost table with global correction factor

This commit is contained in:
Zsolt Felfoldi 2019-01-18 11:31:37 +01:00
parent 2b2fe5350f
commit 6ffab7ae37
3 changed files with 216 additions and 52 deletions

View file

@ -109,7 +109,6 @@ type ProtocolManager struct {
reqDist *requestDistributor
retriever *retrieveManager
servingQueue *servingQueue
inSizeCostFactor, outSizeCostFactor float64
downloader *downloader.Downloader
fetcher *lightFetcher
@ -223,14 +222,14 @@ func (pm *ProtocolManager) Start(maxPeers int) {
}
}
}
freePeers := pm.maxFreePeers(0, 0)
if freePeers < maxPeers {
log.Warn("Light peer count limited", "specified", maxPeers, "allowed", freePeers)
}
if pm.lightSync {
go pm.syncer()
} else {
freePeers := pm.maxFreePeers(0, 0)
if freePeers < maxPeers {
log.Warn("Light peer count limited", "specified", maxPeers, "allowed", freePeers)
}
pm.clientPool = newFreeClientPool(pm.chainDb, freePeers, 10000, mclock.System{})
go func() {
for range pm.newPeerCh {
@ -454,7 +453,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
p.responseCount++
responseCount := p.responseCount
var (
maxCost uint64
maxCost, avgTime uint64
priority int64
)
@ -470,6 +469,8 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
if maxCost > p.fcParams.BufLimit {
maxCost = p.fcParams.BufLimit
}
costs = reqAvgTime[msg.Code]
avgTime = costs.baseCost + reqCnt*costs.reqCost
if accepted, bufShort, servingPriority := p.fcClient.AcceptRequest(reqID, responseCount, maxCost); !accepted {
if bufShort > 0 {
@ -499,19 +500,21 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
p.responseLock.Lock()
defer p.responseLock.Unlock()
realCost := servingTime
inSizeCost := uint64(float64(msg.Size) * pm.inSizeCostFactor)
if inSizeCost > realCost {
realCost = inSizeCost
cost := float64(servingTime)
inSizeCost := float64(msg.Size) * pm.server.inSizeCostFactor
if inSizeCost > cost {
cost = inSizeCost
}
if reply != nil {
outSizeCost := uint64(float64(reply.size()) * pm.outSizeCostFactor)
if outSizeCost > realCost {
realCost = outSizeCost
outSizeCost := float64(reply.size()) * pm.server.outSizeCostFactor
if outSizeCost > cost {
cost = outSizeCost
}
}
realCost := uint64(cost * pm.server.getGlobalCostFactor())
bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost)
pm.server.updateGlobalCostFactor(avgTime, servingTime)
if pm.server.fcCostStats != nil {
pm.server.fcCostStats.update(msg.Code, amount, realCost)
}

View file

@ -519,8 +519,9 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
}
send = send.add("flowControl/BL", server.defParams.BufLimit)
send = send.add("flowControl/MRR", server.defParams.MinRecharge)
send = send.add("flowControl/MRC", server.fcCostList)
p.fcCosts = server.fcCostTable
costList := server.makeCostList()
send = send.add("flowControl/MRC", costList)
p.fcCosts = costList.decode()
p.fcParams = server.defParams
} else {
//on client node

View file

@ -19,7 +19,10 @@ package les
import (
"crypto/ecdsa"
"encoding/binary"
"math"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/mclock"
@ -38,15 +41,56 @@ import (
const (
bufLimitRatio = 6000 // fixed bufLimit/MRR ratio
makeCostStats = false // make request cost statistics during operation
makeCostStats = true // make request cost statistics during operation
)
var (
reqAvgTime = requestCostTable{
GetBlockHeadersMsg: {150000, 30000},
GetBlockBodiesMsg: {0, 700000},
GetReceiptsMsg: {0, 1000000},
GetCodeMsg: {0, 450000},
GetProofsV1Msg: {0, 600000},
GetProofsV2Msg: {0, 600000},
GetHeaderProofsMsg: {0, 1000000},
GetHelperTrieProofsMsg: {0, 1000000},
SendTxMsg: {0, 450000},
SendTxV2Msg: {0, 450000},
GetTxStatusMsg: {0, 250000},
}
reqMaxInSize = requestCostTable{
GetBlockHeadersMsg: {40, 0},
GetBlockBodiesMsg: {0, 40},
GetReceiptsMsg: {0, 40},
GetCodeMsg: {0, 80},
GetProofsV1Msg: {0, 80},
GetProofsV2Msg: {0, 80},
GetHeaderProofsMsg: {0, 20},
GetHelperTrieProofsMsg: {0, 20},
SendTxMsg: {0, 66000},
SendTxV2Msg: {0, 66000},
GetTxStatusMsg: {0, 50},
}
reqMaxOutSize = requestCostTable{
GetBlockHeadersMsg: {0, 556},
GetBlockBodiesMsg: {0, 100000},
GetReceiptsMsg: {0, 200000},
GetCodeMsg: {0, 50000},
GetProofsV1Msg: {0, 4000},
GetProofsV2Msg: {0, 4000},
GetHeaderProofsMsg: {0, 4000},
GetHelperTrieProofsMsg: {0, 4000},
SendTxMsg: {0, 0},
SendTxV2Msg: {0, 100},
GetTxStatusMsg: {0, 100},
}
minBufLimit = 100000000
)
type LesServer struct {
lesCommons
fcManager *flowcontrol.ClientManager // nil if our node is client only
fcCostList RequestCostList
fcCostTable requestCostTable
fcCostStats *requestCostStats
defParams flowcontrol.ServerParams
lesTopics []discv5.Topic
@ -54,9 +98,15 @@ type LesServer struct {
quitSync chan struct{}
onlyAnnounce bool
totalCapacity, minCapacity, minBufLimit, bufLimitRatio uint64
totalCapacity, minCapacity uint64
rcNormal, rcBlockProcessing flowcontrol.PieceWiseLinear // buffer recharge curve for normal operation and block processing mode
thcNormal, thcBlockProcessing int // serving thread count for normal operation and block processing mode
inSizeCostFactor, outSizeCostFactor float64
globalCostFactor float64
gcfUpdateCh chan gcfUpdate
gcfLock sync.RWMutex
}
func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
@ -125,18 +175,16 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
srv.totalCapacity = capNormal
if config.LightBandwidthIn > 0 {
pm.inSizeCostFactor = float64(srv.totalCapacity) / float64(config.LightBandwidthIn)
srv.inSizeCostFactor = float64(srv.totalCapacity) / float64(config.LightBandwidthIn)
}
if config.LightBandwidthOut > 0 {
pm.outSizeCostFactor = float64(srv.totalCapacity) / float64(config.LightBandwidthOut)
srv.outSizeCostFactor = float64(srv.totalCapacity) / float64(config.LightBandwidthOut)
}
srv.fcCostList, srv.minBufLimit = pm.benchmarkCosts(srv.thcNormal, pm.inSizeCostFactor, pm.outSizeCostFactor)
srv.fcCostTable = srv.fcCostList.decode()
if makeCostStats {
srv.fcCostStats = newCostStats(srv.fcCostTable)
srv.fcCostStats = newCostStats(srv.makeCostList().decode())
}
srv.minCapacity = (srv.minBufLimit-1)/bufLimitRatio + 1
srv.minCapacity = uint64((minBufLimit-1)/bufLimitRatio + 1)
chtV1SectionCount, _, _ := srv.chtIndexer.Sections() // indexer still uses LES/1 4k section size for backwards server compatibility
chtV2SectionCount := chtV1SectionCount / (params.CHTFrequencyClient / params.CHTFrequencyServer)
@ -159,6 +207,7 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
srv.chtIndexer.Start(eth.BlockChain())
srv.blockProcLoop(pm)
srv.gcfLoop()
return srv, nil
}
@ -236,15 +285,126 @@ func (s *LesServer) Stop() {
s.protocolManager.Stop()
}
type requestCosts struct {
func (s *LesServer) makeCostList() RequestCostList {
maxCost := func(avgTime, inSize, outSize uint64) uint64 {
globalCostFactor := s.getGlobalCostFactor()
cost := avgTime * 2
inSizeCost := uint64(float64(inSize) * s.inSizeCostFactor * globalCostFactor * 1.2)
if inSizeCost > cost {
cost = inSizeCost
}
outSizeCost := uint64(float64(outSize) * s.outSizeCostFactor * globalCostFactor * 1.2)
if outSizeCost > cost {
cost = outSizeCost
}
return cost
}
var list RequestCostList
for code, data := range reqAvgTime {
list = append(list, requestCostListItem{
MsgCode: code,
BaseCost: maxCost(data.baseCost, reqMaxInSize[code].baseCost, reqMaxOutSize[code].baseCost),
ReqCost: maxCost(data.reqCost, reqMaxInSize[code].reqCost, reqMaxOutSize[code].reqCost),
})
}
return list
}
const (
gcfMinWeight = time.Second * 10
gcfMaxWeight = time.Minute
gcfUsageThreshold = 0.5
gcfUsageTC = time.Second
gcfDbKey = "_globalCostFactor"
)
type gcfUpdate struct {
avgTime, servingTime float64
}
func (s *LesServer) gcfLoop() {
s.protocolManager.wg.Add(1)
var gcf, gcfUsage, gcfSum, gcfWeight float64
lastUpdate := mclock.Now()
expUpdate := lastUpdate
gcf = 1
data, _ := s.protocolManager.chainDb.Get([]byte(gcfDbKey))
if len(data) == 16 {
gcfSum = math.Float64frombits(binary.BigEndian.Uint64(data[0:8]))
gcfWeight = math.Float64frombits(binary.BigEndian.Uint64(data[8:16]))
if gcfWeight >= float64(gcfMinWeight) {
gcf = gcfSum / gcfWeight
}
}
s.globalCostFactor = gcf
s.gcfUpdateCh = make(chan gcfUpdate, 100)
go func() {
for {
select {
case r := <-s.gcfUpdateCh:
now := mclock.Now()
max := r.servingTime * gcf
if r.avgTime > max {
max = r.avgTime
}
dt := float64(now - expUpdate)
expUpdate = now
gcfUsage = gcfUsage*math.Exp(-dt/float64(gcfUsageTC)) + max*1000000/float64(gcfUsageTC)
if gcfUsage >= gcfUsageThreshold*float64(s.totalCapacity)*gcf {
gcfSum += r.avgTime
gcfWeight += r.servingTime
if time.Duration(now-lastUpdate) > time.Second && gcfWeight >= float64(gcfMinWeight) {
gcf = gcfSum / gcfWeight
if gcfWeight >= float64(gcfMaxWeight) {
gcfSum = gcf * float64(gcfMaxWeight)
gcfWeight = float64(gcfMaxWeight)
}
lastUpdate = now
s.gcfLock.Lock()
s.globalCostFactor = gcf
s.gcfLock.Unlock()
log.Debug("globalCostFactor updated", "gcf", gcf, "weight", time.Duration(gcfWeight))
}
}
case <-s.protocolManager.quitSync:
var data [16]byte
binary.BigEndian.PutUint64(data[0:8], math.Float64bits(gcfSum))
binary.BigEndian.PutUint64(data[8:16], math.Float64bits(gcfWeight))
s.protocolManager.chainDb.Put([]byte(gcfDbKey), data[:])
log.Debug("globalCostFactor saved", "sum", time.Duration(gcfSum), "weight", time.Duration(gcfWeight))
s.protocolManager.wg.Done()
return
}
}
}()
}
func (s *LesServer) getGlobalCostFactor() float64 {
s.gcfLock.RLock()
defer s.gcfLock.RUnlock()
return s.globalCostFactor
}
func (s *LesServer) updateGlobalCostFactor(avgTime, servingTime uint64) {
s.gcfUpdateCh <- gcfUpdate{float64(avgTime), float64(servingTime)}
}
type (
requestCosts struct {
baseCost, reqCost uint64
}
requestCostTable map[uint64]*requestCosts
type requestCostTable map[uint64]*requestCosts
type RequestCostList []struct {
RequestCostList []requestCostListItem
requestCostListItem struct {
MsgCode, BaseCost, ReqCost uint64
}
)
func (list RequestCostList) decode() requestCostTable {
table := make(requestCostTable)