Add estimate fee and average block period prometheus metrics

This commit is contained in:
Martin Boehm 2022-12-10 00:22:52 +01:00 committed by Martin
parent aebc1c3495
commit 76a19b7f10
6 changed files with 94 additions and 25 deletions

View File

@ -2244,10 +2244,10 @@ type bitcoinTypeEstimatedFee struct {
lock sync.Mutex lock sync.Mutex
} }
const bitcoinTypeEstimatedFeeCacheSize = 300 const estimatedFeeCacheSize = 300
var bitcoinTypeEstimatedFeeCache [bitcoinTypeEstimatedFeeCacheSize]bitcoinTypeEstimatedFee var estimatedFeeCache [estimatedFeeCacheSize]bitcoinTypeEstimatedFee
var bitcoinTypeEstimatedFeeConservativeCache [bitcoinTypeEstimatedFeeCacheSize]bitcoinTypeEstimatedFee var estimatedFeeConservativeCache [estimatedFeeCacheSize]bitcoinTypeEstimatedFee
func (w *Worker) cachedBitcoinTypeEstimateFee(blocks int, conservative bool, s *bitcoinTypeEstimatedFee) (big.Int, error) { func (w *Worker) cachedBitcoinTypeEstimateFee(blocks int, conservative bool, s *bitcoinTypeEstimatedFee) (big.Int, error) {
s.lock.Lock() s.lock.Lock()
@ -2261,18 +2261,22 @@ func (w *Worker) cachedBitcoinTypeEstimateFee(blocks int, conservative bool, s *
if err == nil { if err == nil {
s.timestamp = time.Now().Unix() s.timestamp = time.Now().Unix()
s.fee = fee s.fee = fee
w.metrics.EstimatedFee.With(common.Labels{
"blocks": strconv.Itoa(blocks),
"conservative": strconv.FormatBool(conservative),
}).Set(float64(fee.Int64()))
} }
return fee, err 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 // it uses 10 second cache to reduce calls to the backend
func (w *Worker) BitcoinTypeEstimateFee(blocks int, conservative bool) (big.Int, error) { func (w *Worker) EstimateFee(blocks int, conservative bool) (big.Int, error) {
if blocks >= bitcoinTypeEstimatedFeeCacheSize { if blocks >= estimatedFeeCacheSize {
return w.chain.EstimateSmartFee(blocks, conservative) return w.chain.EstimateSmartFee(blocks, conservative)
} }
if 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])
} }

View File

@ -6,6 +6,8 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/golang/glog"
) )
const ( const (
@ -68,6 +70,7 @@ type InternalState struct {
BestHeight uint32 `json:"bestHeight"` BestHeight uint32 `json:"bestHeight"`
LastSync time.Time `json:"lastSync"` LastSync time.Time `json:"lastSync"`
BlockTimes []uint32 `json:"-"` BlockTimes []uint32 `json:"-"`
AvgBlockPeriod uint32 `json:"-"`
IsMempoolSynchronized bool `json:"isMempoolSynchronized"` IsMempoolSynchronized bool `json:"isMempoolSynchronized"`
MempoolSize int `json:"mempoolSize"` MempoolSize int `json:"mempoolSize"`
@ -77,7 +80,6 @@ type InternalState struct {
UtxoChecked bool `json:"utxoChecked"` UtxoChecked bool `json:"utxoChecked"`
// store only the historical state, not the current state of the fiat rates in DB
HasFiatRates bool `json:"-"` HasFiatRates bool `json:"-"`
HasTokenFiatRates bool `json:"-"` HasTokenFiatRates bool `json:"-"`
HistoricalFiatRatesTime time.Time `json:"historicalFiatRatesTime"` HistoricalFiatRatesTime time.Time `json:"historicalFiatRatesTime"`
@ -208,11 +210,23 @@ func (is *InternalState) GetBlockTime(height uint32) uint32 {
return 0 return 0
} }
// AppendBlockTime appends block time to BlockTimes // SetBlockTimes initializes BlockTimes array, returns AvgBlockPeriod
func (is *InternalState) AppendBlockTime(time uint32) { 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() is.mux.Lock()
defer is.mux.Unlock() defer is.mux.Unlock()
is.BlockTimes = append(is.BlockTimes, time) is.BlockTimes = append(is.BlockTimes, time)
is.computeAvgBlockPeriod()
return is.AvgBlockPeriod
} }
// RemoveLastBlockTimes removes last times from BlockTimes // RemoveLastBlockTimes removes last times from BlockTimes
@ -223,6 +237,7 @@ func (is *InternalState) RemoveLastBlockTimes(count int) {
count = len(is.BlockTimes) count = len(is.BlockTimes)
} }
is.BlockTimes = is.BlockTimes[:len(is.BlockTimes)-count] 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 // 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) 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 // SetBackendInfo sets new BackendInfo
func (is *InternalState) SetBackendInfo(bi *BackendInfo) { func (is *InternalState) SetBackendInfo(bi *BackendInfo) {
is.mux.Lock() is.mux.Lock()

View File

@ -24,6 +24,8 @@ type Metrics struct {
IndexDBSize prometheus.Gauge IndexDBSize prometheus.Gauge
ExplorerViews *prometheus.CounterVec ExplorerViews *prometheus.CounterVec
MempoolSize prometheus.Gauge MempoolSize prometheus.Gauge
EstimatedFee *prometheus.GaugeVec
AvgBlockPeriod prometheus.Gauge
DbColumnRows *prometheus.GaugeVec DbColumnRows *prometheus.GaugeVec
DbColumnSize *prometheus.GaugeVec DbColumnSize *prometheus.GaugeVec
BlockbookAppInfo *prometheus.GaugeVec BlockbookAppInfo *prometheus.GaugeVec
@ -169,6 +171,21 @@ func GetMetrics(coin string) (*Metrics, error) {
ConstLabels: Labels{"coin": coin}, 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( metrics.DbColumnRows = prometheus.NewGaugeVec(
prometheus.GaugeOpts{ prometheus.GaugeOpts{
Name: "blockbook_dbcolumn_rows", Name: "blockbook_dbcolumn_rows",
@ -209,7 +226,7 @@ func GetMetrics(coin string) (*Metrics, error) {
) )
metrics.ExplorerPendingRequests = prometheus.NewGaugeVec( metrics.ExplorerPendingRequests = prometheus.NewGaugeVec(
prometheus.GaugeOpts{ prometheus.GaugeOpts{
Name: "blockbook_explorer_pending_reqests", Name: "blockbook_explorer_pending_requests",
Help: "Number of unfinished requests in explorer interface", Help: "Number of unfinished requests in explorer interface",
ConstLabels: Labels{"coin": coin}, ConstLabels: Labels{"coin": coin},
}, },
@ -217,7 +234,7 @@ func GetMetrics(coin string) (*Metrics, error) {
) )
metrics.WebsocketPendingRequests = prometheus.NewGaugeVec( metrics.WebsocketPendingRequests = prometheus.NewGaugeVec(
prometheus.GaugeOpts{ prometheus.GaugeOpts{
Name: "blockbook_websocket_pending_reqests", Name: "blockbook_websocket_pending_requests",
Help: "Number of unfinished requests in websocket interface", Help: "Number of unfinished requests in websocket interface",
ConstLabels: Labels{"coin": coin}, ConstLabels: Labels{"coin": coin},
}, },
@ -225,7 +242,7 @@ func GetMetrics(coin string) (*Metrics, error) {
) )
metrics.SocketIOPendingRequests = prometheus.NewGaugeVec( metrics.SocketIOPendingRequests = prometheus.NewGaugeVec(
prometheus.GaugeOpts{ prometheus.GaugeOpts{
Name: "blockbook_socketio_pending_reqests", Name: "blockbook_socketio_pending_requests",
Help: "Number of unfinished requests in socketio interface", Help: "Number of unfinished requests in socketio interface",
ConstLabels: Labels{"coin": coin}, ConstLabels: Labels{"coin": coin},
}, },

View File

@ -403,11 +403,14 @@ func (b *BulkConnect) Close() error {
return err return err
} }
} }
var err error bt, err := b.d.loadBlockTimes()
b.d.is.BlockTimes, err = b.d.loadBlockTimes()
if err != nil { if err != nil {
return err 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 { if err := b.d.SetInconsistentState(false); err != nil {
return err return err

View File

@ -380,7 +380,10 @@ func (d *RocksDB) ConnectBlock(block *bchain.Block) error {
if err := d.WriteBatch(wb); err != nil { if err := d.WriteBatch(wb); err != nil {
return err 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 return nil
} }
@ -1631,7 +1634,6 @@ func (d *RocksDB) loadBlockTimes() ([]uint32, error) {
} }
times = append(times, time) times = append(times, time)
} }
glog.Info("loaded ", len(times), " block times")
return times, nil return times, nil
} }
@ -1699,10 +1701,15 @@ func (d *RocksDB) LoadInternalState(rpcCoin string) (*common.InternalState, erro
return nil, err return nil, err
} }
is.DbColumns = nc is.DbColumns = nc
is.BlockTimes, err = d.loadBlockTimes() bt, err := d.loadBlockTimes()
if err != nil { if err != nil {
return nil, err return nil, err
} }
avg := is.SetBlockTimes(bt)
if d.metrics != nil {
d.metrics.AvgBlockPeriod.Set(float64(avg))
}
// after load, reset the synchronization data // after load, reset the synchronization data
is.IsSynchronized = false is.IsSynchronized = false
is.IsMempoolSynchronized = false is.IsMempoolSynchronized = false

View File

@ -638,11 +638,15 @@ func (s *WebsocketServer) estimateFee(c *websocketChannel, params []byte) (inter
return nil, err return nil, err
} }
sg := strconv.FormatUint(gas, 10) sg := strconv.FormatUint(gas, 10)
for i, b := range r.Blocks { b := 1
fee, err := s.chain.EstimateSmartFee(b, true) if len(r.Blocks) > 0 {
if err != nil { b = r.Blocks[0]
return nil, err }
} 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].FeePerUnit = fee.String()
res[i].FeeLimit = sg res[i].FeeLimit = sg
fee.Mul(&fee, new(big.Int).SetUint64(gas)) 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 { for i, b := range r.Blocks {
fee, err := s.api.BitcoinTypeEstimateFee(b, conservative) fee, err := s.api.EstimateFee(b, conservative)
if err != nil { if err != nil {
return nil, err return nil, err
} }