Merge pull request #72 from asymmetric-research/bug-fix

Running fixes/updates
This commit is contained in:
Matt Johnstone 2024-11-08 17:40:31 +02:00 committed by GitHub
commit bb45ab3e76
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 92 additions and 78 deletions

View File

@ -61,29 +61,30 @@ The exporter is configured via the following command line arguments:
The table below describes all the metrics collected by the `solana-exporter`:
| Metric | Description | Labels |
|-------------------------------------|------------------------------------------------------------------------------------------|-------------------------------|
| `solana_validator_active_stake` | Active stake per validator. | `votekey`, `nodekey` |
| `solana_validator_last_vote` | Last voted-on slot per validator. | `votekey`, `nodekey` |
| `solana_validator_root_slot` | Root slot per validator. | `votekey`, `nodekey` |
| `solana_validator_delinquent` | Whether a validator is delinquent. | `votekey`, `nodekey` |
| `solana_account_balance` | Solana account balances. | `address` |
| `solana_node_version` | Node version of solana.* | `version` |
| `solana_node_is_healthy` | Whether the node is healthy.* | N/A |
| `solana_node_num_slots_behind` | The number of slots that the node is behind the latest cluster confirmed slot.* | N/A |
| `solana_node_minimum_ledger_slot` | The lowest slot that the node has information about in its ledger.* | N/A |
| `solana_node_first_available_block` | The slot of the lowest confirmed block that has not been purged from the node's ledger.* | N/A |
| `solana_node_total_transactions` | Total number of transactions processed without error since genesis.* | N/A |
| `solana_node_slot_height` | The current slot number.* | N/A |
| `solana_node_epoch_number` | The current epoch number.* | N/A |
| `solana_node_epoch_first_slot` | Current epoch's first slot \[inclusive\].* | N/A |
| `solana_node_epoch_last_slot` | Current epoch's last slot \[inclusive\].* | N/A |
| `solana_node_leader_slots` | Number of slots processed. | `status`, `nodekey` |
| `solana_node_leader_slots_by_epoch` | Number of slots processed. | `status`, `nodekey`, `epoch` |
| `solana_node_inflation_rewards` | Inflation reward earned. | `votekey`, `epoch` |
| `solana_node_fee_rewards` | Transaction fee rewards earned. | `nodekey`, `epoch` |
| `solana_node_block_size` | Number of transactions per block. | `nodekey`, `transaction_type` |
| `solana_node_block_height` | The current block height of the node.* | N/A |
| Metric | Description | Labels |
|------------------------------------------------|------------------------------------------------------------------------------------------|-------------------------------|
| `solana_validator_active_stake` | Active stake per validator. | `votekey`, `nodekey` |
| `solana_validator_last_vote` | Last voted-on slot per validator. | `votekey`, `nodekey` |
| `solana_validator_root_slot` | Root slot per validator. | `votekey`, `nodekey` |
| `solana_validator_delinquent` | Whether a validator is delinquent. | `votekey`, `nodekey` |
| `solana_account_balance` | Solana account balances. | `address` |
| `solana_node_version` | Node version of solana.* | `version` |
| `solana_node_is_healthy` | Whether the node is healthy.* | N/A |
| `solana_node_num_slots_behind` | The number of slots that the node is behind the latest cluster confirmed slot.* | N/A |
| `solana_node_minimum_ledger_slot` | The lowest slot that the node has information about in its ledger.* | N/A |
| `solana_node_first_available_block` | The slot of the lowest confirmed block that has not been purged from the node's ledger.* | N/A |
| `solana_node_transactions_total` | Total number of transactions processed without error since genesis.* | N/A |
| `solana_node_slot_height` | The current slot number.* | N/A |
| `solana_node_epoch_number` | The current epoch number.* | N/A |
| `solana_node_epoch_first_slot` | Current epoch's first slot \[inclusive\].* | N/A |
| `solana_node_epoch_last_slot` | Current epoch's last slot \[inclusive\].* | N/A |
| `solana_validator_leader_slots_total` | Number of slots processed. | `status`, `nodekey` |
| `solana_validator_leader_slots_by_epoch_total` | Number of slots processed per validator. | `status`, `nodekey`, `epoch` |
| `solana_cluster_slots_by_epoch_total` | Number of slots processed by the cluster. | `status`, `epoch` |
| `solana_validator_inflation_rewards` | Inflation reward earned. | `votekey`, `epoch` |
| `solana_validator_fee_rewards` | Transaction fee rewards earned. | `nodekey`, `epoch` |
| `solana_validator_block_size` | Number of transactions per block. | `nodekey`, `transaction_type` |
| `solana_node_block_height` | The current block height of the node.* | N/A |
***NOTE***: An `*` in the description indicates that the metric **is** tracked in `-light-mode`.

