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-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." ,
)
2024-10-03 02:24:23 -07:00
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-03 02:24:23 -07:00
)
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." ,
)
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-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
2020-01-31 05:53:53 -08:00
2024-10-02 08:08:46 -07:00
/// descriptors:
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-03 02:24:23 -07:00
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 ,
2024-10-03 02:24:23 -07:00
) * solanaCollector {
2020-01-31 05:53:53 -08:00
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 ,
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-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
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 ) {
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
)
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 {
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 )
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-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
2024-10-03 02:24:23 -07:00
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-03 02:24:23 -07:00
)
2024-10-01 12:38:28 -07:00
if * balanceAddresses != "" {
2024-10-03 02:24:23 -07:00
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
}
2024-10-03 02:24:23 -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-03 02:24:23 -07:00
}
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-03 02:24:23 -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
2024-10-04 04:04:23 -07: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
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
}