solana_exporter/cmd/solana_exporter/exporter.go

306 lines
8.6 KiB
Go
Raw Normal View History

package main
import (
2021-01-03 03:23:18 -08:00
"context"
2021-01-03 14:58:24 -08:00
"flag"
2024-10-01 12:38:28 -07:00
"github.com/asymmetric-research/solana_exporter/pkg/rpc"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"net/http"
2024-10-01 12:38:28 -07:00
"strings"
"time"
2021-01-03 14:58:24 -08:00
"k8s.io/klog/v2"
)
var (
2024-10-07 03:09:45 -07:00
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",
"",
2024-10-04 03:24:44 -07:00
"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).",
)
2024-10-07 03:09:45 -07:00
inflationRewardAddresses = flag.String(
"inflation-reward-addresses",
"",
"Comma-separated list of validator vote accounts to track inflationary rewards for",
)
2024-10-07 07:55:24 -07:00
feeRewardAddresses = flag.String(
"fee-reward-addresses",
"",
"Comma-separated list of validator identity accounts to track fee rewards for.",
)
)
func init() {
2021-01-03 14:58:24 -08:00
klog.InitFlags(nil)
}
type solanaCollector struct {
2024-10-02 08:08:46 -07:00
rpcClient rpc.Provider
// config:
2024-10-07 03:09:45 -07:00
slotPace time.Duration
balanceAddresses []string
leaderSlotAddresses []string
inflationRewardAddresses []string
2024-10-07 07:55:24 -07:00
feeRewardAddresses []string
2024-10-02 08:08:46 -07:00
/// descriptors:
totalValidatorsDesc *prometheus.Desc
validatorActivatedStake *prometheus.Desc
validatorLastVote *prometheus.Desc
validatorRootSlot *prometheus.Desc
validatorDelinquent *prometheus.Desc
2022-08-10 10:13:08 -07:00
solanaVersion *prometheus.Desc
2024-10-01 12:38:28 -07:00
balances *prometheus.Desc
}
func createSolanaCollector(
2024-10-07 03:09:45 -07:00
provider rpc.Provider,
slotPace time.Duration,
balanceAddresses []string,
leaderSlotAddresses []string,
inflationRewardAddresses []string,
2024-10-07 07:55:24 -07:00
feeRewardAddresses []string,
) *solanaCollector {
return &solanaCollector{
2024-10-07 03:09:45 -07:00
rpcClient: provider,
slotPace: slotPace,
balanceAddresses: balanceAddresses,
leaderSlotAddresses: leaderSlotAddresses,
inflationRewardAddresses: inflationRewardAddresses,
2024-10-07 07:55:24 -07:00
feeRewardAddresses: feeRewardAddresses,
totalValidatorsDesc: prometheus.NewDesc(
"solana_active_validators",
"Total number of active validators by state",
2024-10-01 02:52:02 -07:00
[]string{"state"},
nil,
),
validatorActivatedStake: prometheus.NewDesc(
"solana_validator_activated_stake",
"Activated stake per validator",
2024-10-01 02:52:02 -07:00
[]string{"pubkey", "nodekey"},
nil,
),
validatorLastVote: prometheus.NewDesc(
"solana_validator_last_vote",
"Last voted slot per validator",
2024-10-01 02:52:02 -07:00
[]string{"pubkey", "nodekey"},
nil,
),
validatorRootSlot: prometheus.NewDesc(
"solana_validator_root_slot",
"Root slot per validator",
2024-10-01 02:52:02 -07:00
[]string{"pubkey", "nodekey"},
nil,
),
validatorDelinquent: prometheus.NewDesc(
"solana_validator_delinquent",
"Whether a validator is delinquent",
2024-10-01 02:52:02 -07:00
[]string{"pubkey", "nodekey"},
nil,
),
2022-08-10 10:13:08 -07:00
solanaVersion: prometheus.NewDesc(
"solana_node_version",
"Node version of solana",
2024-10-01 02:52:02 -07:00
[]string{"version"},
nil,
),
2024-10-01 12:38:28 -07:00
balances: prometheus.NewDesc(
"solana_account_balance",
"Solana account balances",
[]string{"address"},
nil,
),
}
}
2024-10-07 03:09:45 -07:00
func NewSolanaCollector(
2024-10-07 07:55:24 -07:00
rpcAddr string,
balanceAddresses []string,
leaderSlotAddresses []string,
inflationRewardAddresses []string,
feeRewardAddresses []string,
2024-10-07 03:09:45 -07:00
) *solanaCollector {
return createSolanaCollector(
2024-10-07 07:55:24 -07:00
rpc.NewRPCClient(rpcAddr),
slotPacerSchedule,
balanceAddresses,
leaderSlotAddresses,
inflationRewardAddresses,
feeRewardAddresses,
2024-10-07 03:09:45 -07:00
)
2024-06-11 13:23:10 -07:00
}
2021-01-03 07:48:43 -08:00
func (c *solanaCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- c.totalValidatorsDesc
2022-08-10 10:13:08 -07:00
ch <- c.solanaVersion
2024-06-12 02:21:16 -07:00
ch <- c.validatorActivatedStake
ch <- c.validatorLastVote
ch <- c.validatorRootSlot
ch <- c.validatorDelinquent
2024-10-01 12:38:28 -07:00
ch <- c.balances
}
2024-10-01 12:38:28 -07:00
func (c *solanaCollector) collectVoteAccounts(ctx context.Context, ch chan<- prometheus.Metric) {
2024-10-07 06:05:24 -07:00
voteAccounts, err := c.rpcClient.GetVoteAccounts(ctx, rpc.CommitmentConfirmed, votePubkey)
2024-10-01 12:38:28 -07:00
if err != nil {
ch <- prometheus.NewInvalidMetric(c.totalValidatorsDesc, err)
ch <- prometheus.NewInvalidMetric(c.validatorActivatedStake, err)
ch <- prometheus.NewInvalidMetric(c.validatorLastVote, err)
ch <- prometheus.NewInvalidMetric(c.validatorRootSlot, err)
ch <- prometheus.NewInvalidMetric(c.validatorDelinquent, err)
return
}
2024-10-01 02:52:02 -07:00
ch <- prometheus.MustNewConstMetric(
2024-10-01 12:38:28 -07:00
c.totalValidatorsDesc, prometheus.GaugeValue, float64(len(voteAccounts.Delinquent)), "delinquent",
2024-10-01 02:52:02 -07:00
)
ch <- prometheus.MustNewConstMetric(
2024-10-01 12:38:28 -07:00
c.totalValidatorsDesc, prometheus.GaugeValue, float64(len(voteAccounts.Current)), "current",
2024-10-01 02:52:02 -07:00
)
2024-10-01 12:38:28 -07:00
for _, account := range append(voteAccounts.Current, voteAccounts.Delinquent...) {
2024-10-01 02:52:02 -07:00
ch <- prometheus.MustNewConstMetric(
c.validatorActivatedStake,
prometheus.GaugeValue,
float64(account.ActivatedStake),
account.VotePubkey,
account.NodePubkey,
)
ch <- prometheus.MustNewConstMetric(
c.validatorLastVote,
prometheus.GaugeValue,
float64(account.LastVote),
account.VotePubkey,
account.NodePubkey,
)
ch <- prometheus.MustNewConstMetric(
c.validatorRootSlot,
prometheus.GaugeValue,
float64(account.RootSlot),
account.VotePubkey,
account.NodePubkey,
)
}
2024-10-01 12:38:28 -07:00
for _, account := range voteAccounts.Current {
2024-10-01 02:52:02 -07:00
ch <- prometheus.MustNewConstMetric(
c.validatorDelinquent, prometheus.GaugeValue, 0, account.VotePubkey, account.NodePubkey,
)
}
2024-10-01 12:38:28 -07:00
for _, account := range voteAccounts.Delinquent {
2024-10-01 02:52:02 -07:00
ch <- prometheus.MustNewConstMetric(
c.validatorDelinquent, prometheus.GaugeValue, 1, account.VotePubkey, account.NodePubkey,
)
}
}
2024-10-01 12:38:28 -07:00
func (c *solanaCollector) collectVersion(ctx context.Context, ch chan<- prometheus.Metric) {
version, err := c.rpcClient.GetVersion(ctx)
2022-07-29 05:43:52 -07:00
if err != nil {
2024-10-01 12:38:28 -07:00
ch <- prometheus.NewInvalidMetric(c.solanaVersion, err)
return
}
2022-08-10 10:13:08 -07:00
2024-10-01 12:38:28 -07:00
ch <- prometheus.MustNewConstMetric(c.solanaVersion, prometheus.GaugeValue, 1, version)
}
2022-08-10 10:13:08 -07:00
2024-10-01 12:38:28 -07:00
func (c *solanaCollector) collectBalances(ctx context.Context, ch chan<- prometheus.Metric) {
balances, err := fetchBalances(ctx, c.rpcClient, c.balanceAddresses)
2022-08-10 10:13:08 -07:00
if err != nil {
ch <- prometheus.NewInvalidMetric(c.solanaVersion, err)
2024-10-01 12:38:28 -07:00
return
}
for address, balance := range balances {
ch <- prometheus.MustNewConstMetric(c.balances, prometheus.GaugeValue, balance, address)
}
}
func fetchBalances(ctx context.Context, client rpc.Provider, addresses []string) (map[string]float64, error) {
balances := make(map[string]float64)
for _, address := range addresses {
2024-10-07 06:05:24 -07:00
balance, err := client.GetBalance(ctx, rpc.CommitmentConfirmed, address)
2024-10-01 12:38:28 -07:00
if err != nil {
return nil, err
}
balances[address] = balance
2022-08-10 10:13:08 -07:00
}
2024-10-01 12:38:28 -07:00
return balances, nil
}
func (c *solanaCollector) Collect(ch chan<- prometheus.Metric) {
ctx, cancel := context.WithTimeout(context.Background(), httpTimeout)
defer cancel()
c.collectVoteAccounts(ctx, ch)
2024-10-01 12:49:48 -07:00
c.collectVersion(ctx, ch)
c.collectBalances(ctx, ch)
}
func main() {
2021-01-03 14:58:24 -08:00
flag.Parse()
if *rpcAddr == "" {
klog.Fatal("Please specify -rpcURI")
}
2024-10-04 03:24:44 -07:00
if *leaderSlotAddresses == "" {
klog.Warning(
"Not specifying leader-slot-addresses will lead to potentially thousands of new " +
"Prometheus metrics being created every epoch.",
)
}
2024-06-04 03:24:54 -07:00
httpTimeout = time.Duration(*httpTimeoutSecs) * time.Second
var (
balAddresses []string
lsAddresses []string
2024-10-07 03:09:45 -07:00
irAddresses []string
2024-10-07 07:55:24 -07:00
frAddresses []string
)
2024-10-01 12:38:28 -07:00
if *balanceAddresses != "" {
balAddresses = strings.Split(*balanceAddresses, ",")
2024-10-08 07:33:41 -07:00
klog.Infof("Monitoring balances for %v", balAddresses)
2024-10-01 12:38:28 -07:00
}
if *leaderSlotAddresses != "" {
lsAddresses = strings.Split(*leaderSlotAddresses, ",")
2024-10-08 07:33:41 -07:00
klog.Infof("Monitoring leader-slot by epoch for %v", lsAddresses)
}
2024-10-07 03:09:45 -07:00
if *inflationRewardAddresses != "" {
irAddresses = strings.Split(*inflationRewardAddresses, ",")
2024-10-08 07:33:41 -07:00
klog.Infof("Monitoring inflation reward by epoch for %v", irAddresses)
2024-10-07 03:09:45 -07:00
}
2024-10-07 07:55:24 -07:00
if *feeRewardAddresses != "" {
frAddresses = strings.Split(*feeRewardAddresses, ",")
2024-10-08 07:33:41 -07:00
klog.Infof("Monitoring fee reward by epoch for %v", frAddresses)
2024-10-07 07:55:24 -07:00
}
2024-10-07 07:55:24 -07:00
collector := NewSolanaCollector(*rpcAddr, balAddresses, lsAddresses, irAddresses, frAddresses)
2021-01-03 14:58:24 -08:00
slotWatcher := NewCollectorSlotWatcher(collector)
2024-10-03 08:33:28 -07:00
go slotWatcher.WatchSlots(context.Background(), collector.slotPace)
2021-01-03 14:58:24 -08:00
prometheus.MustRegister(collector)
http.Handle("/metrics", promhttp.Handler())
2021-01-03 14:58:24 -08:00
klog.Infof("listening on %s", *addr)
klog.Fatal(http.ListenAndServe(*addr, nil))
}