Skip to content

Commit

Permalink
feat: add solana_node_is_active metric
Browse files Browse the repository at this point in the history
  • Loading branch information
andreclaro committed Jan 15, 2025
1 parent cf01b8a commit 4273fa6
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 18 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ The exporter is configured via the following command line arguments:

| Option | Description | Default |
|--------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------|
| `-active-identity` | Validator identity public key used to determine if the node is considered active in the `solana_node_is_active` metric. | N/A |
| `-balance-address` | Address to monitor SOL balances for, in addition to the identity and vote accounts of the provided nodekeys - can be set multiple times. | N/A |
| `-comprehensive-slot-tracking` | Set this flag to track `solana_leader_slots_by_epoch` for all validators. | `false` |
| `-http-timeout` | HTTP timeout to use, in seconds. | `60` |
Expand Down Expand Up @@ -72,6 +73,7 @@ The table below describes all the metrics collected by the `solana-exporter`:
| `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_is_active` | Whether the node is active and participating in consensus. | `identity` |
| `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 |
Expand Down
16 changes: 16 additions & 0 deletions cmd/solana-exporter/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type SolanaCollector struct {
NodeNumSlotsBehind *GaugeDesc
NodeMinimumLedgerSlot *GaugeDesc
NodeFirstAvailableBlock *GaugeDesc
NodeIsActive *GaugeDesc
}

func NewSolanaCollector(client *rpc.Client, config *ExporterConfig) *SolanaCollector {
Expand Down Expand Up @@ -104,6 +105,11 @@ func NewSolanaCollector(client *rpc.Client, config *ExporterConfig) *SolanaColle
"solana_node_first_available_block",
"The slot of the lowest confirmed block that has not been purged from the node's ledger.",
),
NodeIsActive: NewGaugeDesc(
"solana_node_is_active",
fmt.Sprintf("Whether the node is active and participating in consensus (using %s pubkey)", IdentityLabel),
IdentityLabel,
),
}
return collector
}
Expand All @@ -120,6 +126,7 @@ func (c *SolanaCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- c.NodeNumSlotsBehind.Desc
ch <- c.NodeMinimumLedgerSlot.Desc
ch <- c.NodeFirstAvailableBlock.Desc
ch <- c.NodeIsActive.Desc
}

func (c *SolanaCollector) collectVoteAccounts(ctx context.Context, ch chan<- prometheus.Metric) {
Expand Down Expand Up @@ -177,6 +184,15 @@ func (c *SolanaCollector) collectIdentity(ctx context.Context, ch chan<- prometh
return
}

if c.config.ActiveIdentity != "" {
isActive := 0
if c.config.ActiveIdentity == identity {
isActive = 1
}
ch <- c.NodeIsActive.MustNewConstMetric(float64(isActive), identity)
c.logger.Info("NodeIsActive collected.")
}

ch <- c.NodeIdentity.MustNewConstMetric(1, identity)
c.logger.Info("Identity collected.")
}
Expand Down
33 changes: 19 additions & 14 deletions cmd/solana-exporter/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import (
"bytes"
"context"
"fmt"
"github.com/asymmetric-research/solana-exporter/pkg/rpc"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/assert"
"math"
"math/rand"
"slices"
"strings"
"testing"
"time"

"github.com/asymmetric-research/solana-exporter/pkg/rpc"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/assert"
)

type (
Expand Down Expand Up @@ -192,16 +193,17 @@ func newTestConfig(simulator *Simulator, fast bool) *ExporterConfig {
pace = time.Duration(500) * time.Millisecond
}
config := ExporterConfig{
time.Second * time.Duration(1),
simulator.Server.URL(),
":8080",
simulator.Nodekeys,
simulator.Votekeys,
nil,
true,
true,
false,
pace,
HttpTimeout: time.Second * time.Duration(1),
RpcUrl: simulator.Server.URL(),
ListenAddress: ":8080",
NodeKeys: simulator.Nodekeys,
VoteKeys: simulator.Votekeys,
BalanceAddresses: nil,
ComprehensiveSlotTracking: true,
MonitorBlockSizes: true,
LightMode: false,
SlotPace: pace,
ActiveIdentity: simulator.Nodekeys[0],
}
return &config
}
Expand Down Expand Up @@ -240,6 +242,9 @@ func TestSolanaCollector(t *testing.T) {
collector.NodeIdentity.makeCollectionTest(
NewLV(1, "testIdentity"),
),
collector.NodeIsActive.makeCollectionTest(
NewLV(1),
),
collector.NodeIsHealthy.makeCollectionTest(
NewLV(1),
),
Expand Down
15 changes: 14 additions & 1 deletion cmd/solana-exporter/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import (
"context"
"flag"
"fmt"
"time"

"github.com/asymmetric-research/solana-exporter/pkg/rpc"
"github.com/asymmetric-research/solana-exporter/pkg/slog"
"time"
)

type (
Expand All @@ -23,6 +24,7 @@ type (
MonitorBlockSizes bool
LightMode bool
SlotPace time.Duration
ActiveIdentity string
}
)

Expand All @@ -46,6 +48,7 @@ func NewExporterConfig(
monitorBlockSizes bool,
lightMode bool,
slotPace time.Duration,
activeIdentity string,
) (*ExporterConfig, error) {
logger := slog.Get()
logger.Infow(
Expand All @@ -58,6 +61,7 @@ func NewExporterConfig(
"comprehensiveSlotTracking", comprehensiveSlotTracking,
"monitorBlockSizes", monitorBlockSizes,
"lightMode", lightMode,
"activeIdentity", activeIdentity,
)
if lightMode {
if comprehensiveSlotTracking {
Expand Down Expand Up @@ -97,6 +101,7 @@ func NewExporterConfig(
MonitorBlockSizes: monitorBlockSizes,
LightMode: lightMode,
SlotPace: slotPace,
ActiveIdentity: activeIdentity,
}
return &config, nil
}
Expand All @@ -112,6 +117,7 @@ func NewExporterConfigFromCLI(ctx context.Context) (*ExporterConfig, error) {
monitorBlockSizes bool
lightMode bool
slotPace int
activeIdentity string
)
flag.IntVar(
&httpTimeout,
Expand Down Expand Up @@ -171,6 +177,12 @@ func NewExporterConfigFromCLI(ctx context.Context) (*ExporterConfig, error) {
1,
"This is the time between slot-watching metric collections, defaults to 1s.",
)
flag.StringVar(
&activeIdentity,
"active-identity",
"",
"Validator identity public key that determines if the node is considered active in the 'solana_node_is_active' metric.",
)
flag.Parse()

config, err := NewExporterConfig(
Expand All @@ -184,6 +196,7 @@ func NewExporterConfigFromCLI(ctx context.Context) (*ExporterConfig, error) {
monitorBlockSizes,
lightMode,
time.Duration(slotPace)*time.Second,
activeIdentity,
)
if err != nil {
return nil, err
Expand Down
8 changes: 7 additions & 1 deletion cmd/solana-exporter/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package main

import (
"context"
"github.com/stretchr/testify/assert"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestNewExporterConfig(t *testing.T) {
Expand All @@ -22,6 +23,7 @@ func TestNewExporterConfig(t *testing.T) {
slotPace time.Duration
wantErr bool
expectedVoteKeys []string
activeIdentity string
}{
{
name: "valid configuration",
Expand All @@ -36,6 +38,7 @@ func TestNewExporterConfig(t *testing.T) {
slotPace: time.Second,
wantErr: false,
expectedVoteKeys: simulator.Votekeys,
activeIdentity: simulator.Nodekeys[0],
},
{
name: "light mode with incompatible options",
Expand All @@ -50,6 +53,7 @@ func TestNewExporterConfig(t *testing.T) {
slotPace: time.Second,
wantErr: true,
expectedVoteKeys: nil,
activeIdentity: simulator.Nodekeys[0],
},
{
name: "empty node keys",
Expand All @@ -64,6 +68,7 @@ func TestNewExporterConfig(t *testing.T) {
slotPace: time.Second,
wantErr: false,
expectedVoteKeys: []string{},
activeIdentity: simulator.Nodekeys[0],
},
}

Expand All @@ -80,6 +85,7 @@ func TestNewExporterConfig(t *testing.T) {
tt.monitorBlockSizes,
tt.lightMode,
tt.slotPace,
tt.activeIdentity,
)

// Check error expectation
Expand Down
3 changes: 2 additions & 1 deletion cmd/solana-exporter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package main

import (
"context"
"net/http"

"github.com/asymmetric-research/solana-exporter/pkg/rpc"
"github.com/asymmetric-research/solana-exporter/pkg/slog"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"net/http"
)

func main() {
Expand Down
15 changes: 14 additions & 1 deletion pkg/rpc/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package rpc

import (
"context"
"github.com/stretchr/testify/assert"
"testing"

"github.com/stretchr/testify/assert"
)

func newMethodTester(t *testing.T, method string, result any) (*MockServer, *Client) {
Expand Down Expand Up @@ -244,3 +245,15 @@ func TestClient_GetVoteAccounts(t *testing.T) {
voteAccounts,
)
}

func TestClient_GetIdentity(t *testing.T) {
_, client := newMethodTester(t, "getIdentity", map[string]string{
"identity": "random2r1F4iWqVcb8M1DbAjQuFpebkQuW2DJtestkey",
})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

identity, err := client.GetIdentity(ctx)
assert.NoError(t, err)
assert.Equal(t, "random2r1F4iWqVcb8M1DbAjQuFpebkQuW2DJtestkey", identity)
}

0 comments on commit 4273fa6

Please sign in to comment.