Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add solana_node_is_active metric #84

Merged
merged 1 commit into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(0, "testIdentity"),
),
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)
}
Loading