github.com/core-coin/go-core/v2@v2.1.9/les/lespay/client/valuetracker.go (about)

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