2020-01-31 05:53:53 -08:00
|
|
|
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"
|
2020-01-31 05:53:53 -08:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
|
|
"net/http"
|
2024-10-01 12:38:28 -07:00
|
|
|
"strings"
|
2020-01-31 05:53:53 -08:00
|
|
|
"time"
|
2021-01-03 14:58:24 -08:00
|
|
|
|
|
|
|
"k8s.io/klog/v2"
|
2020-01-31 05:53:53 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2024-10-01 12:38:28 -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")
|
|
|
|
balanceAddresses = flag.String("balance-addresses", "", "Comma-separated list of addresses to monitor balances")
|
2020-01-31 05:53:53 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
2021-01-03 14:58:24 -08:00
|
|
|
klog.InitFlags(nil)
|
2020-01-31 05:53:53 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
type solanaCollector struct {
|
2024-10-01 12:38:28 -07:00
|
|
|
rpcClient rpc.Provider
|
|
|
|
slotPace time.Duration
|
|
|
|
balanceAddresses []string
|
2020-01-31 05:53:53 -08:00
|
|
|
|
|
|
|
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
|
2020-01-31 05:53:53 -08:00
|
|
|
}
|
|
|
|
|
2024-10-01 12:38:28 -07:00
|
|
|
func createSolanaCollector(provider rpc.Provider, slotPace time.Duration, balanceAddresses []string) *solanaCollector {
|
2020-01-31 05:53:53 -08:00
|
|
|
return &solanaCollector{
|
2024-10-01 12:38:28 -07:00
|
|
|
rpcClient: provider,
|
|
|
|
slotPace: slotPace,
|
|
|
|
balanceAddresses: balanceAddresses,
|
2020-01-31 05:53:53 -08:00
|
|
|
totalValidatorsDesc: prometheus.NewDesc(
|
|
|
|
"solana_active_validators",
|
|
|
|
"Total number of active validators by state",
|
2024-10-01 02:52:02 -07:00
|
|
|
[]string{"state"},
|
|
|
|
nil,
|
|
|
|
),
|
2020-01-31 05:53:53 -08:00
|
|
|
validatorActivatedStake: prometheus.NewDesc(
|
|
|
|
"solana_validator_activated_stake",
|
|
|
|
"Activated stake per validator",
|
2024-10-01 02:52:02 -07:00
|
|
|
[]string{"pubkey", "nodekey"},
|
|
|
|
nil,
|
|
|
|
),
|
2020-01-31 05:53:53 -08:00
|
|
|
validatorLastVote: prometheus.NewDesc(
|
|
|
|
"solana_validator_last_vote",
|
|
|
|
"Last voted slot per validator",
|
2024-10-01 02:52:02 -07:00
|
|
|
[]string{"pubkey", "nodekey"},
|
|
|
|
nil,
|
|
|
|
),
|
2020-01-31 05:53:53 -08:00
|
|
|
validatorRootSlot: prometheus.NewDesc(
|
|
|
|
"solana_validator_root_slot",
|
|
|
|
"Root slot per validator",
|
2024-10-01 02:52:02 -07:00
|
|
|
[]string{"pubkey", "nodekey"},
|
|
|
|
nil,
|
|
|
|
),
|
2020-01-31 05:53:53 -08:00
|
|
|
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,
|
|
|
|
),
|
2020-01-31 05:53:53 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-01 12:38:28 -07:00
|
|
|
func NewSolanaCollector(rpcAddr string, balanceAddresses []string) *solanaCollector {
|
|
|
|
return createSolanaCollector(rpc.NewRPCClient(rpcAddr), slotPacerSchedule, balanceAddresses)
|
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
|
2020-01-31 05:53:53 -08:00
|
|
|
}
|
|
|
|
|
2024-10-01 12:38:28 -07:00
|
|
|
func (c *solanaCollector) collectVoteAccounts(ctx context.Context, ch chan<- prometheus.Metric) {
|
|
|
|
params := map[string]string{"commitment": string(rpc.CommitmentRecent)}
|
|
|
|
if *votePubkey != "" {
|
|
|
|
params = map[string]string{"commitment": string(rpc.CommitmentRecent), "votePubkey": *votePubkey}
|
|
|
|
}
|
|
|
|
|
|
|
|
voteAccounts, err := c.rpcClient.GetVoteAccounts(ctx, []interface{}{params})
|
|
|
|
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
|
|
|
)
|
2020-01-31 05:53:53 -08: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,
|
|
|
|
)
|
2020-01-31 05:53:53 -08:00
|
|
|
}
|
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,
|
|
|
|
)
|
2020-01-31 05:53:53 -08:00
|
|
|
}
|
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,
|
|
|
|
)
|
2020-01-31 05:53:53 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2020-01-31 05:53:53 -08:00
|
|
|
if err != nil {
|
2024-10-01 12:38:28 -07:00
|
|
|
ch <- prometheus.NewInvalidMetric(c.solanaVersion, err)
|
|
|
|
return
|
2020-01-31 05:53:53 -08:00
|
|
|
}
|
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 {
|
|
|
|
balance, err := client.GetBalance(ctx, address)
|
|
|
|
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)
|
2020-01-31 05:53:53 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
2021-01-03 14:58:24 -08:00
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
if *rpcAddr == "" {
|
|
|
|
klog.Fatal("Please specify -rpcURI")
|
|
|
|
}
|
|
|
|
|
2024-06-04 03:24:54 -07:00
|
|
|
httpTimeout = time.Duration(*httpTimeoutSecs) * time.Second
|
|
|
|
|
2024-10-01 12:38:28 -07:00
|
|
|
var monitoredAddresses []string
|
|
|
|
if *balanceAddresses != "" {
|
|
|
|
monitoredAddresses = strings.Split(*balanceAddresses, ",")
|
|
|
|
}
|
|
|
|
collector := NewSolanaCollector(*rpcAddr, monitoredAddresses)
|
2021-01-03 14:58:24 -08:00
|
|
|
|
2024-06-13 15:47:47 -07:00
|
|
|
go collector.WatchSlots(context.Background())
|
2021-01-03 14:58:24 -08:00
|
|
|
|
2020-01-31 05:53:53 -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))
|
2020-01-31 05:53:53 -08:00
|
|
|
}
|