github.com/cryptogateway/go-paymex@v0.0.0-20210204174735-96277fb1e602/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/cryptogateway/go-paymex/common/mclock"
    27  	"github.com/cryptogateway/go-paymex/ethdb"
    28  	"github.com/cryptogateway/go-paymex/les/utils"
    29  	"github.com/cryptogateway/go-paymex/log"
    30  	"github.com/cryptogateway/go-paymex/p2p/enode"
    31  	"github.com/cryptogateway/go-paymex/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  // StatsExpirer returns the current expiration factor so that other values can be expired
   217  // with the same rate as the service value statistics.
   218  func (vt *ValueTracker) StatsExpFactor() utils.ExpirationFactor {
   219  	vt.statsExpLock.RLock()
   220  	defer vt.statsExpLock.RUnlock()
   221  
   222  	return vt.statsExpFactor
   223  }
   224  
   225  // loadFromDb loads the value tracker's state from the database and converts saved
   226  // request basket index mapping if it does not match the specified index to name mapping.
   227  func (vt *ValueTracker) loadFromDb(mapping []string) error {
   228  	enc, err := vt.db.Get(vtKey)
   229  	if err != nil {
   230  		return err
   231  	}
   232  	r := bytes.NewReader(enc)
   233  	var version uint
   234  	if err := rlp.Decode(r, &version); err != nil {
   235  		log.Error("Decoding value tracker state failed", "err", err)
   236  		return err
   237  	}
   238  	if version != vtVersion {
   239  		log.Error("Unknown ValueTracker version", "stored", version, "current", nvtVersion)
   240  		return fmt.Errorf("Unknown ValueTracker version %d (current version is %d)", version, vtVersion)
   241  	}
   242  	var vte valueTrackerEncV1
   243  	if err := rlp.Decode(r, &vte); err != nil {
   244  		log.Error("Decoding value tracker state failed", "err", err)
   245  		return err
   246  	}
   247  	logOffset := utils.Fixed64(vte.ExpOffset)
   248  	dt := time.Now().UnixNano() - int64(vte.SavedAt)
   249  	if dt > 0 {
   250  		logOffset += utils.Float64ToFixed64(float64(dt) * vt.offlineExpRate / math.Log(2))
   251  	}
   252  	vt.statsExpirer.SetLogOffset(vt.clock.Now(), logOffset)
   253  	vt.rtStats = vte.RtStats
   254  	vt.mappings = vte.Mappings
   255  	vt.currentMapping = -1
   256  loop:
   257  	for i, m := range vt.mappings {
   258  		if len(m) != len(mapping) {
   259  			continue loop
   260  		}
   261  		for j, s := range mapping {
   262  			if m[j] != s {
   263  				continue loop
   264  			}
   265  		}
   266  		vt.currentMapping = i
   267  		break
   268  	}
   269  	if vt.currentMapping == -1 {
   270  		vt.currentMapping = len(vt.mappings)
   271  		vt.mappings = append(vt.mappings, mapping)
   272  	}
   273  	if int(vte.RefBasketMapping) == vt.currentMapping {
   274  		vt.refBasket.basket = vte.RefBasket
   275  	} else {
   276  		if vte.RefBasketMapping >= uint(len(vt.mappings)) {
   277  			log.Error("Unknown request basket mapping", "stored", vte.RefBasketMapping, "current", vt.currentMapping)
   278  			return fmt.Errorf("Unknown request basket mapping %d (current version is %d)", vte.RefBasketMapping, vt.currentMapping)
   279  		}
   280  		vt.refBasket.basket = vte.RefBasket.convertMapping(vt.mappings[vte.RefBasketMapping], mapping, vt.initRefBasket)
   281  	}
   282  	return nil
   283  }
   284  
   285  // saveToDb saves the value tracker's state to the database
   286  func (vt *ValueTracker) saveToDb() {
   287  	vte := valueTrackerEncV1{
   288  		Mappings:         vt.mappings,
   289  		RefBasketMapping: uint(vt.currentMapping),
   290  		RefBasket:        vt.refBasket.basket,
   291  		RtStats:          vt.rtStats,
   292  		ExpOffset:        uint64(vt.statsExpirer.LogOffset(vt.clock.Now())),
   293  		SavedAt:          uint64(time.Now().UnixNano()),
   294  	}
   295  	enc1, err := rlp.EncodeToBytes(uint(vtVersion))
   296  	if err != nil {
   297  		log.Error("Encoding value tracker state failed", "err", err)
   298  		return
   299  	}
   300  	enc2, err := rlp.EncodeToBytes(&vte)
   301  	if err != nil {
   302  		log.Error("Encoding value tracker state failed", "err", err)
   303  		return
   304  	}
   305  	if err := vt.db.Put(vtKey, append(enc1, enc2...)); err != nil {
   306  		log.Error("Saving value tracker state failed", "err", err)
   307  	}
   308  }
   309  
   310  // Stop saves the value tracker's state and each loaded node's individual state and
   311  // returns after shutting the internal goroutines down.
   312  func (vt *ValueTracker) Stop() {
   313  	quit := make(chan struct{})
   314  	vt.quit <- quit
   315  	<-quit
   316  	vt.lock.Lock()
   317  	vt.periodicUpdate()
   318  	for id, nv := range vt.connected {
   319  		vt.saveNode(id, nv)
   320  	}
   321  	vt.connected = nil
   322  	vt.saveToDb()
   323  	vt.lock.Unlock()
   324  }
   325  
   326  // Register adds a server node to the value tracker
   327  func (vt *ValueTracker) Register(id enode.ID) *NodeValueTracker {
   328  	vt.lock.Lock()
   329  	defer vt.lock.Unlock()
   330  
   331  	if vt.connected == nil {
   332  		// ValueTracker has already been stopped
   333  		return nil
   334  	}
   335  	nv := vt.loadOrNewNode(id)
   336  	nv.init(vt.clock.Now(), &vt.refBasket.reqValues)
   337  	vt.connected[id] = nv
   338  	return nv
   339  }
   340  
   341  // Unregister removes a server node from the value tracker
   342  func (vt *ValueTracker) Unregister(id enode.ID) {
   343  	vt.lock.Lock()
   344  	defer vt.lock.Unlock()
   345  
   346  	if nv := vt.connected[id]; nv != nil {
   347  		vt.saveNode(id, nv)
   348  		delete(vt.connected, id)
   349  	}
   350  }
   351  
   352  // GetNode 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) GetNode(id enode.ID) *NodeValueTracker {
   355  	vt.lock.Lock()
   356  	defer vt.lock.Unlock()
   357  
   358  	return vt.loadOrNewNode(id)
   359  }
   360  
   361  // loadOrNewNode returns an individual server node's value tracker. If it did not exist before
   362  // then a new node is created.
   363  func (vt *ValueTracker) loadOrNewNode(id enode.ID) *NodeValueTracker {
   364  	if nv, ok := vt.connected[id]; ok {
   365  		return nv
   366  	}
   367  	nv := &NodeValueTracker{lastTransfer: vt.clock.Now()}
   368  	enc, err := vt.db.Get(append(vtNodeKey, id[:]...))
   369  	if err != nil {
   370  		return nv
   371  	}
   372  	r := bytes.NewReader(enc)
   373  	var version uint
   374  	if err := rlp.Decode(r, &version); err != nil {
   375  		log.Error("Failed to decode node value tracker", "id", id, "err", err)
   376  		return nv
   377  	}
   378  	if version != nvtVersion {
   379  		log.Error("Unknown NodeValueTracker version", "stored", version, "current", nvtVersion)
   380  		return nv
   381  	}
   382  	var nve nodeValueTrackerEncV1
   383  	if err := rlp.Decode(r, &nve); err != nil {
   384  		log.Error("Failed to decode node value tracker", "id", id, "err", err)
   385  		return nv
   386  	}
   387  	nv.rtStats = nve.RtStats
   388  	nv.lastRtStats = nve.RtStats
   389  	if int(nve.ServerBasketMapping) == vt.currentMapping {
   390  		nv.basket.basket = nve.ServerBasket
   391  	} else {
   392  		if nve.ServerBasketMapping >= uint(len(vt.mappings)) {
   393  			log.Error("Unknown request basket mapping", "stored", nve.ServerBasketMapping, "current", vt.currentMapping)
   394  			return nv
   395  		}
   396  		nv.basket.basket = nve.ServerBasket.convertMapping(vt.mappings[nve.ServerBasketMapping], vt.mappings[vt.currentMapping], vt.initRefBasket)
   397  	}
   398  	return nv
   399  }
   400  
   401  // saveNode saves a server node's value tracker to the database
   402  func (vt *ValueTracker) saveNode(id enode.ID, nv *NodeValueTracker) {
   403  	recentRtStats := nv.rtStats
   404  	recentRtStats.SubStats(&nv.lastRtStats)
   405  	vt.rtStats.AddStats(&recentRtStats)
   406  	nv.lastRtStats = nv.rtStats
   407  
   408  	nve := nodeValueTrackerEncV1{
   409  		RtStats:             nv.rtStats,
   410  		ServerBasketMapping: uint(vt.currentMapping),
   411  		ServerBasket:        nv.basket.basket,
   412  	}
   413  	enc1, err := rlp.EncodeToBytes(uint(nvtVersion))
   414  	if err != nil {
   415  		log.Error("Failed to encode service value information", "id", id, "err", err)
   416  		return
   417  	}
   418  	enc2, err := rlp.EncodeToBytes(&nve)
   419  	if err != nil {
   420  		log.Error("Failed to encode service value information", "id", id, "err", err)
   421  		return
   422  	}
   423  	if err := vt.db.Put(append(vtNodeKey, id[:]...), append(enc1, enc2...)); err != nil {
   424  		log.Error("Failed to save service value information", "id", id, "err", err)
   425  	}
   426  }
   427  
   428  // UpdateCosts updates the node value tracker's request cost table
   429  func (vt *ValueTracker) UpdateCosts(nv *NodeValueTracker, reqCosts []uint64) {
   430  	vt.lock.Lock()
   431  	defer vt.lock.Unlock()
   432  
   433  	nv.updateCosts(reqCosts, &vt.refBasket.reqValues, vt.refBasket.reqValueFactor(reqCosts))
   434  }
   435  
   436  // RtStats returns the global response time distribution statistics
   437  func (vt *ValueTracker) RtStats() ResponseTimeStats {
   438  	vt.lock.Lock()
   439  	defer vt.lock.Unlock()
   440  
   441  	vt.periodicUpdate()
   442  	return vt.rtStats
   443  }
   444  
   445  // periodicUpdate transfers individual node data to the global statistics, normalizes
   446  // the reference basket and updates request values. The global state is also saved to
   447  // the database with each update.
   448  func (vt *ValueTracker) periodicUpdate() {
   449  	now := vt.clock.Now()
   450  	vt.statsExpLock.Lock()
   451  	vt.statsExpFactor = utils.ExpFactor(vt.statsExpirer.LogOffset(now))
   452  	vt.statsExpLock.Unlock()
   453  
   454  	for _, nv := range vt.connected {
   455  		basket, rtStats := nv.transferStats(now, vt.transferRate)
   456  		vt.refBasket.add(basket)
   457  		vt.rtStats.AddStats(&rtStats)
   458  	}
   459  	vt.refBasket.normalize()
   460  	vt.refBasket.updateReqValues()
   461  	for _, nv := range vt.connected {
   462  		nv.updateCosts(nv.reqCosts, &vt.refBasket.reqValues, vt.refBasket.reqValueFactor(nv.reqCosts))
   463  	}
   464  	vt.saveToDb()
   465  }
   466  
   467  type ServedRequest struct {
   468  	ReqType, Amount uint32
   469  }
   470  
   471  // Served adds a served request to the node's statistics. An actual request may be composed
   472  // of one or more request types (service vector indices).
   473  func (vt *ValueTracker) Served(nv *NodeValueTracker, reqs []ServedRequest, respTime time.Duration) {
   474  	vt.statsExpLock.RLock()
   475  	expFactor := vt.statsExpFactor
   476  	vt.statsExpLock.RUnlock()
   477  
   478  	nv.lock.Lock()
   479  	defer nv.lock.Unlock()
   480  
   481  	var value float64
   482  	for _, r := range reqs {
   483  		nv.basket.add(r.ReqType, r.Amount, nv.reqCosts[r.ReqType]*uint64(r.Amount), expFactor)
   484  		value += (*nv.reqValues)[r.ReqType] * float64(r.Amount)
   485  	}
   486  	nv.rtStats.Add(respTime, value, vt.statsExpFactor)
   487  }
   488  
   489  type RequestStatsItem struct {
   490  	Name                string
   491  	ReqAmount, ReqValue float64
   492  }
   493  
   494  // RequestStats returns the current contents of the reference request basket, with
   495  // request values meaning average per request rather than total.
   496  func (vt *ValueTracker) RequestStats() []RequestStatsItem {
   497  	vt.statsExpLock.RLock()
   498  	expFactor := vt.statsExpFactor
   499  	vt.statsExpLock.RUnlock()
   500  	vt.lock.Lock()
   501  	defer vt.lock.Unlock()
   502  
   503  	vt.periodicUpdate()
   504  	res := make([]RequestStatsItem, len(vt.refBasket.basket.items))
   505  	for i, item := range vt.refBasket.basket.items {
   506  		res[i].Name = vt.mappings[vt.currentMapping][i]
   507  		res[i].ReqAmount = expFactor.Value(float64(item.amount)/basketFactor, vt.refBasket.basket.exp)
   508  		res[i].ReqValue = vt.refBasket.reqValues[i]
   509  	}
   510  	return res
   511  }