les: more robust and quicker global factor calculation

This commit is contained in:
Zsolt Felfoldi 2019-05-29 12:54:28 +02:00
parent e1b6188f27
commit 5d7062724c

View file

@ -84,11 +84,11 @@ var (
const ( const (
maxCostFactor = 2 // ratio of maximum and average cost estimates maxCostFactor = 2 // ratio of maximum and average cost estimates
gfInitWeight = time.Second * 10
gfMaxWeight = time.Second * 1000
gfUsageThreshold = 0.5 gfUsageThreshold = 0.5
gfUsageTC = time.Second gfUsageTC = time.Second
gfDbKey = "_globalCostFactorV2" gfRaiseTC = time.Second * 200
gfDropTC = time.Second * 50
gfDbKey = "_globalCostFactorV3"
) )
// costTracker is responsible for calculating costs and cost estimates on the // costTracker is responsible for calculating costs and cost estimates on the
@ -114,7 +114,7 @@ type costTracker struct {
stats map[uint64][]uint64 stats map[uint64][]uint64
logger *csvlogger.Logger logger *csvlogger.Logger
logRecentUsage, logTotalRecharge, logRelCost *csvlogger.Channel logRecentTime, logRecentAvg, logTotalRecharge, logRelCost *csvlogger.Channel
} }
// newCostTracker creates a cost tracker and loads the cost factor statistics from the database. // newCostTracker creates a cost tracker and loads the cost factor statistics from the database.
@ -127,7 +127,8 @@ func newCostTracker(db ethdb.Database, config *eth.Config, logger *csvlogger.Log
utilTarget: utilTarget, utilTarget: utilTarget,
logger: logger, logger: logger,
logRelCost: logger.NewMinMaxChannel("relativeCost", true), logRelCost: logger.NewMinMaxChannel("relativeCost", true),
logRecentUsage: logger.NewMinMaxChannel("recentUsage", true), logRecentTime: logger.NewMinMaxChannel("recentTime", true),
logRecentAvg: logger.NewMinMaxChannel("recentAvg", true),
logTotalRecharge: logger.NewChannel("totalRecharge", 0.01), logTotalRecharge: logger.NewChannel("totalRecharge", 0.01),
} }
if config.LightBandwidthIn > 0 { if config.LightBandwidthIn > 0 {
@ -217,31 +218,26 @@ type gfUpdate struct {
// total allowed serving time per second but nominated in cost units, should // total allowed serving time per second but nominated in cost units, should
// also be scaled with the cost factor and is also updated by this loop. // also be scaled with the cost factor and is also updated by this loop.
func (ct *costTracker) gfLoop() { func (ct *costTracker) gfLoop() {
var gfUsage, gfSum, gfWeight float64 var gfLog, recentTime, recentAvg float64
lastUpdate := mclock.Now() lastUpdate := mclock.Now()
expUpdate := lastUpdate expUpdate := lastUpdate
data, _ := ct.db.Get([]byte(gfDbKey)) data, _ := ct.db.Get([]byte(gfDbKey))
if len(data) == 16 { if len(data) == 8 {
gfSum = math.Float64frombits(binary.BigEndian.Uint64(data[0:8])) gfLog = math.Float64frombits(binary.BigEndian.Uint64(data[:]))
gfWeight = math.Float64frombits(binary.BigEndian.Uint64(data[8:16]))
} }
if gfWeight < float64(gfInitWeight) { gf := math.Exp(gfLog)
gfSum = float64(gfInitWeight)
gfWeight = float64(gfInitWeight)
}
gf := gfSum / gfWeight
ct.gf = gf ct.gf = gf
totalRecharge := ct.utilTarget * gf totalRecharge := ct.utilTarget * gf
ct.gfUpdateCh = make(chan gfUpdate, 100) ct.gfUpdateCh = make(chan gfUpdate, 100)
threshold := gfUsageThreshold * float64(gfUsageTC) * ct.utilTarget / 1000000
go func() { go func() {
saveCostFactor := func() { saveCostFactor := func() {
var data [16]byte var data [8]byte
binary.BigEndian.PutUint64(data[0:8], math.Float64bits(gfSum)) binary.BigEndian.PutUint64(data[:], math.Float64bits(gfLog))
binary.BigEndian.PutUint64(data[8:16], math.Float64bits(gfWeight))
ct.db.Put([]byte(gfDbKey), data[:]) ct.db.Put([]byte(gfDbKey), data[:])
log.Debug("global cost factor saved", "sum", time.Duration(gfSum), "weight", time.Duration(gfWeight)) log.Debug("global cost factor saved", "value", gf)
} }
saveTicker := time.NewTicker(time.Minute * 10) saveTicker := time.NewTicker(time.Minute * 10)
@ -249,34 +245,47 @@ func (ct *costTracker) gfLoop() {
select { select {
case r := <-ct.gfUpdateCh: case r := <-ct.gfUpdateCh:
now := mclock.Now() now := mclock.Now()
max := r.servingTime * gf
if ct.logRelCost != nil && r.avgTime > 1e-20 { if ct.logRelCost != nil && r.avgTime > 1e-20 {
ct.logRelCost.Update(max / r.avgTime) ct.logRelCost.Update(r.servingTime * gf / r.avgTime)
} }
if r.servingTime > 1000000000 { if r.servingTime > 1000000000 {
ct.logger.Event(fmt.Sprintf("Very long servingTime = %f avgTime = %f costFactor = %f", r.servingTime, r.avgTime, gf)) ct.logger.Event(fmt.Sprintf("Very long servingTime = %f avgTime = %f costFactor = %f", r.servingTime, r.avgTime, gf))
} }
if max > r.avgTime*maxCostFactor {
max = r.avgTime * maxCostFactor
r.servingTime = max / gf
}
if r.avgTime > max {
max = r.avgTime
}
dt := float64(now - expUpdate) dt := float64(now - expUpdate)
expUpdate = now expUpdate = now
gfUsage = gfUsage*math.Exp(-dt/float64(gfUsageTC)) + max*1000000/float64(gfUsageTC) exp := math.Exp(-dt / float64(gfUsageTC))
// calculate gf correction until now, based on previous values
if gfUsage >= gfUsageThreshold*totalRecharge { var gfCorr float64
gfSum += r.avgTime max := recentTime
gfWeight += r.servingTime if recentAvg > max {
if time.Duration(now-lastUpdate) > time.Second { max = recentAvg
gf = gfSum / gfWeight
totalRecharge = ct.utilTarget * gf
if gfWeight >= float64(gfMaxWeight) {
gfSum = gf * float64(gfMaxWeight)
gfWeight = float64(gfMaxWeight)
} }
// we apply continous correction when MAX(recentTime, recentAvg) > threshold
if max > threshold {
// calculate correction time between last expUpdate and now
if max*exp >= threshold {
gfCorr = dt
} else {
gfCorr = math.Log(max/threshold) * float64(gfUsageTC)
}
// calculate log(gf) correction with the right direction and time constant
if recentTime > recentAvg {
// drop gf if actual serving times are larger than average estimates
gfCorr /= -float64(gfDropTC)
} else {
// raise gf if actual serving times are smaller than average estimates
gfCorr /= float64(gfRaiseTC)
}
}
// update recent cost values with current request
recentTime = recentTime*exp + r.servingTime
recentAvg = recentAvg*exp + r.avgTime/gf
if gfCorr != 0 {
gfLog += gfCorr
gf = math.Exp(gfLog)
if time.Duration(now-lastUpdate) > time.Second {
totalRecharge = ct.utilTarget * gf
lastUpdate = now lastUpdate = now
ct.gfLock.Lock() ct.gfLock.Lock()
ct.gf = gf ct.gf = gf
@ -288,10 +297,11 @@ func (ct *costTracker) gfLoop() {
default: default:
} }
} }
log.Debug("global cost factor updated", "gf", gf, "weight", time.Duration(gfWeight)) log.Debug("global cost factor updated", "gf", gf)
} }
} }
ct.logRecentUsage.Update(gfUsage) ct.logRecentTime.Update(recentTime)
ct.logRecentAvg.Update(recentAvg)
ct.logTotalRecharge.Update(totalRecharge) ct.logTotalRecharge.Update(totalRecharge)
case <-saveTicker.C: case <-saveTicker.C: