From 76a19b7f1040c4b96172ca98f50bbd9faddfdb85 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Sat, 10 Dec 2022 00:22:52 +0100 Subject: [PATCH] Add estimate fee and average block period prometheus metrics --- api/worker.go | 20 ++++++++++++-------- common/internalstate.go | 40 +++++++++++++++++++++++++++++++++++++--- common/metrics.go | 23 ++++++++++++++++++++--- db/bulkconnect.go | 7 +++++-- db/rocksdb.go | 13 ++++++++++--- server/websocket.go | 16 ++++++++++------ 6 files changed, 94 insertions(+), 25 deletions(-) diff --git a/api/worker.go b/api/worker.go index e22904a2..255e99df 100644 --- a/api/worker.go +++ b/api/worker.go @@ -2244,10 +2244,10 @@ type bitcoinTypeEstimatedFee struct { lock sync.Mutex } -const bitcoinTypeEstimatedFeeCacheSize = 300 +const estimatedFeeCacheSize = 300 -var bitcoinTypeEstimatedFeeCache [bitcoinTypeEstimatedFeeCacheSize]bitcoinTypeEstimatedFee -var bitcoinTypeEstimatedFeeConservativeCache [bitcoinTypeEstimatedFeeCacheSize]bitcoinTypeEstimatedFee +var estimatedFeeCache [estimatedFeeCacheSize]bitcoinTypeEstimatedFee +var estimatedFeeConservativeCache [estimatedFeeCacheSize]bitcoinTypeEstimatedFee func (w *Worker) cachedBitcoinTypeEstimateFee(blocks int, conservative bool, s *bitcoinTypeEstimatedFee) (big.Int, error) { s.lock.Lock() @@ -2261,18 +2261,22 @@ func (w *Worker) cachedBitcoinTypeEstimateFee(blocks int, conservative bool, s * if err == nil { s.timestamp = time.Now().Unix() s.fee = fee + w.metrics.EstimatedFee.With(common.Labels{ + "blocks": strconv.Itoa(blocks), + "conservative": strconv.FormatBool(conservative), + }).Set(float64(fee.Int64())) } return fee, err } -// BitcoinTypeEstimateFee returns a fee estimation for given number of blocks +// EstimateFee returns a fee estimation for given number of blocks // it uses 10 second cache to reduce calls to the backend -func (w *Worker) BitcoinTypeEstimateFee(blocks int, conservative bool) (big.Int, error) { - if blocks >= bitcoinTypeEstimatedFeeCacheSize { +func (w *Worker) EstimateFee(blocks int, conservative bool) (big.Int, error) { + if blocks >= estimatedFeeCacheSize { return w.chain.EstimateSmartFee(blocks, conservative) } if conservative { - return w.cachedBitcoinTypeEstimateFee(blocks, conservative, &bitcoinTypeEstimatedFeeConservativeCache[blocks]) + return w.cachedBitcoinTypeEstimateFee(blocks, conservative, &estimatedFeeConservativeCache[blocks]) } - return w.cachedBitcoinTypeEstimateFee(blocks, conservative, &bitcoinTypeEstimatedFeeCache[blocks]) + return w.cachedBitcoinTypeEstimateFee(blocks, conservative, &estimatedFeeCache[blocks]) } diff --git a/common/internalstate.go b/common/internalstate.go index 701506d6..7651cf47 100644 --- a/common/internalstate.go +++ b/common/internalstate.go @@ -6,6 +6,8 @@ import ( "sync" "sync/atomic" "time" + + "github.com/golang/glog" ) const ( @@ -68,6 +70,7 @@ type InternalState struct { BestHeight uint32 `json:"bestHeight"` LastSync time.Time `json:"lastSync"` BlockTimes []uint32 `json:"-"` + AvgBlockPeriod uint32 `json:"-"` IsMempoolSynchronized bool `json:"isMempoolSynchronized"` MempoolSize int `json:"mempoolSize"` @@ -77,7 +80,6 @@ type InternalState struct { UtxoChecked bool `json:"utxoChecked"` - // store only the historical state, not the current state of the fiat rates in DB HasFiatRates bool `json:"-"` HasTokenFiatRates bool `json:"-"` HistoricalFiatRatesTime time.Time `json:"historicalFiatRatesTime"` @@ -208,11 +210,23 @@ func (is *InternalState) GetBlockTime(height uint32) uint32 { return 0 } -// AppendBlockTime appends block time to BlockTimes -func (is *InternalState) AppendBlockTime(time uint32) { +// SetBlockTimes initializes BlockTimes array, returns AvgBlockPeriod +func (is *InternalState) SetBlockTimes(blockTimes []uint32) uint32 { + is.mux.Lock() + defer is.mux.Unlock() + is.BlockTimes = blockTimes + is.computeAvgBlockPeriod() + glog.Info("set ", len(is.BlockTimes), " block times, average block period ", is.AvgBlockPeriod, "s") + return is.AvgBlockPeriod +} + +// AppendBlockTime appends block time to BlockTimes, returns AvgBlockPeriod +func (is *InternalState) AppendBlockTime(time uint32) uint32 { is.mux.Lock() defer is.mux.Unlock() is.BlockTimes = append(is.BlockTimes, time) + is.computeAvgBlockPeriod() + return is.AvgBlockPeriod } // RemoveLastBlockTimes removes last times from BlockTimes @@ -223,6 +237,7 @@ func (is *InternalState) RemoveLastBlockTimes(count int) { count = len(is.BlockTimes) } is.BlockTimes = is.BlockTimes[:len(is.BlockTimes)-count] + is.computeAvgBlockPeriod() } // GetBlockHeightOfTime returns block height of the first block with time greater or equal to the given time or MaxUint32 if no such block @@ -246,6 +261,25 @@ func (is *InternalState) GetBlockHeightOfTime(time uint32) uint32 { return uint32(height) } +const avgBlockPeriodSample = 100 + +// Avg100BlocksPeriod returns average period of the last 100 blocks in seconds +func (is *InternalState) GetAvgBlockPeriod() uint32 { + is.mux.Lock() + defer is.mux.Unlock() + return is.AvgBlockPeriod +} + +// computeAvgBlockPeriod returns computes average of the last 100 blocks in seconds +func (is *InternalState) computeAvgBlockPeriod() { + last := len(is.BlockTimes) - 1 + first := last - avgBlockPeriodSample - 1 + if first < 0 { + return + } + is.AvgBlockPeriod = (is.BlockTimes[last] - is.BlockTimes[first]) / avgBlockPeriodSample +} + // SetBackendInfo sets new BackendInfo func (is *InternalState) SetBackendInfo(bi *BackendInfo) { is.mux.Lock() diff --git a/common/metrics.go b/common/metrics.go index 10d1abb7..1769cbf1 100644 --- a/common/metrics.go +++ b/common/metrics.go @@ -24,6 +24,8 @@ type Metrics struct { IndexDBSize prometheus.Gauge ExplorerViews *prometheus.CounterVec MempoolSize prometheus.Gauge + EstimatedFee *prometheus.GaugeVec + AvgBlockPeriod prometheus.Gauge DbColumnRows *prometheus.GaugeVec DbColumnSize *prometheus.GaugeVec BlockbookAppInfo *prometheus.GaugeVec @@ -169,6 +171,21 @@ func GetMetrics(coin string) (*Metrics, error) { ConstLabels: Labels{"coin": coin}, }, ) + metrics.EstimatedFee = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "blockbook_estimated_fee", + Help: "Estimated fee per byte (gas) for number of blocks", + ConstLabels: Labels{"coin": coin}, + }, + []string{"blocks", "conservative"}, + ) + metrics.AvgBlockPeriod = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "blockbook_avg_block_period", + Help: "Average period of mining of last 100 blocks in seconds", + ConstLabels: Labels{"coin": coin}, + }, + ) metrics.DbColumnRows = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: "blockbook_dbcolumn_rows", @@ -209,7 +226,7 @@ func GetMetrics(coin string) (*Metrics, error) { ) metrics.ExplorerPendingRequests = prometheus.NewGaugeVec( prometheus.GaugeOpts{ - Name: "blockbook_explorer_pending_reqests", + Name: "blockbook_explorer_pending_requests", Help: "Number of unfinished requests in explorer interface", ConstLabels: Labels{"coin": coin}, }, @@ -217,7 +234,7 @@ func GetMetrics(coin string) (*Metrics, error) { ) metrics.WebsocketPendingRequests = prometheus.NewGaugeVec( prometheus.GaugeOpts{ - Name: "blockbook_websocket_pending_reqests", + Name: "blockbook_websocket_pending_requests", Help: "Number of unfinished requests in websocket interface", ConstLabels: Labels{"coin": coin}, }, @@ -225,7 +242,7 @@ func GetMetrics(coin string) (*Metrics, error) { ) metrics.SocketIOPendingRequests = prometheus.NewGaugeVec( prometheus.GaugeOpts{ - Name: "blockbook_socketio_pending_reqests", + Name: "blockbook_socketio_pending_requests", Help: "Number of unfinished requests in socketio interface", ConstLabels: Labels{"coin": coin}, }, diff --git a/db/bulkconnect.go b/db/bulkconnect.go index 5238aa2f..b510fe3b 100644 --- a/db/bulkconnect.go +++ b/db/bulkconnect.go @@ -403,11 +403,14 @@ func (b *BulkConnect) Close() error { return err } } - var err error - b.d.is.BlockTimes, err = b.d.loadBlockTimes() + bt, err := b.d.loadBlockTimes() if err != nil { return err } + avg := b.d.is.SetBlockTimes(bt) + if b.d.metrics != nil { + b.d.metrics.AvgBlockPeriod.Set(float64(avg)) + } if err := b.d.SetInconsistentState(false); err != nil { return err diff --git a/db/rocksdb.go b/db/rocksdb.go index 84bdaa6b..181ce35e 100644 --- a/db/rocksdb.go +++ b/db/rocksdb.go @@ -380,7 +380,10 @@ func (d *RocksDB) ConnectBlock(block *bchain.Block) error { if err := d.WriteBatch(wb); err != nil { return err } - d.is.AppendBlockTime(uint32(block.Time)) + avg := d.is.AppendBlockTime(uint32(block.Time)) + if d.metrics != nil { + d.metrics.AvgBlockPeriod.Set(float64(avg)) + } return nil } @@ -1631,7 +1634,6 @@ func (d *RocksDB) loadBlockTimes() ([]uint32, error) { } times = append(times, time) } - glog.Info("loaded ", len(times), " block times") return times, nil } @@ -1699,10 +1701,15 @@ func (d *RocksDB) LoadInternalState(rpcCoin string) (*common.InternalState, erro return nil, err } is.DbColumns = nc - is.BlockTimes, err = d.loadBlockTimes() + bt, err := d.loadBlockTimes() if err != nil { return nil, err } + avg := is.SetBlockTimes(bt) + if d.metrics != nil { + d.metrics.AvgBlockPeriod.Set(float64(avg)) + } + // after load, reset the synchronization data is.IsSynchronized = false is.IsMempoolSynchronized = false diff --git a/server/websocket.go b/server/websocket.go index 4d17b432..facaaa42 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -638,11 +638,15 @@ func (s *WebsocketServer) estimateFee(c *websocketChannel, params []byte) (inter return nil, err } sg := strconv.FormatUint(gas, 10) - for i, b := range r.Blocks { - fee, err := s.chain.EstimateSmartFee(b, true) - if err != nil { - return nil, err - } + b := 1 + if len(r.Blocks) > 0 { + b = r.Blocks[0] + } + fee, err := s.api.EstimateFee(b, true) + if err != nil { + return nil, err + } + for i := range r.Blocks { res[i].FeePerUnit = fee.String() res[i].FeeLimit = sg fee.Mul(&fee, new(big.Int).SetUint64(gas)) @@ -666,7 +670,7 @@ func (s *WebsocketServer) estimateFee(c *websocketChannel, params []byte) (inter } } for i, b := range r.Blocks { - fee, err := s.api.BitcoinTypeEstimateFee(b, conservative) + fee, err := s.api.EstimateFee(b, conservative) if err != nil { return nil, err }