added inflation rewards metric

This commit is contained in:
Matt Johnstone 2024-10-07 12:09:45 +02:00
parent 68a3a29280
commit 172d15a309
No known key found for this signature in database
GPG Key ID: BE985FBB9BE7D3BB
4 changed files with 98 additions and 24 deletions

View File

@ -14,17 +14,28 @@ import (
)
var (
httpTimeout = 60 * time.Second
rpcAddr = flag.String("rpcURI", "", "Solana RPC URI (including protocol and path)")
addr = flag.String("addr", ":8080", "Listen address")
votePubkey = flag.String("votepubkey", "", "Validator vote address (will only return results of this address)")
httpTimeoutSecs = flag.Int("http_timeout", 60, "HTTP timeout in seconds")
balanceAddresses = flag.String("balance-addresses", "", "Comma-separated list of addresses to monitor balances")
httpTimeout = 60 * time.Second
rpcAddr = flag.String("rpcURI", "", "Solana RPC URI (including protocol and path)")
addr = flag.String("addr", ":8080", "Listen address")
votePubkey = flag.String("votepubkey", "", "Validator vote address (will only return results of this address)")
httpTimeoutSecs = flag.Int("http_timeout", 60, "HTTP timeout in seconds")
// addresses:
balanceAddresses = flag.String(
"balance-addresses",
"",
"Comma-separated list of addresses to monitor SOL balances.",
)
leaderSlotAddresses = flag.String(
"leader-slot-addresses",
"",
"Comma-separated list of addresses to monitor leader slots by epoch for, leave nil to track by epoch for all validators (this creates a lot of Prometheus metrics with every new epoch).",
)
inflationRewardAddresses = flag.String(
"inflation-reward-addresses",
"",
"Comma-separated list of validator vote accounts to track inflationary rewards for",
)
)
func init() {
@ -35,9 +46,10 @@ type solanaCollector struct {
rpcClient rpc.Provider
// config:
slotPace time.Duration
balanceAddresses []string
leaderSlotAddresses []string
slotPace time.Duration
balanceAddresses []string
leaderSlotAddresses []string
inflationRewardAddresses []string
/// descriptors:
totalValidatorsDesc *prometheus.Desc
@ -50,13 +62,18 @@ type solanaCollector struct {
}
func createSolanaCollector(
provider rpc.Provider, slotPace time.Duration, balanceAddresses []string, leaderSlotAddresses []string,
provider rpc.Provider,
slotPace time.Duration,
balanceAddresses []string,
leaderSlotAddresses []string,
inflationRewardAddresses []string,
) *solanaCollector {
return &solanaCollector{
rpcClient: provider,
slotPace: slotPace,
balanceAddresses: balanceAddresses,
leaderSlotAddresses: leaderSlotAddresses,
rpcClient: provider,
slotPace: slotPace,
balanceAddresses: balanceAddresses,
leaderSlotAddresses: leaderSlotAddresses,
inflationRewardAddresses: inflationRewardAddresses,
totalValidatorsDesc: prometheus.NewDesc(
"solana_active_validators",
"Total number of active validators by state",
@ -102,8 +119,12 @@ func createSolanaCollector(
}
}
func NewSolanaCollector(rpcAddr string, balanceAddresses []string, leaderSlotAddresses []string) *solanaCollector {
return createSolanaCollector(rpc.NewRPCClient(rpcAddr), slotPacerSchedule, balanceAddresses, leaderSlotAddresses)
func NewSolanaCollector(
rpcAddr string, balanceAddresses []string, leaderSlotAddresses []string, inflationRewardAddresses []string,
) *solanaCollector {
return createSolanaCollector(
rpc.NewRPCClient(rpcAddr), slotPacerSchedule, balanceAddresses, leaderSlotAddresses, inflationRewardAddresses,
)
}
func (c *solanaCollector) Describe(ch chan<- *prometheus.Desc) {
@ -233,6 +254,7 @@ func main() {
var (
balAddresses []string
lsAddresses []string
irAddresses []string
)
if *balanceAddresses != "" {
balAddresses = strings.Split(*balanceAddresses, ",")
@ -240,8 +262,11 @@ func main() {
if *leaderSlotAddresses != "" {
lsAddresses = strings.Split(*leaderSlotAddresses, ",")
}
if *inflationRewardAddresses != "" {
irAddresses = strings.Split(*inflationRewardAddresses, ",")
}
collector := NewSolanaCollector(*rpcAddr, balAddresses, lsAddresses)
collector := NewSolanaCollector(*rpcAddr, balAddresses, lsAddresses, irAddresses)
slotWatcher := NewCollectorSlotWatcher(collector)
go slotWatcher.WatchSlots(context.Background(), collector.slotPace)

View File

@ -42,6 +42,7 @@ type (
var (
identities = []string{"aaa", "bbb", "ccc"}
votekeys = []string{"AAA", "BBB", "CCC"}
balances = map[string]float64{"aaa": 1, "bbb": 2, "ccc": 3}
identityVotes = map[string]string{"aaa": "AAA", "bbb": "BBB", "ccc": "CCC"}
nv = len(identities)
@ -375,7 +376,7 @@ func runCollectionTests(t *testing.T, collector prometheus.Collector, testCases
}
func TestSolanaCollector_Collect_Static(t *testing.T) {
collector := createSolanaCollector(&staticRPCClient{}, slotPacerSchedule, identities, []string{})
collector := createSolanaCollector(&staticRPCClient{}, slotPacerSchedule, identities, []string{}, votekeys)
prometheus.NewPedanticRegistry().MustRegister(collector)
testCases := []collectionTest{
@ -453,7 +454,7 @@ solana_account_balance{address="ccc"} 3
func TestSolanaCollector_Collect_Dynamic(t *testing.T) {
client := newDynamicRPCClient()
collector := createSolanaCollector(client, slotPacerSchedule, identities, []string{})
collector := createSolanaCollector(client, slotPacerSchedule, identities, []string{}, votekeys)
prometheus.NewPedanticRegistry().MustRegister(collector)
// start off by testing initial state:

View File

@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"slices"
"time"
"github.com/asymmetric-research/solana_exporter/pkg/rpc"
@ -17,7 +18,9 @@ const (
type SlotWatcher struct {
client rpc.Provider
leaderSlotAddresses []string
// config:
leaderSlotAddresses []string
inflationRewardAddresses []string
// currentEpoch is the current epoch we are watching
currentEpoch int64
@ -70,10 +73,22 @@ var (
},
[]string{"status", "nodekey", "epoch"},
)
inflationRewards = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "solana_inflation_rewards",
Help: "Inflation reward earned per leader, per epoch",
},
[]string{"votekey", "epoch"},
)
)
func NewCollectorSlotWatcher(collector *solanaCollector) *SlotWatcher {
return &SlotWatcher{client: collector.rpcClient, leaderSlotAddresses: collector.leaderSlotAddresses}
return &SlotWatcher{
client: collector.rpcClient,
leaderSlotAddresses: collector.leaderSlotAddresses,
inflationRewardAddresses: collector.inflationRewardAddresses,
}
}
func init() {
@ -84,6 +99,7 @@ func init() {
prometheus.MustRegister(epochLastSlot)
prometheus.MustRegister(leaderSlotsTotal)
prometheus.MustRegister(leaderSlotsByEpoch)
prometheus.MustRegister(inflationRewards)
}
func (c *SlotWatcher) WatchSlots(ctx context.Context, pace time.Duration) {
@ -123,6 +139,13 @@ func (c *SlotWatcher) WatchSlots(ctx context.Context, pace time.Duration) {
}
if epochInfo.Epoch > c.currentEpoch {
// if we have configured inflation reward addresses, fetch em
if len(c.inflationRewardAddresses) > 0 {
err = c.fetchAndEmitInflationRewards(ctx, c.currentEpoch)
if err != nil {
klog.Errorf("Failed to emit inflation rewards, bailing out: %v", err)
}
}
c.closeCurrentEpoch(ctx, epochInfo)
}
@ -244,3 +267,28 @@ func getEpochBounds(info *rpc.EpochInfo) (int64, int64) {
firstSlot := info.AbsoluteSlot - info.SlotIndex
return firstSlot, firstSlot + info.SlotsInEpoch - 1
}
// fetchAndEmitInflationRewards fetches and emits the inflation rewards for the configured inflationRewardAddresses
// at the provided epoch
func (c *SlotWatcher) fetchAndEmitInflationRewards(ctx context.Context, epoch int64) error {
epochStr := fmt.Sprintf("%d", epoch)
klog.Infof("Fetching inflation reward for epoch %v ...", epochStr)
ctx, cancel := context.WithTimeout(ctx, httpTimeout)
defer cancel()
rewardInfos, err := c.client.GetInflationReward(
ctx, c.inflationRewardAddresses, rpc.CommitmentFinalized, &epoch, nil,
)
if err != nil {
return err
}
for i, rewardInfo := range rewardInfos {
address := c.inflationRewardAddresses[i]
reward := float64(rewardInfo.Amount) / float64(rpc.LamportsInSol)
inflationRewards.WithLabelValues(address, epochStr).Set(reward)
}
klog.Infof("Fetched inflation reward for epoch %v.", epochStr)
return nil
}

View File

@ -91,7 +91,7 @@ func TestSolanaCollector_WatchSlots_Static(t *testing.T) {
leaderSlotsTotal.Reset()
leaderSlotsByEpoch.Reset()
collector := createSolanaCollector(&staticRPCClient{}, 100*time.Millisecond, identities, []string{})
collector := createSolanaCollector(&staticRPCClient{}, 100*time.Millisecond, identities, []string{}, votekeys)
watcher := NewCollectorSlotWatcher(collector)
prometheus.NewPedanticRegistry().MustRegister(collector)
ctx, cancel := context.WithCancel(context.Background())
@ -145,8 +145,8 @@ func TestSolanaCollector_WatchSlots_Dynamic(t *testing.T) {
// create clients:
client := newDynamicRPCClient()
collector := createSolanaCollector(client, 300*time.Millisecond, identities, []string{})
watcher := SlotWatcher{client: client}
collector := createSolanaCollector(client, 300*time.Millisecond, identities, []string{}, votekeys)
watcher := NewCollectorSlotWatcher(collector)
prometheus.NewPedanticRegistry().MustRegister(collector)
// start client/collector and wait a bit: