github.com/JFJun/bsc@v1.0.0/les/lespay/client/valuetracker.go (about)

     1  // Copyright 2020 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 client
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"math"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/JFJun/bsc/common/mclock"
    27  	"github.com/JFJun/bsc/ethdb"
    28  	"github.com/JFJun/bsc/les/utils"
    29  	"github.com/JFJun/bsc/log"
    30  	"github.com/JFJun/bsc/p2p/enode"
    31  	"github.com/JFJun/bsc/rlp"
    32  )
    33  
    34  const (
    35  	vtVersion  = 1 // database encoding format for ValueTracker
    36  	nvtVersion = 1 // database encoding format for NodeValueTracker
    37  )
    38  
    39  var (
    40  	vtKey     = []byte("vt:")
    41  	vtNodeKey = []byte("vtNode:")
    42  )
    43  
    44  // NodeValueTracker collects service value statistics for a specific server node
    45  type NodeValueTracker struct {
    46  	lock sync.Mutex
    47  
    48  	rtStats, lastRtStats ResponseTimeStats
    49  	lastTransfer         mclock.AbsTime
    50  	basket               serverBasket
    51  	reqCosts             []uint64
    52  	reqValues            *[]float64
    53  }
    54  
    55  // init initializes a NodeValueTracker.
    56  // Note that the contents of the referenced reqValues slice will not change; a new
    57  // reference is passed if the values are updated by ValueTracker.
    58  func (nv *NodeValueTracker) init(now mclock.AbsTime, reqValues *[]float64) {
    59  	reqTypeCount := len(*reqValues)
    60  	nv.reqCosts = make([]uint64, reqTypeCount)
    61  	nv.lastTransfer = now
    62  	nv.reqValues = reqValues
    63  	nv.basket.init(reqTypeCount)
    64  }
    65  
    66  // updateCosts updates the request cost table of the server. The request value factor
    67  // is also updated based on the given cost table and the current reference basket.
    68  // Note that the contents of the referenced reqValues slice will not change; a new
    69  // reference is passed if the values are updated by ValueTracker.
    70  func (nv *NodeValueTracker) updateCosts(reqCosts []uint64, reqValues *[]float64, rvFactor float64) {
    71  	nv.lock.Lock()
    72  	defer nv.lock.Unlock()
    73  
    74  	nv.reqCosts = reqCosts
    75  	nv.reqValues = reqValues
    76  	nv.basket.updateRvFactor(rvFactor)
    77  }
    78  
    79  // transferStats returns request basket and response time statistics that should be
    80  // added to the global statistics. The contents of the server's own request basket are
    81  // gradually transferred to the main reference basket and removed from the server basket
    82  // with the specified transfer rate.
    83  // The response time statistics are retained at both places and therefore the global
    84  // distribution is always the sum of the individual server distributions.
    85  func (nv *NodeValueTracker) transferStats(now mclock.AbsTime, transferRate float64) (requestBasket, ResponseTimeStats) {
    86  	nv.lock.Lock()
    87  	defer nv.lock.Unlock()
    88  
    89  	dt := now - nv.lastTransfer
    90  	nv.lastTransfer = now
    91  	if dt < 0 {
    92  		dt = 0
    93  	}
    94  	recentRtStats := nv.rtStats
    95  	recentRtStats.SubStats(&nv.lastRtStats)
    96  	nv.lastRtStats = nv.rtStats
    97  	return nv.basket.transfer(-math.Expm1(-transferRate * float64(dt))), recentRtStats
    98  }
    99  
   100  // RtStats returns the node's own response time distribution statistics
   101  func (nv *NodeValueTracker) RtStats() ResponseTimeStats {
   102  	nv.lock.Lock()
   103  	defer nv.lock.Unlock()
   104  
   105  	return nv.rtStats
   106  }
   107  
   108  // ValueTracker coordinates service value calculation for individual servers and updates
   109  // global statistics
   110  type ValueTracker struct {
   111  	clock        mclock.Clock
   112  	lock         sync.Mutex
   113  	quit         chan chan struct{}
   114  	db           ethdb.KeyValueStore
   115  	connected    map[enode.ID]*NodeValueTracker
   116  	reqTypeCount int
   117  
   118  	refBasket      referenceBasket
   119  	mappings       [][]string
   120  	currentMapping int
   121  	initRefBasket  requestBasket
   122  	rtStats        ResponseTimeStats
   123  
   124  	transferRate                 float64
   125  	statsExpLock                 sync.RWMutex
   126  	statsExpRate, offlineExpRate float64
   127  	statsExpirer                 utils.Expirer
   128  	statsExpFactor               utils.ExpirationFactor
   129  }
   130  
   131  type valueTrackerEncV1 struct {
   132  	Mappings           [][]string
   133  	RefBasketMapping   uint
   134  	RefBasket          requestBasket
   135  	RtStats            ResponseTimeStats
   136  	ExpOffset, SavedAt uint64
   137  }
   138  
   139  type nodeValueTrackerEncV1 struct {
   140  	RtStats             ResponseTimeStats
   141  	ServerBasketMapping uint
   142  	ServerBasket        requestBasket
   143  }
   144  
   145  // RequestInfo is an initializer structure for the service vector.
   146  type RequestInfo struct {
   147  	// Name identifies the request type and is used for re-mapping the service vector if necessary
   148  	Name string
   149  	// InitAmount and InitValue are used to initialize the reference basket
   150  	InitAmount, InitValue float64
   151  }
   152  
   153  // NewValueTracker creates a new ValueTracker and loads its previously saved state from
   154  // the database if possible.
   155  func NewValueTracker(db ethdb.KeyValueStore, clock mclock.Clock, reqInfo []RequestInfo, updatePeriod time.Duration, transferRate, statsExpRate, offlineExpRate float64) *ValueTracker {
   156  	now := clock.Now()
   157  
   158  	initRefBasket := requestBasket{items: make([]basketItem, len(reqInfo))}
   159  	mapping := make([]string, len(reqInfo))
   160  
   161  	var sumAmount, sumValue float64
   162  	for _, req := range reqInfo {
   163  		sumAmount += req.InitAmount
   164  		sumValue += req.InitAmount * req.InitValue
   165  	}
   166  	scaleValues := sumAmount * basketFactor / sumValue
   167  	for i, req := range reqInfo {
   168  		mapping[i] = req.Name
   169  		initRefBasket.items[i].amount = uint64(req.InitAmount * basketFactor)
   170  		initRefBasket.items[i].value = uint64(req.InitAmount * req.InitValue * scaleValues)
   171  	}
   172  
   173  	vt := &ValueTracker{
   174  		clock:          clock,
   175  		connected:      make(map[enode.ID]*NodeValueTracker),
   176  		quit:           make(chan chan struct{}),
   177  		db:             db,
   178  		reqTypeCount:   len(initRefBasket.items),
   179  		initRefBasket:  initRefBasket,
   180  		transferRate:   transferRate,
   181  		statsExpRate:   statsExpRate,
   182  		offlineExpRate: offlineExpRate,
   183  	}
   184  	if vt.loadFromDb(mapping) != nil {
   185  		// previous state not saved or invalid, init with default values
   186  		vt.refBasket.basket = initRefBasket
   187  		vt.mappings = [][]string{mapping}
   188  		vt.currentMapping = 0
   189  	}
   190  	vt.statsExpirer.SetRate(now, statsExpRate)
   191  	vt.refBasket.init(vt.reqTypeCount)
   192  	vt.periodicUpdate()
   193  
   194  	go func() {
   195  		for {
   196  			select {
   197  			case <-clock.After(updatePeriod):
   198  				vt.lock.Lock()
   199  				vt.periodicUpdate()
   200  				vt.lock.Unlock()
   201  			case quit := <-vt.quit:
   202  				close(quit)
   203  				return
   204  			}
   205  		}
   206  	}()
   207  	return vt
   208  }
   209  
   210  // StatsExpirer returns the statistics expirer so that other values can be expired
   211  // with the same rate as the service value statistics.
   212  func (vt *ValueTracker) StatsExpirer() *utils.Expirer {
   213  	return &vt.statsExpirer
   214  }
   215  
   216  // loadFromDb loads the value tracker's state from the database and converts saved
   217  // request basket index mapping if it does not match the specified index to name mapping.
   218  func (vt *ValueTracker) loadFromDb(mapping []string) error {
   219  	enc, err := vt.db.Get(vtKey)
   220  	if err != nil {
   221  		return err
   222  	}
   223  	r := bytes.NewReader(enc)
   224  	var version uint
   225  	if err := rlp.Decode(r, &version); err != nil {
   226  		log.Error("Decoding value tracker state failed", "err", err)
   227  		return err
   228  	}
   229  	if version != vtVersion {
   230  		log.Error("Unknown ValueTracker version", "stored", version, "current", nvtVersion)
   231  		return fmt.Errorf("Unknown ValueTracker version %d (current version is %d)", version, vtVersion)
   232  	}
   233  	var vte valueTrackerEncV1
   234  	if err := rlp.Decode(r, &vte); err != nil {
   235  		log.Error("Decoding value tracker state failed", "err", err)
   236  		return err
   237  	}
   238  	logOffset := utils.Fixed64(vte.ExpOffset)
   239  	dt := time.Now().UnixNano() - int64(vte.SavedAt)
   240  	if dt > 0 {
   241  		logOffset += utils.Float64ToFixed64(float64(dt) * vt.offlineExpRate / math.Log(2))
   242  	}
   243  	vt.statsExpirer.SetLogOffset(vt.clock.Now(), logOffset)
   244  	vt.rtStats = vte.RtStats
   245  	vt.mappings = vte.Mappings
   246  	vt.currentMapping = -1
   247  loop:
   248  	for i, m := range vt.mappings {
   249  		if len(m) != len(mapping) {
   250  			continue loop
   251  		}
   252  		for j, s := range mapping {
   253  			if m[j] != s {
   254  				continue loop
   255  			}
   256  		}
   257  		vt.currentMapping = i
   258  		break
   259  	}
   260  	if vt.currentMapping == -1 {
   261  		vt.currentMapping = len(vt.mappings)
   262  		vt.mappings = append(vt.mappings, mapping)
   263  	}
   264  	if int(vte.RefBasketMapping) == vt.currentMapping {
   265  		vt.refBasket.basket = vte.RefBasket
   266  	} else {
   267  		if vte.RefBasketMapping >= uint(len(vt.mappings)) {
   268  			log.Error("Unknown request basket mapping", "stored", vte.RefBasketMapping, "current", vt.currentMapping)
   269  			return fmt.Errorf("Unknown request basket mapping %d (current version is %d)", vte.RefBasketMapping, vt.currentMapping)
   270  		}
   271  		vt.refBasket.basket = vte.RefBasket.convertMapping(vt.mappings[vte.RefBasketMapping], mapping, vt.initRefBasket)
   272  	}
   273  	return nil
   274  }
   275  
   276  // saveToDb saves the value tracker's state to the database
   277  func (vt *ValueTracker) saveToDb() {
   278  	vte := valueTrackerEncV1{
   279  		Mappings:         vt.mappings,
   280  		RefBasketMapping: uint(vt.currentMapping),
   281  		RefBasket:        vt.refBasket.basket,
   282  		RtStats:          vt.rtStats,
   283  		ExpOffset:        uint64(vt.statsExpirer.LogOffset(vt.clock.Now())),
   284  		SavedAt:          uint64(time.Now().UnixNano()),
   285  	}
   286  	enc1, err := rlp.EncodeToBytes(uint(vtVersion))
   287  	if err != nil {
   288  		log.Error("Encoding value tracker state failed", "err", err)
   289  		return
   290  	}
   291  	enc2, err := rlp.EncodeToBytes(&vte)
   292  	if err != nil {
   293  		log.Error("Encoding value tracker state failed", "err", err)
   294  		return
   295  	}
   296  	if err := vt.db.Put(vtKey, append(enc1, enc2...)); err != nil {
   297  		log.Error("Saving value tracker state failed", "err", err)
   298  	}
   299  }
   300  
   301  // Stop saves the value tracker's state and each loaded node's individual state and
   302  // returns after shutting the internal goroutines down.
   303  func (vt *ValueTracker) Stop() {
   304  	quit := make(chan struct{})
   305  	vt.quit <- quit
   306  	<-quit
   307  	vt.lock.Lock()
   308  	vt.periodicUpdate()
   309  	for id, nv := range vt.connected {
   310  		vt.saveNode(id, nv)
   311  	}
   312  	vt.connected = nil
   313  	vt.saveToDb()
   314  	vt.lock.Unlock()
   315  }
   316  
   317  // Register adds a server node to the value tracker
   318  func (vt *ValueTracker) Register(id enode.ID) *NodeValueTracker {
   319  	vt.lock.Lock()
   320  	defer vt.lock.Unlock()
   321  
   322  	if vt.connected == nil {
   323  		// ValueTracker has already been stopped
   324  		return nil
   325  	}
   326  	nv := vt.loadOrNewNode(id)
   327  	nv.init(vt.clock.Now(), &vt.refBasket.reqValues)
   328  	vt.connected[id] = nv
   329  	return nv
   330  }
   331  
   332  // Unregister removes a server node from the value tracker
   333  func (vt *ValueTracker) Unregister(id enode.ID) {
   334  	vt.lock.Lock()
   335  	defer vt.lock.Unlock()
   336  
   337  	if nv := vt.connected[id]; nv != nil {
   338  		vt.saveNode(id, nv)
   339  		delete(vt.connected, id)
   340  	}
   341  }
   342  
   343  // GetNode returns an individual server node's value tracker. If it did not exist before
   344  // then a new node is created.
   345  func (vt *ValueTracker) GetNode(id enode.ID) *NodeValueTracker {
   346  	vt.lock.Lock()
   347  	defer vt.lock.Unlock()
   348  
   349  	return vt.loadOrNewNode(id)
   350  }
   351  
   352  // loadOrNewNode returns an individual server node's value tracker. If it did not exist before
   353  // then a new node is created.
   354  func (vt *ValueTracker) loadOrNewNode(id enode.ID) *NodeValueTracker {
   355  	if nv, ok := vt.connected[id]; ok {
   356  		return nv
   357  	}
   358  	nv := &NodeValueTracker{lastTransfer: vt.clock.Now()}
   359  	enc, err := vt.db.Get(append(vtNodeKey, id[:]...))
   360  	if err != nil {
   361  		return nv
   362  	}
   363  	r := bytes.NewReader(enc)
   364  	var version uint
   365  	if err := rlp.Decode(r, &version); err != nil {
   366  		log.Error("Failed to decode node value tracker", "id", id, "err", err)
   367  		return nv
   368  	}
   369  	if version != nvtVersion {
   370  		log.Error("Unknown NodeValueTracker version", "stored", version, "current", nvtVersion)
   371  		return nv
   372  	}
   373  	var nve nodeValueTrackerEncV1
   374  	if err := rlp.Decode(r, &nve); err != nil {
   375  		log.Error("Failed to decode node value tracker", "id", id, "err", err)
   376  		return nv
   377  	}
   378  	nv.rtStats = nve.RtStats
   379  	nv.lastRtStats = nve.RtStats
   380  	if int(nve.ServerBasketMapping) == vt.currentMapping {
   381  		nv.basket.basket = nve.ServerBasket
   382  	} else {
   383  		if nve.ServerBasketMapping >= uint(len(vt.mappings)) {
   384  			log.Error("Unknown request basket mapping", "stored", nve.ServerBasketMapping, "current", vt.currentMapping)
   385  			return nv
   386  		}
   387  		nv.basket.basket = nve.ServerBasket.convertMapping(vt.mappings[nve.ServerBasketMapping], vt.mappings[vt.currentMapping], vt.initRefBasket)
   388  	}
   389  	return nv
   390  }
   391  
   392  // saveNode saves a server node's value tracker to the database
   393  func (vt *ValueTracker) saveNode(id enode.ID, nv *NodeValueTracker) {
   394  	recentRtStats := nv.rtStats
   395  	recentRtStats.SubStats(&nv.lastRtStats)
   396  	vt.rtStats.AddStats(&recentRtStats)
   397  	nv.lastRtStats = nv.rtStats
   398  
   399  	nve := nodeValueTrackerEncV1{
   400  		RtStats:             nv.rtStats,
   401  		ServerBasketMapping: uint(vt.currentMapping),
   402  		ServerBasket:        nv.basket.basket,
   403  	}
   404  	enc1, err := rlp.EncodeToBytes(uint(nvtVersion))
   405  	if err != nil {
   406  		log.Error("Failed to encode service value information", "id", id, "err", err)
   407  		return
   408  	}
   409  	enc2, err := rlp.EncodeToBytes(&nve)
   410  	if err != nil {
   411  		log.Error("Failed to encode service value information", "id", id, "err", err)
   412  		return
   413  	}
   414  	if err := vt.db.Put(append(vtNodeKey, id[:]...), append(enc1, enc2...)); err != nil {
   415  		log.Error("Failed to save service value information", "id", id, "err", err)
   416  	}
   417  }
   418  
   419  // UpdateCosts updates the node value tracker's request cost table
   420  func (vt *ValueTracker) UpdateCosts(nv *NodeValueTracker, reqCosts []uint64) {
   421  	vt.lock.Lock()
   422  	defer vt.lock.Unlock()
   423  
   424  	nv.updateCosts(reqCosts, &vt.refBasket.reqValues, vt.refBasket.reqValueFactor(reqCosts))
   425  }
   426  
   427  // RtStats returns the global response time distribution statistics
   428  func (vt *ValueTracker) RtStats() ResponseTimeStats {
   429  	vt.lock.Lock()
   430  	defer vt.lock.Unlock()
   431  
   432  	vt.periodicUpdate()
   433  	return vt.rtStats
   434  }
   435  
   436  // periodicUpdate transfers individual node data to the global statistics, normalizes
   437  // the reference basket and updates request values. The global state is also saved to
   438  // the database with each update.
   439  func (vt *ValueTracker) periodicUpdate() {
   440  	now := vt.clock.Now()
   441  	vt.statsExpLock.Lock()
   442  	vt.statsExpFactor = utils.ExpFactor(vt.statsExpirer.LogOffset(now))
   443  	vt.statsExpLock.Unlock()
   444  
   445  	for _, nv := range vt.connected {
   446  		basket, rtStats := nv.transferStats(now, vt.transferRate)
   447  		vt.refBasket.add(basket)
   448  		vt.rtStats.AddStats(&rtStats)
   449  	}
   450  	vt.refBasket.normalize()
   451  	vt.refBasket.updateReqValues()
   452  	for _, nv := range vt.connected {
   453  		nv.updateCosts(nv.reqCosts, &vt.refBasket.reqValues, vt.refBasket.reqValueFactor(nv.reqCosts))
   454  	}
   455  	vt.saveToDb()
   456  }
   457  
   458  type ServedRequest struct {
   459  	ReqType, Amount uint32
   460  }
   461  
   462  // Served adds a served request to the node's statistics. An actual request may be composed
   463  // of one or more request types (service vector indices).
   464  func (vt *ValueTracker) Served(nv *NodeValueTracker, reqs []ServedRequest, respTime time.Duration) {
   465  	vt.statsExpLock.RLock()
   466  	expFactor := vt.statsExpFactor
   467  	vt.statsExpLock.RUnlock()
   468  
   469  	nv.lock.Lock()
   470  	defer nv.lock.Unlock()
   471  
   472  	var value float64
   473  	for _, r := range reqs {
   474  		nv.basket.add(r.ReqType, r.Amount, nv.reqCosts[r.ReqType]*uint64(r.Amount), expFactor)
   475  		value += (*nv.reqValues)[r.ReqType] * float64(r.Amount)
   476  	}
   477  	nv.rtStats.Add(respTime, value, vt.statsExpFactor)
   478  }
   479  
   480  type RequestStatsItem struct {
   481  	Name                string
   482  	ReqAmount, ReqValue float64
   483  }
   484  
   485  // RequestStats returns the current contents of the reference request basket, with
   486  // request values meaning average per request rather than total.
   487  func (vt *ValueTracker) RequestStats() []RequestStatsItem {
   488  	vt.statsExpLock.RLock()
   489  	expFactor := vt.statsExpFactor
   490  	vt.statsExpLock.RUnlock()
   491  	vt.lock.Lock()
   492  	defer vt.lock.Unlock()
   493  
   494  	vt.periodicUpdate()
   495  	res := make([]RequestStatsItem, len(vt.refBasket.basket.items))
   496  	for i, item := range vt.refBasket.basket.items {
   497  		res[i].Name = vt.mappings[vt.currentMapping][i]
   498  		res[i].ReqAmount = expFactor.Value(float64(item.amount)/basketFactor, vt.refBasket.basket.exp)
   499  		res[i].ReqValue = vt.refBasket.reqValues[i]
   500  	}
   501  	return res
   502  }
   503  
   504  // TotalServiceValue returns the total service value provided by the given node (as
   505  // a function of the weights which are calculated from the request timeout value).
   506  func (vt *ValueTracker) TotalServiceValue(nv *NodeValueTracker, weights ResponseTimeWeights) float64 {
   507  	vt.statsExpLock.RLock()
   508  	expFactor := vt.statsExpFactor
   509  	vt.statsExpLock.RUnlock()
   510  
   511  	nv.lock.Lock()
   512  	defer nv.lock.Unlock()
   513  
   514  	return nv.rtStats.Value(weights, expFactor)
   515  }