From f410d9893da4c51eca44cb9cace997ae93068c64 Mon Sep 17 00:00:00 2001 From: "l.subbotin" Date: Fri, 18 Oct 2024 21:05:47 +0200 Subject: [PATCH 1/4] Introduced additional identity label to metrics. --- cmd/solana_exporter/exporter.go | 22 +++++++++++++--------- cmd/solana_exporter/exporter_test.go | 5 +++-- pkg/rpc/client.go | 11 +++++++++++ pkg/rpc/responses.go | 3 +++ 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/cmd/solana_exporter/exporter.go b/cmd/solana_exporter/exporter.go index 51b092a..b5273d3 100644 --- a/cmd/solana_exporter/exporter.go +++ b/cmd/solana_exporter/exporter.go @@ -20,6 +20,7 @@ const ( VersionLabel = "version" AddressLabel = "address" EpochLabel = "epoch" + IdentityLabel = "identity" StatusSkipped = "skipped" StatusValid = "valid" @@ -38,6 +39,7 @@ type SolanaCollector struct { // config: slotPace time.Duration balanceAddresses []string + identity *string /// descriptors: totalValidatorsDesc *prometheus.Desc @@ -51,13 +53,12 @@ type SolanaCollector struct { numSlotsBehind *prometheus.Desc } -func NewSolanaCollector( - provider rpc.Provider, slotPace time.Duration, balanceAddresses []string, nodekeys []string, votekeys []string, -) *SolanaCollector { +func NewSolanaCollector(provider rpc.Provider, slotPace time.Duration, balanceAddresses []string, nodekeys []string, votekeys []string, identity *string) *SolanaCollector { collector := &SolanaCollector{ rpcClient: provider, slotPace: slotPace, balanceAddresses: CombineUnique(balanceAddresses, nodekeys, votekeys), + identity: identity, totalValidatorsDesc: prometheus.NewDesc( "solana_active_validators", "Total number of active validators by state", @@ -103,13 +104,13 @@ func NewSolanaCollector( isHealthy: prometheus.NewDesc( "solana_is_healthy", "Whether the node is healthy or not.", - nil, + []string{IdentityLabel}, nil, ), numSlotsBehind: prometheus.NewDesc( "solana_num_slots_behind", "The number of slots that the node is behind the latest cluster confirmed slot.", - nil, + []string{IdentityLabel}, nil, ), } @@ -241,8 +242,8 @@ func (c *SolanaCollector) collectHealth(ctx context.Context, ch chan<- prometheu } } - ch <- prometheus.MustNewConstMetric(c.isHealthy, prometheus.GaugeValue, float64(isHealthy)) - ch <- prometheus.MustNewConstMetric(c.numSlotsBehind, prometheus.GaugeValue, float64(numSlotsBehind)) + ch <- prometheus.MustNewConstMetric(c.isHealthy, prometheus.GaugeValue, float64(isHealthy), *c.identity) + ch <- prometheus.MustNewConstMetric(c.numSlotsBehind, prometheus.GaugeValue, float64(numSlotsBehind), *c.identity) return } @@ -273,8 +274,11 @@ func main() { if err != nil { klog.Fatalf("Failed to get associated vote accounts for %v: %v", config.NodeKeys, err) } - - collector := NewSolanaCollector(client, slotPacerSchedule, config.BalanceAddresses, config.NodeKeys, votekeys) + identity, err := client.GetIdentity(ctx) + if err != nil { + klog.Fatalf("Failed to get identity: %v", err) + } + collector := NewSolanaCollector(client, slotPacerSchedule, config.BalanceAddresses, config.NodeKeys, votekeys, identity) slotWatcher := NewSlotWatcher( client, config.NodeKeys, votekeys, config.ComprehensiveSlotTracking, config.MonitorBlockSizes, ) diff --git a/cmd/solana_exporter/exporter_test.go b/cmd/solana_exporter/exporter_test.go index 38adb41..67ea6bc 100644 --- a/cmd/solana_exporter/exporter_test.go +++ b/cmd/solana_exporter/exporter_test.go @@ -43,6 +43,7 @@ type ( var ( identities = []string{"aaa", "bbb", "ccc"} votekeys = []string{"AAA", "BBB", "CCC"} + identity = "aaa" balances = map[string]float64{"aaa": 1, "bbb": 2, "ccc": 3, "AAA": 4, "BBB": 5, "CCC": 6} identityVotes = map[string]string{"aaa": "AAA", "bbb": "BBB", "ccc": "CCC"} nv = len(identities) @@ -420,7 +421,7 @@ func runCollectionTests(t *testing.T, collector prometheus.Collector, testCases } func TestSolanaCollector_Collect_Static(t *testing.T) { - collector := NewSolanaCollector(&staticRPCClient{}, slotPacerSchedule, nil, identities, votekeys) + collector := NewSolanaCollector(&staticRPCClient{}, slotPacerSchedule, nil, identities, votekeys, &identity) prometheus.NewPedanticRegistry().MustRegister(collector) testCases := []collectionTest{ @@ -492,7 +493,7 @@ solana_node_version{version="1.16.7"} 1 func TestSolanaCollector_Collect_Dynamic(t *testing.T) { client := newDynamicRPCClient() - collector := NewSolanaCollector(client, slotPacerSchedule, nil, identities, votekeys) + collector := NewSolanaCollector(client, slotPacerSchedule, nil, identities, votekeys, &identity) prometheus.NewPedanticRegistry().MustRegister(collector) // start off by testing initial state: diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index 8fcff76..28ceb23 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client.go @@ -75,6 +75,7 @@ type Provider interface { GetBlock(ctx context.Context, commitment Commitment, slot int64, transactionDetails string) (*Block, error) GetHealth(ctx context.Context) (*string, error) + GetIdentity(ctx context.Context) (*string, error) } func (c Commitment) MarshalJSON() ([]byte, error) { @@ -315,3 +316,13 @@ func (c *Client) GetHealth(ctx context.Context) (*string, error) { } return &resp.Result, nil } + +// GetIdentity Returns the identity pubkey for the current node +// See API docs: https://solana.com/docs/rpc/http/getidentity +func (c *Client) GetIdentity(ctx context.Context) (*string, error) { + var resp response[Identity] + if err := getResponse(ctx, c, "getIdentity", []any{}, &resp); err != nil { + return nil, err + } + return &resp.Result.Identity, nil +} diff --git a/pkg/rpc/responses.go b/pkg/rpc/responses.go index f587f22..6a23180 100644 --- a/pkg/rpc/responses.go +++ b/pkg/rpc/responses.go @@ -98,6 +98,9 @@ type ( RewardType string `json:"rewardType"` Commission uint8 `json:"commission"` } + Identity struct { + Identity string `json:"identity"` + } ) func (e *RPCError) Error() string { From 6eaeb91be272162a9c33ff410fc52555fba7bb46 Mon Sep 17 00:00:00 2001 From: "l.subbotin" Date: Mon, 21 Oct 2024 09:50:15 +0200 Subject: [PATCH 2/4] Added solana_minimum_ledger_slot, solana_first_available_block, solana_block_height metrics. --- cmd/solana_exporter/exporter.go | 60 +++++++++++++++++++++++++++++++++ pkg/rpc/client.go | 33 ++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/cmd/solana_exporter/exporter.go b/cmd/solana_exporter/exporter.go index b5273d3..85e6682 100644 --- a/cmd/solana_exporter/exporter.go +++ b/cmd/solana_exporter/exporter.go @@ -51,6 +51,9 @@ type SolanaCollector struct { balances *prometheus.Desc isHealthy *prometheus.Desc numSlotsBehind *prometheus.Desc + minimumLedgerSlot *prometheus.Desc + firstAvailableBlock *prometheus.Desc + blockHeight *prometheus.Desc } func NewSolanaCollector(provider rpc.Provider, slotPace time.Duration, balanceAddresses []string, nodekeys []string, votekeys []string, identity *string) *SolanaCollector { @@ -113,6 +116,24 @@ func NewSolanaCollector(provider rpc.Provider, slotPace time.Duration, balanceAd []string{IdentityLabel}, nil, ), + minimumLedgerSlot: prometheus.NewDesc( + "solana_minimum_ledger_slot", + "The lowest slot that the node has information about in its ledger.", + []string{IdentityLabel}, + nil, + ), + firstAvailableBlock: prometheus.NewDesc( + "solana_first_available_block", + "The slot of the lowest confirmed block that has not been purged from the ledger.", + []string{IdentityLabel}, + nil, + ), + blockHeight: prometheus.NewDesc( + "solana_block_height", + "The current block height of the node.", + []string{IdentityLabel}, + nil, + ), } return collector } @@ -127,6 +148,9 @@ func (c *SolanaCollector) Describe(ch chan<- *prometheus.Desc) { ch <- c.balances ch <- c.isHealthy ch <- c.numSlotsBehind + ch <- c.minimumLedgerSlot + ch <- c.firstAvailableBlock + ch <- c.blockHeight } func (c *SolanaCollector) collectVoteAccounts(ctx context.Context, ch chan<- prometheus.Metric) { @@ -195,6 +219,39 @@ func (c *SolanaCollector) collectVersion(ctx context.Context, ch chan<- promethe ch <- prometheus.MustNewConstMetric(c.solanaVersion, prometheus.GaugeValue, 1, version) } +func (c *SolanaCollector) collectMinimumLedgerSlot(ctx context.Context, ch chan<- prometheus.Metric) { + slot, err := c.rpcClient.GetMinimumLedgerSlot(ctx) + + if err != nil { + klog.Errorf("failed to get minimum lidger slot: %v", err) + ch <- prometheus.NewInvalidMetric(c.minimumLedgerSlot, err) + return + } + + ch <- prometheus.MustNewConstMetric(c.minimumLedgerSlot, prometheus.GaugeValue, float64(*slot), *c.identity) +} +func (c *SolanaCollector) collectFirstAvailableBlock(ctx context.Context, ch chan<- prometheus.Metric) { + block, err := c.rpcClient.GetFirstAvailableBlock(ctx) + + if err != nil { + klog.Errorf("failed to get first available block: %v", err) + ch <- prometheus.NewInvalidMetric(c.firstAvailableBlock, err) + return + } + + ch <- prometheus.MustNewConstMetric(c.firstAvailableBlock, prometheus.GaugeValue, float64(*block), *c.identity) +} +func (c *SolanaCollector) collectBlockHeight(ctx context.Context, ch chan<- prometheus.Metric) { + height, err := c.rpcClient.GetBlockHeight(ctx) + + if err != nil { + klog.Errorf("failed to get block height: %v", err) + ch <- prometheus.NewInvalidMetric(c.blockHeight, err) + return + } + + ch <- prometheus.MustNewConstMetric(c.blockHeight, prometheus.GaugeValue, float64(*height), *c.identity) +} func (c *SolanaCollector) collectBalances(ctx context.Context, ch chan<- prometheus.Metric) { balances, err := FetchBalances(ctx, c.rpcClient, c.balanceAddresses) @@ -256,6 +313,9 @@ func (c *SolanaCollector) Collect(ch chan<- prometheus.Metric) { c.collectVersion(ctx, ch) c.collectBalances(ctx, ch) c.collectHealth(ctx, ch) + c.collectMinimumLedgerSlot(ctx, ch) + c.collectFirstAvailableBlock(ctx, ch) + c.collectBlockHeight(ctx, ch) } func main() { diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index 28ceb23..983f185 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client.go @@ -76,6 +76,9 @@ type Provider interface { GetHealth(ctx context.Context) (*string, error) GetIdentity(ctx context.Context) (*string, error) + GetMinimumLedgerSlot(ctx context.Context) (*int64, error) + GetFirstAvailableBlock(ctx context.Context) (*int64, error) + GetBlockHeight(ctx context.Context) (*int64, error) } func (c Commitment) MarshalJSON() ([]byte, error) { @@ -326,3 +329,33 @@ func (c *Client) GetIdentity(ctx context.Context) (*string, error) { } return &resp.Result.Identity, nil } + +// MinimumLedgerSlot Returns the lowest slot that the node has information about in its ledger. +// See API docs: https://solana.com/docs/rpc/http/minimumledgerslot +func (c *Client) GetMinimumLedgerSlot(ctx context.Context) (*int64, error) { + var resp response[int64] + if err := getResponse(ctx, c, "minimumLedgerSlot", []any{}, &resp); err != nil { + return nil, err + } + return &resp.Result, nil +} + +// GetFirstAvailableBlock Returns the slot of the lowest confirmed block that has not been purged from the ledger +// See API docs: https://solana.com/docs/rpc/http/getfirstavailableblock +func (c *Client) GetFirstAvailableBlock(ctx context.Context) (*int64, error) { + var resp response[int64] + if err := getResponse(ctx, c, "getFirstAvailableBlock", []any{}, &resp); err != nil { + return nil, err + } + return &resp.Result, nil +} + +// GetBlockHeight Returns the current block height of the node +// See API docs: https://solana.com/docs/rpc/http/getblockheight +func (c *Client) GetBlockHeight(ctx context.Context) (*int64, error) { + var resp response[int64] + if err := getResponse(ctx, c, "getBlockHeight", []any{}, &resp); err != nil { + return nil, err + } + return &resp.Result, nil +} From 199b6694d5b291d0c1ef403d1883f48154a45fba Mon Sep 17 00:00:00 2001 From: "l.subbotin" Date: Wed, 23 Oct 2024 22:45:29 +0200 Subject: [PATCH 3/4] Tests fix. --- cmd/solana_exporter/exporter_test.go | 48 ++++++++++++++++++++++++++++ cmd/solana_exporter/slots_test.go | 12 ++++--- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/cmd/solana_exporter/exporter_test.go b/cmd/solana_exporter/exporter_test.go index 67ea6bc..39254bf 100644 --- a/cmd/solana_exporter/exporter_test.go +++ b/cmd/solana_exporter/exporter_test.go @@ -185,6 +185,30 @@ func (c *staticRPCClient) GetHealth(ctx context.Context) (*string, error) { return &health, nil } +//goland:noinspection GoUnusedParameter +func (c *staticRPCClient) GetIdentity(ctx context.Context) (*string, error) { + nodeIdentity := "aaa" + return &nodeIdentity, nil +} + +//goland:noinspection GoUnusedParameter +func (c *staticRPCClient) GetBlockHeight(ctx context.Context) (*int64, error) { + blockHeight := int64(1233) + return &blockHeight, nil +} + +//goland:noinspection GoUnusedParameter +func (c *staticRPCClient) GetFirstAvailableBlock(ctx context.Context) (*int64, error) { + firstAvailiableBlock := int64(33) + return &firstAvailiableBlock, nil +} + +//goland:noinspection GoUnusedParameter +func (c *staticRPCClient) GetMinimumLedgerSlot(ctx context.Context) (*int64, error) { + minimumLedgerSlot := int64(23) + return &minimumLedgerSlot, nil +} + /* ===== DYNAMIC CLIENT =====: */ @@ -386,6 +410,30 @@ func (c *dynamicRPCClient) GetHealth(ctx context.Context) (*string, error) { return &health, nil } +//goland:noinspection GoUnusedParameter +func (c *dynamicRPCClient) GetIdentity(ctx context.Context) (*string, error) { + nodeIdentity := "aaa" + return &nodeIdentity, nil +} + +//goland:noinspection GoUnusedParameter +func (c *dynamicRPCClient) GetBlockHeight(ctx context.Context) (*int64, error) { + blockHeight := int64(1233) + return &blockHeight, nil +} + +//goland:noinspection GoUnusedParameter +func (c *dynamicRPCClient) GetFirstAvailableBlock(ctx context.Context) (*int64, error) { + firstAvailiableBlock := int64(33) + return &firstAvailiableBlock, nil +} + +//goland:noinspection GoUnusedParameter +func (c *dynamicRPCClient) GetMinimumLedgerSlot(ctx context.Context) (*int64, error) { + minimumLedgerSlot := int64(23) + return &minimumLedgerSlot, nil +} + /* ===== OTHER TEST UTILITIES =====: */ diff --git a/cmd/solana_exporter/slots_test.go b/cmd/solana_exporter/slots_test.go index ca44114..19c633e 100644 --- a/cmd/solana_exporter/slots_test.go +++ b/cmd/solana_exporter/slots_test.go @@ -90,14 +90,16 @@ func assertSlotMetricsChangeCorrectly(t *testing.T, initial slotMetricValues, fi func TestSolanaCollector_WatchSlots_Static(t *testing.T) { client := staticRPCClient{} - collector := NewSolanaCollector(&client, 100*time.Millisecond, nil, identities, votekeys) + ctx, cancel := context.WithCancel(context.Background()) + nodeIdentity, _ := client.GetIdentity(ctx) + collector := NewSolanaCollector(&client, 100*time.Millisecond, nil, identities, votekeys, nodeIdentity) watcher := NewSlotWatcher(&client, identities, votekeys, false, false) // reset metrics before running tests: watcher.LeaderSlotsTotalMetric.Reset() watcher.LeaderSlotsByEpochMetric.Reset() prometheus.NewPedanticRegistry().MustRegister(collector) - ctx, cancel := context.WithCancel(context.Background()) + defer cancel() go watcher.WatchSlots(ctx, collector.slotPace) @@ -159,7 +161,9 @@ func TestSolanaCollector_WatchSlots_Static(t *testing.T) { func TestSolanaCollector_WatchSlots_Dynamic(t *testing.T) { // create clients: client := newDynamicRPCClient() - collector := NewSolanaCollector(client, 300*time.Millisecond, nil, identities, votekeys) + runCtx, runCancel := context.WithCancel(context.Background()) + nodeIdentity, _ := client.GetIdentity(runCtx) + collector := NewSolanaCollector(client, 300*time.Millisecond, nil, identities, votekeys, nodeIdentity) watcher := NewSlotWatcher(client, identities, votekeys, false, false) // reset metrics before running tests: watcher.LeaderSlotsTotalMetric.Reset() @@ -167,7 +171,7 @@ func TestSolanaCollector_WatchSlots_Dynamic(t *testing.T) { prometheus.NewPedanticRegistry().MustRegister(collector) // start client/collector and wait a bit: - runCtx, runCancel := context.WithCancel(context.Background()) + defer runCancel() go client.Run(runCtx) time.Sleep(time.Second) From 6794ebc88137c60468b697a05c84f7c4cda4ffdc Mon Sep 17 00:00:00 2001 From: "l.subbotin" Date: Wed, 23 Oct 2024 23:48:37 +0200 Subject: [PATCH 4/4] Moved blockHeight to slots.go. --- cmd/solana_exporter/exporter.go | 22 +--------------------- cmd/solana_exporter/exporter_test.go | 12 ------------ cmd/solana_exporter/slots.go | 13 +++++++++++++ cmd/solana_exporter/slots_test.go | 4 ++-- pkg/rpc/client.go | 17 +++-------------- 5 files changed, 19 insertions(+), 49 deletions(-) diff --git a/cmd/solana_exporter/exporter.go b/cmd/solana_exporter/exporter.go index 85e6682..0bcf2c9 100644 --- a/cmd/solana_exporter/exporter.go +++ b/cmd/solana_exporter/exporter.go @@ -53,7 +53,6 @@ type SolanaCollector struct { numSlotsBehind *prometheus.Desc minimumLedgerSlot *prometheus.Desc firstAvailableBlock *prometheus.Desc - blockHeight *prometheus.Desc } func NewSolanaCollector(provider rpc.Provider, slotPace time.Duration, balanceAddresses []string, nodekeys []string, votekeys []string, identity *string) *SolanaCollector { @@ -128,12 +127,6 @@ func NewSolanaCollector(provider rpc.Provider, slotPace time.Duration, balanceAd []string{IdentityLabel}, nil, ), - blockHeight: prometheus.NewDesc( - "solana_block_height", - "The current block height of the node.", - []string{IdentityLabel}, - nil, - ), } return collector } @@ -150,7 +143,6 @@ func (c *SolanaCollector) Describe(ch chan<- *prometheus.Desc) { ch <- c.numSlotsBehind ch <- c.minimumLedgerSlot ch <- c.firstAvailableBlock - ch <- c.blockHeight } func (c *SolanaCollector) collectVoteAccounts(ctx context.Context, ch chan<- prometheus.Metric) { @@ -241,17 +233,6 @@ func (c *SolanaCollector) collectFirstAvailableBlock(ctx context.Context, ch cha ch <- prometheus.MustNewConstMetric(c.firstAvailableBlock, prometheus.GaugeValue, float64(*block), *c.identity) } -func (c *SolanaCollector) collectBlockHeight(ctx context.Context, ch chan<- prometheus.Metric) { - height, err := c.rpcClient.GetBlockHeight(ctx) - - if err != nil { - klog.Errorf("failed to get block height: %v", err) - ch <- prometheus.NewInvalidMetric(c.blockHeight, err) - return - } - - ch <- prometheus.MustNewConstMetric(c.blockHeight, prometheus.GaugeValue, float64(*height), *c.identity) -} func (c *SolanaCollector) collectBalances(ctx context.Context, ch chan<- prometheus.Metric) { balances, err := FetchBalances(ctx, c.rpcClient, c.balanceAddresses) @@ -315,7 +296,6 @@ func (c *SolanaCollector) Collect(ch chan<- prometheus.Metric) { c.collectHealth(ctx, ch) c.collectMinimumLedgerSlot(ctx, ch) c.collectFirstAvailableBlock(ctx, ch) - c.collectBlockHeight(ctx, ch) } func main() { @@ -340,7 +320,7 @@ func main() { } collector := NewSolanaCollector(client, slotPacerSchedule, config.BalanceAddresses, config.NodeKeys, votekeys, identity) slotWatcher := NewSlotWatcher( - client, config.NodeKeys, votekeys, config.ComprehensiveSlotTracking, config.MonitorBlockSizes, + client, config.NodeKeys, votekeys, *identity, config.ComprehensiveSlotTracking, config.MonitorBlockSizes, ) ctx, cancel := context.WithCancel(ctx) defer cancel() diff --git a/cmd/solana_exporter/exporter_test.go b/cmd/solana_exporter/exporter_test.go index 39254bf..4b9eed5 100644 --- a/cmd/solana_exporter/exporter_test.go +++ b/cmd/solana_exporter/exporter_test.go @@ -191,12 +191,6 @@ func (c *staticRPCClient) GetIdentity(ctx context.Context) (*string, error) { return &nodeIdentity, nil } -//goland:noinspection GoUnusedParameter -func (c *staticRPCClient) GetBlockHeight(ctx context.Context) (*int64, error) { - blockHeight := int64(1233) - return &blockHeight, nil -} - //goland:noinspection GoUnusedParameter func (c *staticRPCClient) GetFirstAvailableBlock(ctx context.Context) (*int64, error) { firstAvailiableBlock := int64(33) @@ -416,12 +410,6 @@ func (c *dynamicRPCClient) GetIdentity(ctx context.Context) (*string, error) { return &nodeIdentity, nil } -//goland:noinspection GoUnusedParameter -func (c *dynamicRPCClient) GetBlockHeight(ctx context.Context) (*int64, error) { - blockHeight := int64(1233) - return &blockHeight, nil -} - //goland:noinspection GoUnusedParameter func (c *dynamicRPCClient) GetFirstAvailableBlock(ctx context.Context) (*int64, error) { firstAvailiableBlock := int64(33) diff --git a/cmd/solana_exporter/slots.go b/cmd/solana_exporter/slots.go index cd56485..5abbbf9 100644 --- a/cmd/solana_exporter/slots.go +++ b/cmd/solana_exporter/slots.go @@ -23,6 +23,7 @@ type SlotWatcher struct { // config: nodekeys []string votekeys []string + identity string comprehensiveSlotTracking bool monitorBlockSizes bool @@ -48,12 +49,14 @@ type SlotWatcher struct { InflationRewardsMetric *prometheus.GaugeVec FeeRewardsMetric *prometheus.CounterVec BlockSizeMetric *prometheus.GaugeVec + BlockHeight *prometheus.GaugeVec } func NewSlotWatcher( client rpc.Provider, nodekeys []string, votekeys []string, + identity string, comprehensiveSlotTracking bool, monitorBlockSizes bool, ) *SlotWatcher { @@ -61,6 +64,7 @@ func NewSlotWatcher( client: client, nodekeys: nodekeys, votekeys: votekeys, + identity: identity, comprehensiveSlotTracking: comprehensiveSlotTracking, monitorBlockSizes: monitorBlockSizes, // metrics: @@ -119,6 +123,13 @@ func NewSlotWatcher( }, []string{NodekeyLabel}, ), + BlockHeight: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "solana_block_height", + Help: "The current block height of the node.", + }, + []string{IdentityLabel}, + ), } // register: for _, collector := range []prometheus.Collector{ @@ -132,6 +143,7 @@ func NewSlotWatcher( watcher.InflationRewardsMetric, watcher.FeeRewardsMetric, watcher.BlockSizeMetric, + watcher.BlockHeight, } { if err := prometheus.Register(collector); err != nil { var ( @@ -176,6 +188,7 @@ func (c *SlotWatcher) WatchSlots(ctx context.Context, pace time.Duration) { c.TotalTransactionsMetric.Set(float64(epochInfo.TransactionCount)) c.SlotHeightMetric.Set(float64(epochInfo.AbsoluteSlot)) + c.BlockHeight.WithLabelValues(c.identity).Set(float64(epochInfo.BlockHeight)) // if we get here, then the tracking numbers are set, so this is a "normal" run. // start by checking if we have progressed since last run: diff --git a/cmd/solana_exporter/slots_test.go b/cmd/solana_exporter/slots_test.go index 19c633e..223fa21 100644 --- a/cmd/solana_exporter/slots_test.go +++ b/cmd/solana_exporter/slots_test.go @@ -93,7 +93,7 @@ func TestSolanaCollector_WatchSlots_Static(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) nodeIdentity, _ := client.GetIdentity(ctx) collector := NewSolanaCollector(&client, 100*time.Millisecond, nil, identities, votekeys, nodeIdentity) - watcher := NewSlotWatcher(&client, identities, votekeys, false, false) + watcher := NewSlotWatcher(&client, identities, votekeys, *nodeIdentity, false, false) // reset metrics before running tests: watcher.LeaderSlotsTotalMetric.Reset() watcher.LeaderSlotsByEpochMetric.Reset() @@ -164,7 +164,7 @@ func TestSolanaCollector_WatchSlots_Dynamic(t *testing.T) { runCtx, runCancel := context.WithCancel(context.Background()) nodeIdentity, _ := client.GetIdentity(runCtx) collector := NewSolanaCollector(client, 300*time.Millisecond, nil, identities, votekeys, nodeIdentity) - watcher := NewSlotWatcher(client, identities, votekeys, false, false) + watcher := NewSlotWatcher(client, identities, votekeys, *nodeIdentity, false, false) // reset metrics before running tests: watcher.LeaderSlotsTotalMetric.Reset() watcher.LeaderSlotsByEpochMetric.Reset() diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index 983f185..ea0fe7f 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client.go @@ -78,7 +78,6 @@ type Provider interface { GetIdentity(ctx context.Context) (*string, error) GetMinimumLedgerSlot(ctx context.Context) (*int64, error) GetFirstAvailableBlock(ctx context.Context) (*int64, error) - GetBlockHeight(ctx context.Context) (*int64, error) } func (c Commitment) MarshalJSON() ([]byte, error) { @@ -320,7 +319,7 @@ func (c *Client) GetHealth(ctx context.Context) (*string, error) { return &resp.Result, nil } -// GetIdentity Returns the identity pubkey for the current node +// GetIdentity returns the identity pubkey for the current node // See API docs: https://solana.com/docs/rpc/http/getidentity func (c *Client) GetIdentity(ctx context.Context) (*string, error) { var resp response[Identity] @@ -330,7 +329,7 @@ func (c *Client) GetIdentity(ctx context.Context) (*string, error) { return &resp.Result.Identity, nil } -// MinimumLedgerSlot Returns the lowest slot that the node has information about in its ledger. +// MinimumLedgerSlot returns the lowest slot that the node has information about in its ledger. // See API docs: https://solana.com/docs/rpc/http/minimumledgerslot func (c *Client) GetMinimumLedgerSlot(ctx context.Context) (*int64, error) { var resp response[int64] @@ -340,7 +339,7 @@ func (c *Client) GetMinimumLedgerSlot(ctx context.Context) (*int64, error) { return &resp.Result, nil } -// GetFirstAvailableBlock Returns the slot of the lowest confirmed block that has not been purged from the ledger +// GetFirstAvailableBlock returns the slot of the lowest confirmed block that has not been purged from the ledger // See API docs: https://solana.com/docs/rpc/http/getfirstavailableblock func (c *Client) GetFirstAvailableBlock(ctx context.Context) (*int64, error) { var resp response[int64] @@ -349,13 +348,3 @@ func (c *Client) GetFirstAvailableBlock(ctx context.Context) (*int64, error) { } return &resp.Result, nil } - -// GetBlockHeight Returns the current block height of the node -// See API docs: https://solana.com/docs/rpc/http/getblockheight -func (c *Client) GetBlockHeight(ctx context.Context) (*int64, error) { - var resp response[int64] - if err := getResponse(ctx, c, "getBlockHeight", []any{}, &resp); err != nil { - return nil, err - } - return &resp.Result, nil -}