View File

@ -22,9 +22,6 @@ const (
StatusSkipped = "skipped"
StatusValid = "valid"
StateCurrent = "current"
StateDelinquent = "delinquent"
TransactionTypeVote = "vote"
TransactionTypeNonVote = "non_vote"
)

View File

@ -32,17 +32,18 @@ type SlotWatcher struct {
leaderSchedule map[string][]int64
// prometheus:
TotalTransactionsMetric prometheus.Gauge
SlotHeightMetric prometheus.Gauge
EpochNumberMetric prometheus.Gauge
EpochFirstSlotMetric prometheus.Gauge
EpochLastSlotMetric prometheus.Gauge
LeaderSlotsMetric *prometheus.CounterVec
LeaderSlotsByEpochMetric *prometheus.CounterVec
InflationRewardsMetric *prometheus.CounterVec
FeeRewardsMetric *prometheus.CounterVec
BlockSizeMetric *prometheus.GaugeVec
BlockHeightMetric prometheus.Gauge
TotalTransactionsMetric prometheus.Gauge
SlotHeightMetric prometheus.Gauge
EpochNumberMetric prometheus.Gauge
EpochFirstSlotMetric prometheus.Gauge
EpochLastSlotMetric prometheus.Gauge
LeaderSlotsMetric *prometheus.CounterVec
LeaderSlotsByEpochMetric *prometheus.CounterVec
ClusterSlotsByEpochMetric *prometheus.CounterVec
InflationRewardsMetric *prometheus.CounterVec
FeeRewardsMetric *prometheus.CounterVec
BlockSizeMetric *prometheus.GaugeVec
BlockHeightMetric prometheus.Gauge
}
func NewSlotWatcher(client *rpc.Client, config *ExporterConfig) *SlotWatcher {
@ -94,6 +95,16 @@ func NewSlotWatcher(client *rpc.Client, config *ExporterConfig) *SlotWatcher {
},
[]string{NodekeyLabel, EpochLabel, SkipStatusLabel},
),
ClusterSlotsByEpochMetric: prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "solana_cluster_slots_by_epoch_total",
Help: fmt.Sprintf(
"Number of slots processed by the cluster, grouped by %s ('%s' or '%s'), and %s",
SkipStatusLabel, StatusValid, StatusSkipped, EpochLabel,
),
},
[]string{EpochLabel, SkipStatusLabel},
),
InflationRewardsMetric: prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "solana_validator_inflation_rewards_total",
@ -320,11 +331,15 @@ func (c *SlotWatcher) fetchAndEmitBlockProduction(ctx context.Context, startSlot
c.LeaderSlotsMetric.WithLabelValues(address, StatusValid).Add(valid)
c.LeaderSlotsMetric.WithLabelValues(address, StatusSkipped).Add(skipped)
epochStr := toString(c.currentEpoch)
if slices.Contains(c.config.NodeKeys, address) || c.config.ComprehensiveSlotTracking {
epochStr := toString(c.currentEpoch)
c.LeaderSlotsByEpochMetric.WithLabelValues(address, epochStr, StatusValid).Add(valid)
c.LeaderSlotsByEpochMetric.WithLabelValues(address, epochStr, StatusSkipped).Add(skipped)
}
// additionally, track block production for the whole cluster:
c.ClusterSlotsByEpochMetric.WithLabelValues(epochStr, StatusValid).Add(valid)
c.ClusterSlotsByEpochMetric.WithLabelValues(epochStr, StatusSkipped).Add(skipped)
}
c.logger.Debugf("Fetched block production in [%v -> %v]", startSlot, endSlot)
@ -381,8 +396,9 @@ func (c *SlotWatcher) fetchAndEmitSingleBlockInfo(
return err
}
foundFeeReward := false
for _, reward := range block.Rewards {
if reward.RewardType == "fee" {
if strings.ToLower(reward.RewardType) == "fee" {
// make sure we haven't made a logic issue or something:
assertf(
reward.Pubkey == nodekey,
@ -390,11 +406,16 @@ func (c *SlotWatcher) fetchAndEmitSingleBlockInfo(
nodekey,
reward.Pubkey,
)
amount := float64(reward.Lamports) / float64(rpc.LamportsInSol)
amount := float64(reward.Lamports) / rpc.LamportsInSol
c.FeeRewardsMetric.WithLabelValues(nodekey, toString(epoch)).Add(amount)
foundFeeReward = true
}
}
if !foundFeeReward {
c.logger.Errorf("No fee reward for slot %d", slot)
}
// track block size:
if c.config.MonitorBlockSizes {
// now count and emit votes:
@ -424,7 +445,7 @@ func (c *SlotWatcher) fetchAndEmitInflationRewards(ctx context.Context, epoch in
for i, rewardInfo := range rewardInfos {
address := c.config.VoteKeys[i]
reward := float64(rewardInfo.Amount) / float64(rpc.LamportsInSol)
reward := float64(rewardInfo.Amount) / rpc.LamportsInSol
c.InflationRewardsMetric.WithLabelValues(address, toString(epoch)).Add(reward)
}
c.logger.Infof("Fetched inflation reward for epoch %v.", epoch)

View File

@ -7,7 +7,6 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/assert"
"regexp"
"testing"
"time"
)
@ -21,22 +20,6 @@ type slotMetricValues struct {
BlockHeight float64
}
// extractName takes a Prometheus descriptor and returns its name
func extractName(desc *prometheus.Desc) string {
// Get the string representation of the descriptor
descString := desc.String()
// Use regex to extract the metric name and help message from the descriptor string
reName := regexp.MustCompile(`fqName: "([^"]+)"`)
nameMatch := reName.FindStringSubmatch(descString)
var name string
if len(nameMatch) > 1 {
name = nameMatch[1]
}
return name
}
func getSlotMetricValues(watcher *SlotWatcher) slotMetricValues {
return slotMetricValues{
SlotHeight: testutil.ToFloat64(watcher.SlotHeightMetric),
@ -217,25 +200,40 @@ func TestSlotWatcher_WatchSlots_Dynamic(t *testing.T) {
if final.EpochNumber > initial.EpochNumber {
epochChanged = true
// run some tests for the previous epoch:
// run some tests for the previous epoch, starting with cluster as a whole:
epochStr := toString(initial.EpochNumber)
assert.Equalf(t,
float64(simulator.EpochSize*3/4),
testutil.ToFloat64(watcher.ClusterSlotsByEpochMetric.WithLabelValues(epochStr, StatusValid)),
"Incorrect %s cluster slots at epoch %s",
StatusValid, epochStr,
)
assert.Equalf(t,
float64(simulator.EpochSize*1/4),
testutil.ToFloat64(watcher.ClusterSlotsByEpochMetric.WithLabelValues(epochStr, StatusSkipped)),
"Incorrect %s cluster slots at epoch %s",
StatusSkipped, epochStr,
)
// now test per validator:
leaderSlotsPerEpoch := simulator.EpochSize / len(simulator.Nodekeys)
for i, nodekey := range simulator.Nodekeys {
// leader slots per epoch:
assert.Equalf(t,
float64(leaderSlotsPerEpoch*3/4),
testutil.ToFloat64(
watcher.LeaderSlotsByEpochMetric.WithLabelValues(nodekey, toString(initial.EpochNumber), StatusValid),
watcher.LeaderSlotsByEpochMetric.WithLabelValues(nodekey, epochStr, StatusValid),
),
"Incorrect %s leader slots for %s at epoch %v",
StatusValid, nodekey, initial.EpochNumber,
"Incorrect %s leader slots for %s at epoch %s",
StatusValid, nodekey, epochStr,
)
assert.Equalf(t,
float64(leaderSlotsPerEpoch*1/4),
testutil.ToFloat64(
watcher.LeaderSlotsByEpochMetric.WithLabelValues(nodekey, toString(initial.EpochNumber), StatusSkipped),
watcher.LeaderSlotsByEpochMetric.WithLabelValues(nodekey, epochStr, StatusSkipped),
),
"Incorrect %s leader slots for %s at epoch %v",
StatusSkipped, nodekey, initial.EpochNumber,
"Incorrect %s leader slots for %s at epoch %s",
StatusSkipped, nodekey, epochStr,
)
// inflation rewards:
@ -243,20 +241,20 @@ func TestSlotWatcher_WatchSlots_Dynamic(t *testing.T) {
assert.Equalf(t,
float64(simulator.InflationRewardLamports)/rpc.LamportsInSol,
testutil.ToFloat64(
watcher.InflationRewardsMetric.WithLabelValues(votekey, toString(initial.EpochNumber)),
watcher.InflationRewardsMetric.WithLabelValues(votekey, epochStr),
),
"Incorrect inflation reward for %s at epoch %v",
votekey, initial.EpochNumber,
"Incorrect inflation reward for %s at epoch %s",
votekey, epochStr,
)
// fee rewards:
assert.Equalf(t,
float64(simulator.FeeRewardLamports*leaderSlotsPerEpoch*3/4)/rpc.LamportsInSol,
testutil.ToFloat64(
watcher.FeeRewardsMetric.WithLabelValues(nodekey, toString(initial.EpochNumber)),
watcher.FeeRewardsMetric.WithLabelValues(nodekey, epochStr),
),
"Incorrect fee reward for %s at epoch %v",
nodekey, initial.EpochNumber,
"Incorrect fee reward for %s at epoch %s",
nodekey, epochStr,
)
}
}

File diff suppressed because one or more lines are too long

View File

@ -31,10 +31,6 @@ type (
Commitment string
)
func (c Commitment) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{"commitment": string(c)})
}
const (
// LamportsInSol is the number of lamports in 1 SOL (a billion)
LamportsInSol = 1_000_000_000
@ -85,7 +81,7 @@ func getResponse[T any](
if err != nil {
return fmt.Errorf("error processing %s rpc call: %w", method, err)
}
// log response:
// debug log response:
logger.Debugf("%s response: %v", method, string(body))
// unmarshal the response into the predicted format
@ -105,7 +101,8 @@ func getResponse[T any](
// See API docs: https://solana.com/docs/rpc/http/getepochinfo
func (c *Client) GetEpochInfo(ctx context.Context, commitment Commitment) (*EpochInfo, error) {
var resp Response[EpochInfo]
if err := getResponse(ctx, c, "getEpochInfo", []any{commitment}, &resp); err != nil {
config := map[string]string{"commitment": string(commitment)}
if err := getResponse(ctx, c, "getEpochInfo", []any{config}, &resp); err != nil {
return nil, err
}
return &resp.Result, nil