github.com/jincm/wesharechain@v0.0.0-20210122032815-1537409ce26a/chain/les/costtracker.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 detailct.
    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
    18  
    19  import (
    20  	"encoding/binary"
    21  	"math"
    22  	"sync"
    23  	"sync/atomic"
    24  	"time"
    25  
    26  	"github.com/ethereum/go-ethereum/common/mclock"
    27  	"github.com/ethereum/go-ethereum/eth"
    28  	"github.com/ethereum/go-ethereum/ethdb"
    29  	"github.com/ethereum/go-ethereum/les/flowcontrol"
    30  	"github.com/ethereum/go-ethereum/log"
    31  )
    32  
    33  const makeCostStats = false // make request cost statistics during operation
    34  
    35  var (
    36  	// average request cost estimates based on serving time
    37  	reqAvgTimeCost = requestCostTable{
    38  		GetBlockHeadersMsg:     {150000, 30000},
    39  		GetBlockBodiesMsg:      {0, 700000},
    40  		GetReceiptsMsg:         {0, 1000000},
    41  		GetCodeMsg:             {0, 450000},
    42  		GetProofsV1Msg:         {0, 600000},
    43  		GetProofsV2Msg:         {0, 600000},
    44  		GetHeaderProofsMsg:     {0, 1000000},
    45  		GetHelperTrieProofsMsg: {0, 1000000},
    46  		SendTxMsg:              {0, 450000},
    47  		SendTxV2Msg:            {0, 450000},
    48  		GetTxStatusMsg:         {0, 250000},
    49  	}
    50  	// maximum incoming message size estimates
    51  	reqMaxInSize = requestCostTable{
    52  		GetBlockHeadersMsg:     {40, 0},
    53  		GetBlockBodiesMsg:      {0, 40},
    54  		GetReceiptsMsg:         {0, 40},
    55  		GetCodeMsg:             {0, 80},
    56  		GetProofsV1Msg:         {0, 80},
    57  		GetProofsV2Msg:         {0, 80},
    58  		GetHeaderProofsMsg:     {0, 20},
    59  		GetHelperTrieProofsMsg: {0, 20},
    60  		SendTxMsg:              {0, 66000},
    61  		SendTxV2Msg:            {0, 66000},
    62  		GetTxStatusMsg:         {0, 50},
    63  	}
    64  	// maximum outgoing message size estimates
    65  	reqMaxOutSize = requestCostTable{
    66  		GetBlockHeadersMsg:     {0, 556},
    67  		GetBlockBodiesMsg:      {0, 100000},
    68  		GetReceiptsMsg:         {0, 200000},
    69  		GetCodeMsg:             {0, 50000},
    70  		GetProofsV1Msg:         {0, 4000},
    71  		GetProofsV2Msg:         {0, 4000},
    72  		GetHeaderProofsMsg:     {0, 4000},
    73  		GetHelperTrieProofsMsg: {0, 4000},
    74  		SendTxMsg:              {0, 0},
    75  		SendTxV2Msg:            {0, 100},
    76  		GetTxStatusMsg:         {0, 100},
    77  	}
    78  	minBufLimit = uint64(50000000 * maxCostFactor)  // minimum buffer limit allowed for a client
    79  	minCapacity = (minBufLimit-1)/bufLimitRatio + 1 // minimum capacity allowed for a client
    80  )
    81  
    82  const (
    83  	maxCostFactor    = 2 // ratio of maximum and average cost estimates
    84  	gfInitWeight     = time.Second * 10
    85  	gfMaxWeight      = time.Hour
    86  	gfUsageThreshold = 0.5
    87  	gfUsageTC        = time.Second
    88  	gfDbKey          = "_globalCostFactor"
    89  )
    90  
    91  // costTracker is responsible for calculating costs and cost estimates on the
    92  // server side. It continuously updates the global cost factor which is defined
    93  // as the number of cost units per nanosecond of serving time in a single thread.
    94  // It is based on statistics collected during serving requests in high-load periods
    95  // and practically acts as a one-dimension request price scaling factor over the
    96  // pre-defined cost estimate table. Instead of scaling the cost values, the real
    97  // value of cost units is changed by applying the factor to the serving times. This
    98  // is more convenient because the changes in the cost factor can be applied immediately
    99  // without always notifying the clients about the changed cost tables.
   100  type costTracker struct {
   101  	db     ethdb.Database
   102  	stopCh chan chan struct{}
   103  
   104  	inSizeFactor, outSizeFactor float64
   105  	gf, utilTarget              float64
   106  
   107  	gfUpdateCh      chan gfUpdate
   108  	gfLock          sync.RWMutex
   109  	totalRechargeCh chan uint64
   110  
   111  	stats map[uint64][]uint64
   112  }
   113  
   114  // newCostTracker creates a cost tracker and loads the cost factor statistics from the database
   115  func newCostTracker(db ethdb.Database, config *eth.Config) *costTracker {
   116  	utilTarget := float64(config.LightServ) * flowcontrol.FixedPointMultiplier / 100
   117  	ct := &costTracker{
   118  		db:         db,
   119  		stopCh:     make(chan chan struct{}),
   120  		utilTarget: utilTarget,
   121  	}
   122  	if config.LightBandwidthIn > 0 {
   123  		ct.inSizeFactor = utilTarget / float64(config.LightBandwidthIn)
   124  	}
   125  	if config.LightBandwidthOut > 0 {
   126  		ct.outSizeFactor = utilTarget / float64(config.LightBandwidthOut)
   127  	}
   128  	if makeCostStats {
   129  		ct.stats = make(map[uint64][]uint64)
   130  		for code := range reqAvgTimeCost {
   131  			ct.stats[code] = make([]uint64, 10)
   132  		}
   133  	}
   134  	ct.gfLoop()
   135  	return ct
   136  }
   137  
   138  // stop stops the cost tracker and saves the cost factor statistics to the database
   139  func (ct *costTracker) stop() {
   140  	stopCh := make(chan struct{})
   141  	ct.stopCh <- stopCh
   142  	<-stopCh
   143  	if makeCostStats {
   144  		ct.printStats()
   145  	}
   146  }
   147  
   148  // makeCostList returns upper cost estimates based on the hardcoded cost estimate
   149  // tables and the optionally specified incoming/outgoing bandwidth limits
   150  func (ct *costTracker) makeCostList() RequestCostList {
   151  	maxCost := func(avgTime, inSize, outSize uint64) uint64 {
   152  		globalFactor := ct.globalFactor()
   153  
   154  		cost := avgTime * maxCostFactor
   155  		inSizeCost := uint64(float64(inSize) * ct.inSizeFactor * globalFactor * maxCostFactor)
   156  		if inSizeCost > cost {
   157  			cost = inSizeCost
   158  		}
   159  		outSizeCost := uint64(float64(outSize) * ct.outSizeFactor * globalFactor * maxCostFactor)
   160  		if outSizeCost > cost {
   161  			cost = outSizeCost
   162  		}
   163  		return cost
   164  	}
   165  	var list RequestCostList
   166  	for code, data := range reqAvgTimeCost {
   167  		list = append(list, requestCostListItem{
   168  			MsgCode:  code,
   169  			BaseCost: maxCost(data.baseCost, reqMaxInSize[code].baseCost, reqMaxOutSize[code].baseCost),
   170  			ReqCost:  maxCost(data.reqCost, reqMaxInSize[code].reqCost, reqMaxOutSize[code].reqCost),
   171  		})
   172  	}
   173  	return list
   174  }
   175  
   176  type gfUpdate struct {
   177  	avgTime, servingTime float64
   178  }
   179  
   180  // gfLoop starts an event loop which updates the global cost factor which is
   181  // calculated as a weighted average of the average estimate / serving time ratio.
   182  // The applied weight equals the serving time if gfUsage is over a threshold,
   183  // zero otherwise. gfUsage is the recent average serving time per time unit in
   184  // an exponential moving window. This ensures that statistics are collected only
   185  // under high-load circumstances where the measured serving times are relevant.
   186  // The total recharge parameter of the flow control system which controls the
   187  // total allowed serving time per second but nominated in cost units, should
   188  // also be scaled with the cost factor and is also updated by this loop.
   189  func (ct *costTracker) gfLoop() {
   190  	var gfUsage, gfSum, gfWeight float64
   191  	lastUpdate := mclock.Now()
   192  	expUpdate := lastUpdate
   193  
   194  	data, _ := ct.db.Get([]byte(gfDbKey))
   195  	if len(data) == 16 {
   196  		gfSum = math.Float64frombits(binary.BigEndian.Uint64(data[0:8]))
   197  		gfWeight = math.Float64frombits(binary.BigEndian.Uint64(data[8:16]))
   198  	}
   199  	if gfWeight < float64(gfInitWeight) {
   200  		gfSum = float64(gfInitWeight)
   201  		gfWeight = float64(gfInitWeight)
   202  	}
   203  	gf := gfSum / gfWeight
   204  	ct.gf = gf
   205  	ct.gfUpdateCh = make(chan gfUpdate, 100)
   206  
   207  	go func() {
   208  		for {
   209  			select {
   210  			case r := <-ct.gfUpdateCh:
   211  				now := mclock.Now()
   212  				max := r.servingTime * gf
   213  				if r.avgTime > max {
   214  					max = r.avgTime
   215  				}
   216  				dt := float64(now - expUpdate)
   217  				expUpdate = now
   218  				gfUsage = gfUsage*math.Exp(-dt/float64(gfUsageTC)) + max*1000000/float64(gfUsageTC)
   219  
   220  				if gfUsage >= gfUsageThreshold*ct.utilTarget*gf {
   221  					gfSum += r.avgTime
   222  					gfWeight += r.servingTime
   223  					if time.Duration(now-lastUpdate) > time.Second {
   224  						gf = gfSum / gfWeight
   225  						if gfWeight >= float64(gfMaxWeight) {
   226  							gfSum = gf * float64(gfMaxWeight)
   227  							gfWeight = float64(gfMaxWeight)
   228  						}
   229  						lastUpdate = now
   230  						ct.gfLock.Lock()
   231  						ct.gf = gf
   232  						ch := ct.totalRechargeCh
   233  						ct.gfLock.Unlock()
   234  						if ch != nil {
   235  							select {
   236  							case ct.totalRechargeCh <- uint64(ct.utilTarget * gf):
   237  							default:
   238  							}
   239  						}
   240  						log.Debug("global cost factor updated", "gf", gf, "weight", time.Duration(gfWeight))
   241  					}
   242  				}
   243  			case stopCh := <-ct.stopCh:
   244  				var data [16]byte
   245  				binary.BigEndian.PutUint64(data[0:8], math.Float64bits(gfSum))
   246  				binary.BigEndian.PutUint64(data[8:16], math.Float64bits(gfWeight))
   247  				ct.db.Put([]byte(gfDbKey), data[:])
   248  				log.Debug("global cost factor saved", "sum", time.Duration(gfSum), "weight", time.Duration(gfWeight))
   249  				close(stopCh)
   250  				return
   251  			}
   252  		}
   253  	}()
   254  }
   255  
   256  // globalFactor returns the current value of the global cost factor
   257  func (ct *costTracker) globalFactor() float64 {
   258  	ct.gfLock.RLock()
   259  	defer ct.gfLock.RUnlock()
   260  
   261  	return ct.gf
   262  }
   263  
   264  // totalRecharge returns the current total recharge parameter which is used by
   265  // flowcontrol.ClientManager and is scaled by the global cost factor
   266  func (ct *costTracker) totalRecharge() uint64 {
   267  	ct.gfLock.RLock()
   268  	defer ct.gfLock.RUnlock()
   269  
   270  	return uint64(ct.gf * ct.utilTarget)
   271  }
   272  
   273  // subscribeTotalRecharge returns all future updates to the total recharge value
   274  // through a channel and also returns the current value
   275  func (ct *costTracker) subscribeTotalRecharge(ch chan uint64) uint64 {
   276  	ct.gfLock.Lock()
   277  	defer ct.gfLock.Unlock()
   278  
   279  	ct.totalRechargeCh = ch
   280  	return uint64(ct.gf * ct.utilTarget)
   281  }
   282  
   283  // updateStats updates the global cost factor and (if enabled) the real cost vs.
   284  // average estimate statistics
   285  func (ct *costTracker) updateStats(code, amount, servingTime, realCost uint64) {
   286  	avg := reqAvgTimeCost[code]
   287  	avgTime := avg.baseCost + amount*avg.reqCost
   288  	select {
   289  	case ct.gfUpdateCh <- gfUpdate{float64(avgTime), float64(servingTime)}:
   290  	default:
   291  	}
   292  	if makeCostStats {
   293  		realCost <<= 4
   294  		l := 0
   295  		for l < 9 && realCost > avgTime {
   296  			l++
   297  			realCost >>= 1
   298  		}
   299  		atomic.AddUint64(&ct.stats[code][l], 1)
   300  	}
   301  }
   302  
   303  // realCost calculates the final cost of a request based on actual serving time,
   304  // incoming and outgoing message size
   305  //
   306  // Note: message size is only taken into account if bandwidth limitation is applied
   307  // and the cost based on either message size is greater than the cost based on
   308  // serving time. A maximum of the three costs is applied instead of their sum
   309  // because the three limited resources (serving thread time and i/o bandwidth) can
   310  // also be maxed out simultaneously.
   311  func (ct *costTracker) realCost(servingTime uint64, inSize, outSize uint32) uint64 {
   312  	cost := float64(servingTime)
   313  	inSizeCost := float64(inSize) * ct.inSizeFactor
   314  	if inSizeCost > cost {
   315  		cost = inSizeCost
   316  	}
   317  	outSizeCost := float64(outSize) * ct.outSizeFactor
   318  	if outSizeCost > cost {
   319  		cost = outSizeCost
   320  	}
   321  	return uint64(cost * ct.globalFactor())
   322  }
   323  
   324  // printStats prints the distribution of real request cost relative to the average estimates
   325  func (ct *costTracker) printStats() {
   326  	if ct.stats == nil {
   327  		return
   328  	}
   329  	for code, arr := range ct.stats {
   330  		log.Info("Request cost statistics", "code", code, "1/16", arr[0], "1/8", arr[1], "1/4", arr[2], "1/2", arr[3], "1", arr[4], "2", arr[5], "4", arr[6], "8", arr[7], "16", arr[8], ">16", arr[9])
   331  	}
   332  }
   333  
   334  type (
   335  	// requestCostTable assigns a cost estimate function to each request type
   336  	// which is a linear function of the requested amount
   337  	// (cost = baseCost + reqCost * amount)
   338  	requestCostTable map[uint64]*requestCosts
   339  	requestCosts     struct {
   340  		baseCost, reqCost uint64
   341  	}
   342  
   343  	// RequestCostList is a list representation of request costs which is used for
   344  	// database storage and communication through the network
   345  	RequestCostList     []requestCostListItem
   346  	requestCostListItem struct {
   347  		MsgCode, BaseCost, ReqCost uint64
   348  	}
   349  )
   350  
   351  // getCost calculates the estimated cost for a given request type and amount
   352  func (table requestCostTable) getCost(code, amount uint64) uint64 {
   353  	costs := table[code]
   354  	return costs.baseCost + amount*costs.reqCost
   355  }
   356  
   357  // decode converts a cost list to a cost table
   358  func (list RequestCostList) decode() requestCostTable {
   359  	table := make(requestCostTable)
   360  	for _, e := range list {
   361  		table[e.MsgCode] = &requestCosts{
   362  			baseCost: e.BaseCost,
   363  			reqCost:  e.ReqCost,
   364  		}
   365  	}
   366  	return table
   367  }
   368  
   369  // testCostList returns a dummy request cost list used by tests
   370  func testCostList() RequestCostList {
   371  	cl := make(RequestCostList, len(reqAvgTimeCost))
   372  	var max uint64
   373  	for code := range reqAvgTimeCost {
   374  		if code > max {
   375  			max = code
   376  		}
   377  	}
   378  	i := 0
   379  	for code := uint64(0); code <= max; code++ {
   380  		if _, ok := reqAvgTimeCost[code]; ok {
   381  			cl[i].MsgCode = code
   382  			cl[i].BaseCost = 0
   383  			cl[i].ReqCost = 0
   384  			i++
   385  		}
   386  	}
   387  	return cl
   388  }