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