From ab386801b1073801b5b9dc1ecceab395a4204747 Mon Sep 17 00:00:00 2001 From: Matt Johnstone Date: Wed, 2 Oct 2024 14:47:04 +0200 Subject: [PATCH 1/8] refactored GetBlockProduction --- cmd/solana_exporter/exporter.go | 4 +- cmd/solana_exporter/exporter_test.go | 49 +++++++++++----------- cmd/solana_exporter/slots.go | 42 +++++++++---------- cmd/solana_exporter/slots_test.go | 2 +- pkg/rpc/client.go | 63 ++++++++++++++++------------ pkg/rpc/responses.go | 53 +++++++++++++---------- 6 files changed, 118 insertions(+), 95 deletions(-) diff --git a/cmd/solana_exporter/exporter.go b/cmd/solana_exporter/exporter.go index a147622..343b010 100644 --- a/cmd/solana_exporter/exporter.go +++ b/cmd/solana_exporter/exporter.go @@ -114,9 +114,9 @@ func (c *solanaCollector) Describe(ch chan<- *prometheus.Desc) { } func (c *solanaCollector) collectVoteAccounts(ctx context.Context, ch chan<- prometheus.Metric) { - params := map[string]string{"commitment": string(rpc.CommitmentRecent)} + params := map[string]string{"commitment": string(rpc.CommitmentProcessed)} if *votePubkey != "" { - params = map[string]string{"commitment": string(rpc.CommitmentRecent), "votePubkey": *votePubkey} + params = map[string]string{"commitment": string(rpc.CommitmentProcessed), "votePubkey": *votePubkey} } voteAccounts, err := c.rpcClient.GetVoteAccounts(ctx, []interface{}{params}) diff --git a/cmd/solana_exporter/exporter_test.go b/cmd/solana_exporter/exporter_test.go index 0c57063..4f7ad58 100644 --- a/cmd/solana_exporter/exporter_test.go +++ b/cmd/solana_exporter/exporter_test.go @@ -54,13 +54,12 @@ var ( TransactionCount: 22661093, } staticBlockProduction = rpc.BlockProduction{ - FirstSlot: 100000000, - LastSlot: 200000000, - Hosts: map[string]rpc.BlockProductionPerHost{ - "bbb": {LeaderSlots: 40000000, BlocksProduced: 36000000}, - "ccc": {LeaderSlots: 30000000, BlocksProduced: 29600000}, - "aaa": {LeaderSlots: 30000000, BlocksProduced: 10000000}, + ByIdentity: map[string]rpc.HostProduction{ + "aaa": {300, 100}, + "bbb": {400, 360}, + "ccc": {300, 296}, }, + Range: rpc.BlockProductionRange{FirstSlot: 1000, LastSlot: 2000}, } staticVoteAccounts = rpc.VoteAccounts{ Current: []rpc.VoteAccount{ @@ -136,7 +135,7 @@ func (c *staticRPCClient) GetVoteAccounts(ctx context.Context, params []interfac //goland:noinspection GoUnusedParameter func (c *staticRPCClient) GetBlockProduction( - ctx context.Context, firstSlot *int64, lastSlot *int64, + ctx context.Context, identity *string, firstSlot *int64, lastSlot *int64, ) (*rpc.BlockProduction, error) { return &staticBlockProduction, nil } @@ -292,23 +291,25 @@ func (c *dynamicRPCClient) GetVoteAccounts(ctx context.Context, params []interfa //goland:noinspection GoUnusedParameter func (c *dynamicRPCClient) GetBlockProduction( - ctx context.Context, firstSlot *int64, lastSlot *int64, + ctx context.Context, identity *string, firstSlot *int64, lastSlot *int64, ) (*rpc.BlockProduction, error) { - hostProduction := make(map[string]rpc.BlockProductionPerHost) + byIdentity := make(map[string]rpc.HostProduction) for _, identity := range identities { - hostProduction[identity] = rpc.BlockProductionPerHost{LeaderSlots: 0, BlocksProduced: 0} + byIdentity[identity] = rpc.HostProduction{LeaderSlots: 0, BlocksProduced: 0} } for i := *firstSlot; i <= *lastSlot; i++ { info := c.SlotInfos[int(i)] - hp := hostProduction[info.leader] - hp.LeaderSlots++ + production := byIdentity[info.leader] + production.LeaderSlots++ if info.blockProduced { - hp.BlocksProduced++ + production.BlocksProduced++ } - hostProduction[info.leader] = hp + byIdentity[info.leader] = production } - production := rpc.BlockProduction{FirstSlot: *firstSlot, LastSlot: *lastSlot, Hosts: hostProduction} - return &production, nil + blockProduction := rpc.BlockProduction{ + ByIdentity: byIdentity, Range: rpc.BlockProductionRange{FirstSlot: *firstSlot, LastSlot: *lastSlot}, + } + return &blockProduction, nil } //goland:noinspection GoUnusedParameter @@ -407,19 +408,19 @@ solana_validator_delinquent{nodekey="ccc",pubkey="CCC"} 0 { Name: "solana_node_version", ExpectedResponse: ` - # HELP solana_node_version Node version of solana - # TYPE solana_node_version gauge - solana_node_version{version="1.16.7"} 1 +# HELP solana_node_version Node version of solana +# TYPE solana_node_version gauge +solana_node_version{version="1.16.7"} 1 `, }, { Name: "solana_account_balance", ExpectedResponse: ` - # HELP solana_account_balance Solana account balances - # TYPE solana_account_balance gauge - solana_account_balance{address="aaa"} 1 - solana_account_balance{address="bbb"} 2 - solana_account_balance{address="ccc"} 3 +# HELP solana_account_balance Solana account balances +# TYPE solana_account_balance gauge +solana_account_balance{address="aaa"} 1 +solana_account_balance{address="bbb"} 2 +solana_account_balance{address="ccc"} 3 `, }, } diff --git a/cmd/solana_exporter/slots.go b/cmd/solana_exporter/slots.go index 2d36e94..2b306d3 100644 --- a/cmd/solana_exporter/slots.go +++ b/cmd/solana_exporter/slots.go @@ -70,22 +70,22 @@ func init() { func (c *solanaCollector) WatchSlots(ctx context.Context) { // Get current slot height and epoch info - ctx_, cancel := context.WithTimeout(context.Background(), httpTimeout) - info, err := c.rpcClient.GetEpochInfo(ctx_, rpc.CommitmentMax) + ctx_, cancel := context.WithTimeout(ctx, httpTimeout) + epochInfo, err := c.rpcClient.GetEpochInfo(ctx_, rpc.CommitmentFinalized) if err != nil { klog.Fatalf("failed to fetch epoch info, bailing out: %v", err) } cancel() - totalTransactionsTotal.Set(float64(info.TransactionCount)) - confirmedSlotHeight.Set(float64(info.AbsoluteSlot)) - - // watermark is the last slot number we generated ticks for. Set it to the current offset on startup (we do not backfill slots we missed at startup) - watermark := info.AbsoluteSlot - currentEpoch, firstSlot, lastSlot := getEpochBounds(info) + // watermark is the last slot number we generated ticks for. Set it to the current offset on startup ( + // we do not backfill slots we missed at startup) + watermark := epochInfo.AbsoluteSlot + currentEpoch, firstSlot, lastSlot := getEpochBounds(epochInfo) currentEpochNumber.Set(float64(currentEpoch)) epochFirstSlot.Set(float64(firstSlot)) epochLastSlot.Set(float64(lastSlot)) + totalTransactionsTotal.Set(float64(epochInfo.TransactionCount)) + confirmedSlotHeight.Set(float64(epochInfo.AbsoluteSlot)) klog.Infof("Starting at slot %d in epoch %d (%d-%d)", firstSlot, currentEpoch, firstSlot, lastSlot) _, err = c.updateCounters(currentEpoch, watermark, &lastSlot) @@ -105,7 +105,7 @@ func (c *solanaCollector) WatchSlots(ctx context.Context) { // Get current slot height and epoch info ctx_, cancel := context.WithTimeout(context.Background(), httpTimeout) - info, err := c.rpcClient.GetEpochInfo(ctx_, rpc.CommitmentMax) + info, err := c.rpcClient.GetEpochInfo(ctx_, rpc.CommitmentFinalized) if err != nil { klog.Warningf("failed to fetch epoch info, retrying: %v", err) cancel() @@ -215,23 +215,23 @@ func (c *solanaCollector) updateCounters(epoch, firstSlot int64, lastSlotOpt *in ctx, cancel = context.WithTimeout(context.Background(), httpTimeout) defer cancel() - blockProduction, err := c.rpcClient.GetBlockProduction(ctx, &firstSlot, &lastSlot) + blockProductionValue, err := c.rpcClient.GetBlockProduction(ctx, nil, &firstSlot, &lastSlot) if err != nil { return 0, fmt.Errorf("failed to fetch block production, retrying: %v", err) } - for host, prod := range blockProduction.Hosts { - valid := float64(prod.BlocksProduced) - skipped := float64(prod.LeaderSlots - prod.BlocksProduced) + for identity, production := range blockProductionValue.ByIdentity { + valid := float64(production.BlocksProduced) + skipped := float64(production.LeaderSlots - production.BlocksProduced) epochStr := fmt.Sprintf("%d", epoch) - leaderSlotsTotal.WithLabelValues("valid", host).Add(valid) - leaderSlotsTotal.WithLabelValues("skipped", host).Add(skipped) + leaderSlotsTotal.WithLabelValues("valid", identity).Add(valid) + leaderSlotsTotal.WithLabelValues("skipped", identity).Add(skipped) - if len(c.leaderSlotAddresses) == 0 || slices.Contains(c.leaderSlotAddresses, host) { - leaderSlotsByEpoch.WithLabelValues("valid", host, epochStr).Add(valid) - leaderSlotsByEpoch.WithLabelValues("skipped", host, epochStr).Add(skipped) + if len(c.leaderSlotAddresses) == 0 || slices.Contains(c.leaderSlotAddresses, identity) { + leaderSlotsByEpoch.WithLabelValues("valid", identity, epochStr).Add(valid) + leaderSlotsByEpoch.WithLabelValues("skipped", identity, epochStr).Add(skipped) } klog.V(1).Infof( @@ -239,9 +239,9 @@ func (c *solanaCollector) updateCounters(epoch, firstSlot int64, lastSlotOpt *in epochStr, firstSlot, lastSlot, - host, - prod.BlocksProduced, - prod.LeaderSlots-prod.BlocksProduced, + identity, + production.BlocksProduced, + production.LeaderSlots-production.BlocksProduced, ) } diff --git a/cmd/solana_exporter/slots_test.go b/cmd/solana_exporter/slots_test.go index d8d8110..49c9bd1 100644 --- a/cmd/solana_exporter/slots_test.go +++ b/cmd/solana_exporter/slots_test.go @@ -34,7 +34,7 @@ func testBlockProductionMetric( host string, status string, ) { - hostInfo := staticBlockProduction.Hosts[host] + hostInfo := staticBlockProduction.ByIdentity[host] // get expected value depending on status: var expectedValue float64 switch status { diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index fc8414f..df65b4d 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client.go @@ -38,7 +38,9 @@ type Provider interface { // GetBlockProduction retrieves the block production information for the specified slot range. // The method takes a context for cancellation, and pointers to the first and last slots of the range. // It returns a BlockProduction struct containing the block production details, or an error if the operation fails. - GetBlockProduction(ctx context.Context, firstSlot *int64, lastSlot *int64) (*BlockProduction, error) + GetBlockProduction( + ctx context.Context, identity *string, firstSlot *int64, lastSlot *int64, + ) (*BlockProduction, error) // GetEpochInfo retrieves the information regarding the current epoch. // The method takes a context for cancellation and a commitment level to specify the desired state. @@ -70,16 +72,15 @@ func (c Commitment) MarshalJSON() ([]byte, error) { } const ( - // CommitmentMax represents the most recent block confirmed by the cluster super-majority - //as having reached maximum lockout. - CommitmentMax Commitment = "max" - // CommitmentRoot Most recent block having reached maximum lockout on this node. - CommitmentRoot Commitment = "root" - // CommitmentSingleGossip represents the most recent block that has been voted on - //by the cluster super-majority (optimistic confirmation). - CommitmentSingleGossip Commitment = "singleGossip" - // CommitmentRecent represents the nodes most recent block - CommitmentRecent Commitment = "recent" + // CommitmentFinalized level offers the highest level of certainty for a transaction on the Solana blockchain. + // A transaction is considered “Finalized” when it is included in a block that has been confirmed by a + // supermajority of the stake, and at least 31 additional confirmed blocks have been built on top of it. + CommitmentFinalized Commitment = "finalized" + // CommitmentConfirmed level is reached when a transaction is included in a block that has been voted on + // by a supermajority (66%+) of the network’s stake. + CommitmentConfirmed Commitment = "confirmed" + // CommitmentProcessed level represents a transaction that has been received by the network and included in a block. + CommitmentProcessed Commitment = "processed" ) func NewRPCClient(rpcAddr string) *Client { @@ -163,32 +164,42 @@ func (c *Client) GetSlot(ctx context.Context) (int64, error) { return resp.Result, nil } -func (c *Client) GetBlockProduction(ctx context.Context, firstSlot *int64, lastSlot *int64) (*BlockProduction, error) { +func (c *Client) GetBlockProduction( + ctx context.Context, identity *string, firstSlot *int64, lastSlot *int64, +) (*BlockProduction, error) { + // can't provide a last slot without a first: + if firstSlot == nil && lastSlot != nil { + panic("can't provide a last slot without a first!") + } + // format params: - params := make([]interface{}, 1) + config := make(map[string]interface{}) + if identity != nil { + config["identity"] = *identity + } if firstSlot != nil { - params[0] = map[string]interface{}{"range": blockProductionRange{FirstSlot: *firstSlot, LastSlot: lastSlot}} + blockRange := map[string]int64{"firstSlot": *firstSlot} + if lastSlot != nil { + blockRange["lastSlot"] = *lastSlot + } + config["range"] = blockRange + } + + var params []interface{} + if len(config) > 0 { + params = append(params, config) } // make request: - var resp response[blockProductionResult] + var resp response[contextualResult[BlockProduction]] if err := c.getResponse(ctx, "getBlockProduction", params, &resp); err != nil { return nil, err } - - // convert to BlockProduction format: - hosts := make(map[string]BlockProductionPerHost) - for id, arr := range resp.Result.Value.ByIdentity { - hosts[id] = BlockProductionPerHost{LeaderSlots: arr[0], BlocksProduced: arr[1]} - } - production := BlockProduction{ - FirstSlot: resp.Result.Value.Range.FirstSlot, LastSlot: *resp.Result.Value.Range.LastSlot, Hosts: hosts, - } - return &production, nil + return &resp.Result.Value, nil } func (c *Client) GetBalance(ctx context.Context, address string) (float64, error) { - var resp response[BalanceResult] + var resp response[contextualResult[int64]] if err := c.getResponse(ctx, "getBalance", []interface{}{address}, &resp); err != nil { return 0, err } diff --git a/pkg/rpc/responses.go b/pkg/rpc/responses.go index 1ca2c76..c8e9582 100644 --- a/pkg/rpc/responses.go +++ b/pkg/rpc/responses.go @@ -1,11 +1,23 @@ package rpc +import ( + "encoding/json" + "fmt" +) + type ( response[T any] struct { Result T `json:"result"` Error rpcError `json:"error"` } + contextualResult[T any] struct { + Value T `json:"value"` + Context struct { + Slot int64 `json:"slot"` + } + } + EpochInfo struct { // Current absolute slot in epoch AbsoluteSlot int64 `json:"absoluteSlot"` @@ -37,36 +49,35 @@ type ( Delinquent []VoteAccount `json:"delinquent"` } - blockProductionRange struct { - FirstSlot int64 `json:"firstSlot"` - LastSlot *int64 `json:"lastSlot,omitempty"` + HostProduction struct { + LeaderSlots int64 + BlocksProduced int64 } - blockProductionResult struct { - Value struct { - ByIdentity map[string][]int64 `json:"byIdentity"` - Range blockProductionRange `json:"range"` - } `json:"value"` + BlockProductionRange struct { + FirstSlot int64 `json:"firstSlot"` + LastSlot int64 `json:"lastSlot"` } - BlockProductionPerHost struct { - LeaderSlots int64 - BlocksProduced int64 + BlockProduction struct { + ByIdentity map[string]HostProduction `json:"byIdentity"` + Range BlockProductionRange `json:"range"` } +) - BlockProduction struct { - FirstSlot int64 - LastSlot int64 - Hosts map[string]BlockProductionPerHost +func (hp *HostProduction) UnmarshalJSON(data []byte) error { + var arr []int64 + if err := json.Unmarshal(data, &arr); err != nil { + return err } - BalanceResult struct { - Value int64 `json:"value"` - Context struct { - Slot int64 `json:"slot"` - } `json:"context"` + if len(arr) != 2 { + return fmt.Errorf("expected array of 2 integers, got %d", len(arr)) } -) + hp.LeaderSlots = arr[0] + hp.BlocksProduced = arr[1] + return nil +} func (r response[T]) getError() rpcError { return r.Error From ced08197cfcad2f3048f7c4f5acdc7af2c9ab190 Mon Sep 17 00:00:00 2001 From: Matt Johnstone Date: Wed, 2 Oct 2024 14:56:38 +0200 Subject: [PATCH 2/8] refactored GetVoteAccounts --- cmd/solana_exporter/exporter.go | 7 +----- cmd/solana_exporter/exporter_test.go | 8 +++++-- pkg/rpc/client.go | 36 +++++++++++++++++----------- 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/cmd/solana_exporter/exporter.go b/cmd/solana_exporter/exporter.go index 343b010..f2fae3d 100644 --- a/cmd/solana_exporter/exporter.go +++ b/cmd/solana_exporter/exporter.go @@ -114,12 +114,7 @@ func (c *solanaCollector) Describe(ch chan<- *prometheus.Desc) { } func (c *solanaCollector) collectVoteAccounts(ctx context.Context, ch chan<- prometheus.Metric) { - params := map[string]string{"commitment": string(rpc.CommitmentProcessed)} - if *votePubkey != "" { - params = map[string]string{"commitment": string(rpc.CommitmentProcessed), "votePubkey": *votePubkey} - } - - voteAccounts, err := c.rpcClient.GetVoteAccounts(ctx, []interface{}{params}) + voteAccounts, err := c.rpcClient.GetVoteAccounts(ctx, rpc.CommitmentProcessed, votePubkey) if err != nil { ch <- prometheus.NewInvalidMetric(c.totalValidatorsDesc, err) ch <- prometheus.NewInvalidMetric(c.validatorActivatedStake, err) diff --git a/cmd/solana_exporter/exporter_test.go b/cmd/solana_exporter/exporter_test.go index 4f7ad58..df11fb9 100644 --- a/cmd/solana_exporter/exporter_test.go +++ b/cmd/solana_exporter/exporter_test.go @@ -129,7 +129,9 @@ func (c *staticRPCClient) GetVersion(ctx context.Context) (string, error) { } //goland:noinspection GoUnusedParameter -func (c *staticRPCClient) GetVoteAccounts(ctx context.Context, params []interface{}) (*rpc.VoteAccounts, error) { +func (c *staticRPCClient) GetVoteAccounts( + ctx context.Context, commitment rpc.Commitment, votePubkey *string, +) (*rpc.VoteAccounts, error) { return &staticVoteAccounts, nil } @@ -266,7 +268,9 @@ func (c *dynamicRPCClient) GetVersion(ctx context.Context) (string, error) { } //goland:noinspection GoUnusedParameter -func (c *dynamicRPCClient) GetVoteAccounts(ctx context.Context, params []interface{}) (*rpc.VoteAccounts, error) { +func (c *dynamicRPCClient) GetVoteAccounts( + ctx context.Context, commitment rpc.Commitment, votePubkey *string, +) (*rpc.VoteAccounts, error) { var currentVoteAccounts, delinquentVoteAccounts []rpc.VoteAccount for identity, vote := range identityVotes { info := c.ValidatorInfos[identity] diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index df65b4d..5ad6c5d 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client.go @@ -22,10 +22,10 @@ type ( } rpcRequest struct { - Version string `json:"jsonrpc"` - ID int `json:"id"` - Method string `json:"method"` - Params []interface{} `json:"params"` + Version string `json:"jsonrpc"` + ID int `json:"id"` + Method string `json:"method"` + Params []any `json:"params"` } Commitment string @@ -56,7 +56,7 @@ type Provider interface { // The method takes a context for cancellation and a slice of parameters to filter the vote accounts. // It returns a pointer to a VoteAccounts struct containing the vote accounts details, // or an error if the operation fails. - GetVoteAccounts(ctx context.Context, params []interface{}) (*VoteAccounts, error) + GetVoteAccounts(ctx context.Context, commitment Commitment, votePubkey *string) (*VoteAccounts, error) // GetVersion retrieves the version of the Solana node. // The method takes a context for cancellation. @@ -87,7 +87,7 @@ func NewRPCClient(rpcAddr string) *Client { return &Client{httpClient: http.Client{}, rpcAddr: rpcAddr} } -func (c *Client) getResponse(ctx context.Context, method string, params []interface{}, result HasRPCError) error { +func (c *Client) getResponse(ctx context.Context, method string, params []any, result HasRPCError) error { // format request: request := &rpcRequest{Version: "2.0", ID: 1, Method: method, Params: params} buffer, err := json.Marshal(request) @@ -132,15 +132,23 @@ func (c *Client) getResponse(ctx context.Context, method string, params []interf func (c *Client) GetEpochInfo(ctx context.Context, commitment Commitment) (*EpochInfo, error) { var resp response[EpochInfo] - if err := c.getResponse(ctx, "getEpochInfo", []interface{}{commitment}, &resp); err != nil { + if err := c.getResponse(ctx, "getEpochInfo", []any{commitment}, &resp); err != nil { return nil, err } return &resp.Result, nil } -func (c *Client) GetVoteAccounts(ctx context.Context, params []interface{}) (*VoteAccounts, error) { +func (c *Client) GetVoteAccounts( + ctx context.Context, commitment Commitment, votePubkey *string, +) (*VoteAccounts, error) { + // format params: + config := map[string]string{"commitment": string(commitment)} + if votePubkey != nil { + config["votePubkey"] = *votePubkey + } + var resp response[VoteAccounts] - if err := c.getResponse(ctx, "getVoteAccounts", params, &resp); err != nil { + if err := c.getResponse(ctx, "getVoteAccounts", []any{config}, &resp); err != nil { return nil, err } return &resp.Result, nil @@ -150,7 +158,7 @@ func (c *Client) GetVersion(ctx context.Context) (string, error) { var resp response[struct { Version string `json:"solana-core"` }] - if err := c.getResponse(ctx, "getVersion", []interface{}{}, &resp); err != nil { + if err := c.getResponse(ctx, "getVersion", []any{}, &resp); err != nil { return "", err } return resp.Result.Version, nil @@ -158,7 +166,7 @@ func (c *Client) GetVersion(ctx context.Context) (string, error) { func (c *Client) GetSlot(ctx context.Context) (int64, error) { var resp response[int64] - if err := c.getResponse(ctx, "getSlot", []interface{}{}, &resp); err != nil { + if err := c.getResponse(ctx, "getSlot", []any{}, &resp); err != nil { return 0, err } return resp.Result, nil @@ -173,7 +181,7 @@ func (c *Client) GetBlockProduction( } // format params: - config := make(map[string]interface{}) + config := make(map[string]any) if identity != nil { config["identity"] = *identity } @@ -185,7 +193,7 @@ func (c *Client) GetBlockProduction( config["range"] = blockRange } - var params []interface{} + var params []any if len(config) > 0 { params = append(params, config) } @@ -200,7 +208,7 @@ func (c *Client) GetBlockProduction( func (c *Client) GetBalance(ctx context.Context, address string) (float64, error) { var resp response[contextualResult[int64]] - if err := c.getResponse(ctx, "getBalance", []interface{}{address}, &resp); err != nil { + if err := c.getResponse(ctx, "getBalance", []any{address}, &resp); err != nil { return 0, err } return float64(resp.Result.Value / 1_000_000_000), nil From 70990df4148e013c4966404b75732ae1a150bf69 Mon Sep 17 00:00:00 2001 From: Matt Johnstone Date: Wed, 2 Oct 2024 17:08:46 +0200 Subject: [PATCH 3/8] refactoring WatchSlots --- cmd/solana_exporter/exporter.go | 13 ++++++++++--- cmd/solana_exporter/slots.go | 4 ++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/cmd/solana_exporter/exporter.go b/cmd/solana_exporter/exporter.go index f2fae3d..e04e631 100644 --- a/cmd/solana_exporter/exporter.go +++ b/cmd/solana_exporter/exporter.go @@ -32,11 +32,14 @@ func init() { } type solanaCollector struct { - rpcClient rpc.Provider - slotPace time.Duration - balanceAddresses []string + rpcClient rpc.Provider + + // config: + slotPace time.Duration + balanceAddresses []string leaderSlotAddresses []string + /// descriptors: totalValidatorsDesc *prometheus.Desc validatorActivatedStake *prometheus.Desc validatorLastVote *prometheus.Desc @@ -44,6 +47,10 @@ type solanaCollector struct { validatorDelinquent *prometheus.Desc solanaVersion *prometheus.Desc balances *prometheus.Desc + + // state: + epochWatermark int64 + slotWatermark int64 } func createSolanaCollector( diff --git a/cmd/solana_exporter/slots.go b/cmd/solana_exporter/slots.go index 2b306d3..bfd27a9 100644 --- a/cmd/solana_exporter/slots.go +++ b/cmd/solana_exporter/slots.go @@ -215,12 +215,12 @@ func (c *solanaCollector) updateCounters(epoch, firstSlot int64, lastSlotOpt *in ctx, cancel = context.WithTimeout(context.Background(), httpTimeout) defer cancel() - blockProductionValue, err := c.rpcClient.GetBlockProduction(ctx, nil, &firstSlot, &lastSlot) + blockProduction, err := c.rpcClient.GetBlockProduction(ctx, nil, &firstSlot, &lastSlot) if err != nil { return 0, fmt.Errorf("failed to fetch block production, retrying: %v", err) } - for identity, production := range blockProductionValue.ByIdentity { + for identity, production := range blockProduction.ByIdentity { valid := float64(production.BlocksProduced) skipped := float64(production.LeaderSlots - production.BlocksProduced) From 8e4f5e8d6f1f3b188787d5297f832a6106fad62f Mon Sep 17 00:00:00 2001 From: Matt Johnstone Date: Thu, 3 Oct 2024 17:33:28 +0200 Subject: [PATCH 4/8] refactored WatchSlots() to SlotWatcher --- cmd/solana_exporter/exporter.go | 7 +- cmd/solana_exporter/slots.go | 256 ++++++++++++++---------------- cmd/solana_exporter/slots_test.go | 9 +- cmd/solana_exporter/utils.go | 25 +++ pkg/rpc/client.go | 4 + 5 files changed, 157 insertions(+), 144 deletions(-) create mode 100644 cmd/solana_exporter/utils.go diff --git a/cmd/solana_exporter/exporter.go b/cmd/solana_exporter/exporter.go index e04e631..852924d 100644 --- a/cmd/solana_exporter/exporter.go +++ b/cmd/solana_exporter/exporter.go @@ -47,10 +47,6 @@ type solanaCollector struct { validatorDelinquent *prometheus.Desc solanaVersion *prometheus.Desc balances *prometheus.Desc - - // state: - epochWatermark int64 - slotWatermark int64 } func createSolanaCollector( @@ -247,7 +243,8 @@ func main() { collector := NewSolanaCollector(*rpcAddr, balAddresses, lsAddresses) - go collector.WatchSlots(context.Background()) + slotWatcher := SlotWatcher{client: collector.rpcClient} + go slotWatcher.WatchSlots(context.Background(), collector.slotPace) prometheus.MustRegister(collector) http.Handle("/metrics", promhttp.Handler()) diff --git a/cmd/solana_exporter/slots.go b/cmd/solana_exporter/slots.go index bfd27a9..191e6b5 100644 --- a/cmd/solana_exporter/slots.go +++ b/cmd/solana_exporter/slots.go @@ -15,6 +15,20 @@ const ( slotPacerSchedule = 1 * time.Second ) +type SlotWatcher struct { + client rpc.Provider + + // currentEpoch is the current epoch we are watching + currentEpoch int64 + // firstSlot is the first slot [inclusive] of the current epoch which we are watching + firstSlot int64 + // lastSlot is the last slot [inclusive] of the current epoch which we are watching + lastSlot int64 + + // slotWatermark is the last (most recent) slot we have tracked + slotWatermark int64 +} + var ( totalTransactionsTotal = prometheus.NewGauge(prometheus.GaugeOpts{ Name: "solana_confirmed_transactions_total", @@ -68,182 +82,154 @@ func init() { prometheus.MustRegister(leaderSlotsByEpoch) } -func (c *solanaCollector) WatchSlots(ctx context.Context) { - // Get current slot height and epoch info - ctx_, cancel := context.WithTimeout(ctx, httpTimeout) - epochInfo, err := c.rpcClient.GetEpochInfo(ctx_, rpc.CommitmentFinalized) - if err != nil { - klog.Fatalf("failed to fetch epoch info, bailing out: %v", err) - } - cancel() - - // watermark is the last slot number we generated ticks for. Set it to the current offset on startup ( - // we do not backfill slots we missed at startup) - watermark := epochInfo.AbsoluteSlot - currentEpoch, firstSlot, lastSlot := getEpochBounds(epochInfo) - currentEpochNumber.Set(float64(currentEpoch)) - epochFirstSlot.Set(float64(firstSlot)) - epochLastSlot.Set(float64(lastSlot)) - totalTransactionsTotal.Set(float64(epochInfo.TransactionCount)) - confirmedSlotHeight.Set(float64(epochInfo.AbsoluteSlot)) - - klog.Infof("Starting at slot %d in epoch %d (%d-%d)", firstSlot, currentEpoch, firstSlot, lastSlot) - _, err = c.updateCounters(currentEpoch, watermark, &lastSlot) - if err != nil { - klog.Error(err) - } - ticker := time.NewTicker(c.slotPace) +func (c *SlotWatcher) WatchSlots(ctx context.Context, pace time.Duration) { + ticker := time.NewTicker(pace) + defer ticker.Stop() + + klog.Infof("Starting slot watcher") for { select { case <-ctx.Done(): - klog.Infof("Stopping WatchSlots() at slot %v", watermark) + klog.Infof("Stopping WatchSlots() at slot %v", c.slotWatermark) return - default: <-ticker.C - // Get current slot height and epoch info - ctx_, cancel := context.WithTimeout(context.Background(), httpTimeout) - info, err := c.rpcClient.GetEpochInfo(ctx_, rpc.CommitmentFinalized) + ctx_, cancel := context.WithTimeout(ctx, httpTimeout) + epochInfo, err := c.client.GetEpochInfo(ctx_, rpc.CommitmentFinalized) if err != nil { - klog.Warningf("failed to fetch epoch info, retrying: %v", err) - cancel() - continue + klog.Warningf("Failed to get epoch info, bailing out: %v", err) } cancel() - if watermark == info.AbsoluteSlot { - klog.V(2).Infof("slot has not advanced at %d, skipping", info.AbsoluteSlot) - continue - } - - if currentEpoch != info.Epoch { - klog.Infof( - "changing epoch from %d to %d. Watermark: %d, lastSlot: %d", - currentEpoch, - info.Epoch, - watermark, - lastSlot, - ) - - last, err := c.updateCounters(currentEpoch, watermark, &lastSlot) - if err != nil { - klog.Error(err) - continue - } - - klog.Infof( - "counters updated to slot %d (+%d), epoch %d (slots %d-%d, %d remaining)", - last, - last-watermark, - currentEpoch, - firstSlot, - lastSlot, - lastSlot-last, - ) - - watermark = last - currentEpoch, firstSlot, lastSlot = getEpochBounds(info) - - currentEpochNumber.Set(float64(currentEpoch)) - epochFirstSlot.Set(float64(firstSlot)) - epochLastSlot.Set(float64(lastSlot)) + // if we are running for the first time, then we need to set our tracking numbers: + if c.currentEpoch == 0 { + c.trackEpoch(epochInfo) } - totalTransactionsTotal.Set(float64(info.TransactionCount)) - confirmedSlotHeight.Set(float64(info.AbsoluteSlot)) + totalTransactionsTotal.Set(float64(epochInfo.TransactionCount)) + confirmedSlotHeight.Set(float64(epochInfo.AbsoluteSlot)) - last, err := c.updateCounters(currentEpoch, watermark, nil) - if err != nil { - klog.Info(err) + // if we get here, then the tracking numbers are set, so this is a "normal" run. + // start by checking if we have progressed since last run: + if epochInfo.AbsoluteSlot <= c.slotWatermark { + klog.Infof("confirmed slot number has not advanced from %v, skipping", c.slotWatermark) continue } - klog.Infof( - "counters updated to slot %d (offset %d, +%d), epoch %d (slots %d-%d, %d remaining)", - last, - info.SlotIndex, - last-watermark, - currentEpoch, - firstSlot, - lastSlot, - lastSlot-last, - ) - - watermark = last + if epochInfo.Epoch > c.currentEpoch { + c.closeCurrentEpoch(ctx, epochInfo) + } + + // update block production metrics up until the current slot: + c.fetchAndEmitBlockProduction(ctx, epochInfo.AbsoluteSlot) } } } -// getEpochBounds returns the epoch, first slot and last slot given an EpochInfo struct -func getEpochBounds(info *rpc.EpochInfo) (int64, int64, int64) { - firstSlot := info.AbsoluteSlot - info.SlotIndex - lastSlot := firstSlot + info.SlotsInEpoch +func (c *SlotWatcher) trackEpoch(epoch *rpc.EpochInfo) { + firstSlot, lastSlot := getEpochBounds(epoch) + // if we haven't yet set c.currentEpoch, that (hopefully) means this is the initial setup, + // and so we can simply store the tracking numbers + if c.currentEpoch == 0 { + c.currentEpoch = epoch.Epoch + c.firstSlot = firstSlot + c.lastSlot = lastSlot + // we don't backfill on startup. we set the watermark to current slot minus 1, + //such that the current slot is the first slot tracked + c.slotWatermark = epoch.AbsoluteSlot - 1 + } else { + // if c.currentEpoch is already set, then, just in case, run some checks + // to make sure that we make sure that we are tracking consistently + assertf(epoch.Epoch == c.currentEpoch+1, "epoch jumped from %v to %v", c.currentEpoch, epoch.Epoch) + assertf( + firstSlot == c.lastSlot+1, + "first slot %v does not follow from current last slot %v", + firstSlot, + c.lastSlot, + ) - return info.Epoch, firstSlot, lastSlot -} + // and also, make sure that we have completed the last epoch: + assertf( + c.slotWatermark == c.lastSlot, + "can't update epoch when watermark %v hasn't reached current last-slot %v", + c.slotWatermark, + c.lastSlot, + ) -func (c *solanaCollector) updateCounters(epoch, firstSlot int64, lastSlotOpt *int64) (int64, error) { - ctx, cancel := context.WithTimeout(context.Background(), httpTimeout) - defer cancel() + // the epoch number is progressing correctly, so we can update our tracking numbers: + c.currentEpoch = epoch.Epoch + c.firstSlot = firstSlot + c.lastSlot = lastSlot + } - var lastSlot int64 - var err error + // emit epoch bounds: + currentEpochNumber.Set(float64(c.currentEpoch)) + epochFirstSlot.Set(float64(c.firstSlot)) + epochLastSlot.Set(float64(c.lastSlot)) +} - if lastSlotOpt == nil { - lastSlot, err = c.rpcClient.GetSlot(ctx) +func (c *SlotWatcher) closeCurrentEpoch(ctx context.Context, newEpoch *rpc.EpochInfo) { + c.fetchAndEmitBlockProduction(ctx, c.lastSlot) + c.trackEpoch(newEpoch) +} - if err != nil { - return 0, fmt.Errorf("error while getting the last slot: %v", err) - } - klog.V(2).Infof("Setting lastSlot to %d", lastSlot) - } else { - lastSlot = *lastSlotOpt - klog.Infof("Got lastSlot: %d", lastSlot) +func (c *SlotWatcher) checkValidSlotRange(from, to int64) error { + if from < c.firstSlot || to > c.lastSlot { + return fmt.Errorf( + "start-end slots (%v -> %v) is not contained within current epoch %v range (%v -> %v)", + from, + to, + c.currentEpoch, + c.firstSlot, + c.lastSlot, + ) } + return nil +} - if firstSlot > lastSlot { - return 0, fmt.Errorf( - "in epoch %d, firstSlot (%d) > lastSlot (%d), this should not happen, not updating", - epoch, - firstSlot, - lastSlot, - ) +func (c *SlotWatcher) fetchAndEmitBlockProduction(ctx context.Context, endSlot int64) { + // add 1 because GetBlockProduction's range is inclusive, and the watermark is already tracked + startSlot := c.slotWatermark + 1 + klog.Infof("Fetching block production in [%v -> %v]", startSlot, endSlot) + + // make sure the bounds are contained within the epoch we are currently watching: + if err := c.checkValidSlotRange(startSlot, endSlot); err != nil { + panic(err) } - ctx, cancel = context.WithTimeout(context.Background(), httpTimeout) + // fetch block production: + ctx, cancel := context.WithTimeout(ctx, httpTimeout) defer cancel() - - blockProduction, err := c.rpcClient.GetBlockProduction(ctx, nil, &firstSlot, &lastSlot) + blockProduction, err := c.client.GetBlockProduction(ctx, nil, &startSlot, &endSlot) if err != nil { - return 0, fmt.Errorf("failed to fetch block production, retrying: %v", err) + klog.Warningf("Failed to get block production, bailing out: %v", err) } - for identity, production := range blockProduction.ByIdentity { + // emit the metrics: + for address, production := range blockProduction.ByIdentity { valid := float64(production.BlocksProduced) skipped := float64(production.LeaderSlots - production.BlocksProduced) - epochStr := fmt.Sprintf("%d", epoch) + epochStr := fmt.Sprintf("%d", c.currentEpoch) - leaderSlotsTotal.WithLabelValues("valid", identity).Add(valid) - leaderSlotsTotal.WithLabelValues("skipped", identity).Add(skipped) + leaderSlotsTotal.WithLabelValues("valid", address).Add(valid) + leaderSlotsTotal.WithLabelValues("skipped", address).Add(skipped) - if len(c.leaderSlotAddresses) == 0 || slices.Contains(c.leaderSlotAddresses, identity) { - leaderSlotsByEpoch.WithLabelValues("valid", identity, epochStr).Add(valid) - leaderSlotsByEpoch.WithLabelValues("skipped", identity, epochStr).Add(skipped) + if len(c.leaderSlotAddresses) == 0 || slices.Contains(c.leaderSlotAddresses, address) { + leaderSlotsByEpoch.WithLabelValues("valid", address, epochStr).Add(valid) + leaderSlotsByEpoch.WithLabelValues("skipped", address, epochStr).Add(skipped) } - - klog.V(1).Infof( - "Epoch %s, slots %d-%d, node %s: Added %d valid and %d skipped slots", - epochStr, - firstSlot, - lastSlot, - identity, - production.BlocksProduced, - production.LeaderSlots-production.BlocksProduced, - ) } - return lastSlot, nil + klog.Infof("Fetched block production in [%v -> %v]", startSlot, endSlot) + // update the slot watermark: + c.slotWatermark = endSlot +} + +// getEpochBounds returns the first slot and last slot within an [inclusive] Epoch +func getEpochBounds(info *rpc.EpochInfo) (int64, int64) { + firstSlot := info.AbsoluteSlot - info.SlotIndex + return firstSlot, firstSlot + info.SlotsInEpoch - 1 } diff --git a/cmd/solana_exporter/slots_test.go b/cmd/solana_exporter/slots_test.go index 49c9bd1..ce0c013 100644 --- a/cmd/solana_exporter/slots_test.go +++ b/cmd/solana_exporter/slots_test.go @@ -92,14 +92,14 @@ func TestSolanaCollector_WatchSlots_Static(t *testing.T) { leaderSlotsByEpoch.Reset() collector := createSolanaCollector(&staticRPCClient{}, 100*time.Millisecond, identities, []string{}) + watcher := SlotWatcher{client: collector.rpcClient} prometheus.NewPedanticRegistry().MustRegister(collector) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - go collector.WatchSlots(ctx) + go watcher.WatchSlots(ctx, collector.slotPace) time.Sleep(1 * time.Second) - firstSlot := staticEpochInfo.AbsoluteSlot - staticEpochInfo.SlotIndex - lastSlot := firstSlot + staticEpochInfo.SlotsInEpoch + firstSlot, lastSlot := getEpochBounds(&staticEpochInfo) tests := []struct { expectedValue float64 metric prometheus.Gauge @@ -146,6 +146,7 @@ func TestSolanaCollector_WatchSlots_Dynamic(t *testing.T) { // create clients: client := newDynamicRPCClient() collector := createSolanaCollector(client, 300*time.Millisecond, identities, []string{}) + watcher := SlotWatcher{client: client} prometheus.NewPedanticRegistry().MustRegister(collector) // start client/collector and wait a bit: @@ -156,7 +157,7 @@ func TestSolanaCollector_WatchSlots_Dynamic(t *testing.T) { slotsCtx, slotsCancel := context.WithCancel(context.Background()) defer slotsCancel() - go collector.WatchSlots(slotsCtx) + go watcher.WatchSlots(slotsCtx, collector.slotPace) time.Sleep(time.Second) initial := getSlotMetricValues() diff --git a/cmd/solana_exporter/utils.go b/cmd/solana_exporter/utils.go new file mode 100644 index 0000000..35e9adf --- /dev/null +++ b/cmd/solana_exporter/utils.go @@ -0,0 +1,25 @@ +package main + +import ( + "encoding/json" + "fmt" +) + +func assertf(condition bool, format string, args ...any) { + if !condition { + panic(fmt.Errorf(format, args...)) + } +} + +// prettyPrint is just a useful debugging function +func prettyPrint(obj any) { + // For pretty-printed JSON, use json.MarshalIndent + prettyJSON, err := json.MarshalIndent(obj, "", " ") + if err != nil { + fmt.Println("Error marshalling to pretty JSON:", err, ". obj: ", obj) + return + } + + // Print the pretty JSON string + fmt.Println(string(prettyJSON)) +} diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index 5ad6c5d..6126d1f 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client.go @@ -188,6 +188,10 @@ func (c *Client) GetBlockProduction( if firstSlot != nil { blockRange := map[string]int64{"firstSlot": *firstSlot} if lastSlot != nil { + // make sure first and last slot are in order: + if *firstSlot > *lastSlot { + panic(fmt.Errorf("last slot %v is greater than first slot %v", *lastSlot, *firstSlot)) + } blockRange["lastSlot"] = *lastSlot } config["range"] = blockRange From 5c522d7b12a5cc69743c4c879b921b2028790c3a Mon Sep 17 00:00:00 2001 From: Matt Johnstone Date: Thu, 3 Oct 2024 17:43:48 +0200 Subject: [PATCH 5/8] added documentation --- cmd/solana_exporter/slots.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/solana_exporter/slots.go b/cmd/solana_exporter/slots.go index 191e6b5..907f648 100644 --- a/cmd/solana_exporter/slots.go +++ b/cmd/solana_exporter/slots.go @@ -24,7 +24,6 @@ type SlotWatcher struct { firstSlot int64 // lastSlot is the last slot [inclusive] of the current epoch which we are watching lastSlot int64 - // slotWatermark is the last (most recent) slot we have tracked slotWatermark int64 } @@ -128,6 +127,8 @@ func (c *SlotWatcher) WatchSlots(ctx context.Context, pace time.Duration) { } } +// trackEpoch takes in a new rpc.EpochInfo and sets the SlotWatcher tracking metrics accordingly, +// and updates the prometheus gauges associated with those metrics. func (c *SlotWatcher) trackEpoch(epoch *rpc.EpochInfo) { firstSlot, lastSlot := getEpochBounds(epoch) // if we haven't yet set c.currentEpoch, that (hopefully) means this is the initial setup, @@ -170,11 +171,14 @@ func (c *SlotWatcher) trackEpoch(epoch *rpc.EpochInfo) { epochLastSlot.Set(float64(c.lastSlot)) } +// closeCurrentEpoch is called when an epoch change-over happens, and we need to make sure we track the last +// remaining slots in the "current" epoch before we start tracking the new one. func (c *SlotWatcher) closeCurrentEpoch(ctx context.Context, newEpoch *rpc.EpochInfo) { c.fetchAndEmitBlockProduction(ctx, c.lastSlot) c.trackEpoch(newEpoch) } +// checkValidSlotRange makes sure that the slot range we are going to query is within the current epoch we are tracking. func (c *SlotWatcher) checkValidSlotRange(from, to int64) error { if from < c.firstSlot || to > c.lastSlot { return fmt.Errorf( @@ -189,6 +193,8 @@ func (c *SlotWatcher) checkValidSlotRange(from, to int64) error { return nil } +// fetchAndEmitBlockProduction fetches block production up to the provided endSlot, emits the prometheus metrics, +// and updates the SlotWatcher.slotWatermark accordingly func (c *SlotWatcher) fetchAndEmitBlockProduction(ctx context.Context, endSlot int64) { // add 1 because GetBlockProduction's range is inclusive, and the watermark is already tracked startSlot := c.slotWatermark + 1 From 64332b368b791b67fbaf674a02e1805541380393 Mon Sep 17 00:00:00 2001 From: Matt Johnstone Date: Fri, 4 Oct 2024 06:44:35 +0200 Subject: [PATCH 6/8] added json to contextualResult --- pkg/rpc/responses.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/rpc/responses.go b/pkg/rpc/responses.go index c8e9582..1e971f9 100644 --- a/pkg/rpc/responses.go +++ b/pkg/rpc/responses.go @@ -15,7 +15,7 @@ type ( Value T `json:"value"` Context struct { Slot int64 `json:"slot"` - } + } `json:"context"` } EpochInfo struct { From db8373b81d6a00dde0cdacabd9f38480af97c3e5 Mon Sep 17 00:00:00 2001 From: Matt Johnstone Date: Fri, 4 Oct 2024 13:04:23 +0200 Subject: [PATCH 7/8] added leader-slot-addresses to SlotWatcher --- cmd/solana_exporter/exporter.go | 6 +++--- cmd/solana_exporter/slots.go | 6 ++++++ cmd/solana_exporter/slots_test.go | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cmd/solana_exporter/exporter.go b/cmd/solana_exporter/exporter.go index 852924d..e7556d4 100644 --- a/cmd/solana_exporter/exporter.go +++ b/cmd/solana_exporter/exporter.go @@ -35,8 +35,8 @@ type solanaCollector struct { rpcClient rpc.Provider // config: - slotPace time.Duration - balanceAddresses []string + slotPace time.Duration + balanceAddresses []string leaderSlotAddresses []string /// descriptors: @@ -243,7 +243,7 @@ func main() { collector := NewSolanaCollector(*rpcAddr, balAddresses, lsAddresses) - slotWatcher := SlotWatcher{client: collector.rpcClient} + slotWatcher := NewCollectorSlotWatcher(collector) go slotWatcher.WatchSlots(context.Background(), collector.slotPace) prometheus.MustRegister(collector) diff --git a/cmd/solana_exporter/slots.go b/cmd/solana_exporter/slots.go index 907f648..f8cf953 100644 --- a/cmd/solana_exporter/slots.go +++ b/cmd/solana_exporter/slots.go @@ -18,6 +18,8 @@ const ( type SlotWatcher struct { client rpc.Provider + leaderSlotAddresses []string + // currentEpoch is the current epoch we are watching currentEpoch int64 // firstSlot is the first slot [inclusive] of the current epoch which we are watching @@ -71,6 +73,10 @@ var ( ) ) +func NewCollectorSlotWatcher(collector *solanaCollector) *SlotWatcher { + return &SlotWatcher{client: collector.rpcClient, leaderSlotAddresses: collector.leaderSlotAddresses} +} + func init() { prometheus.MustRegister(totalTransactionsTotal) prometheus.MustRegister(confirmedSlotHeight) diff --git a/cmd/solana_exporter/slots_test.go b/cmd/solana_exporter/slots_test.go index ce0c013..2d2ee88 100644 --- a/cmd/solana_exporter/slots_test.go +++ b/cmd/solana_exporter/slots_test.go @@ -92,7 +92,7 @@ func TestSolanaCollector_WatchSlots_Static(t *testing.T) { leaderSlotsByEpoch.Reset() collector := createSolanaCollector(&staticRPCClient{}, 100*time.Millisecond, identities, []string{}) - watcher := SlotWatcher{client: collector.rpcClient} + watcher := NewCollectorSlotWatcher(collector) prometheus.NewPedanticRegistry().MustRegister(collector) ctx, cancel := context.WithCancel(context.Background()) defer cancel() From bf217cfc225bfc899102fafaafa45fa5ea3e9ad6 Mon Sep 17 00:00:00 2001 From: Matt Johnstone Date: Sun, 6 Oct 2024 16:58:15 +0200 Subject: [PATCH 8/8] implemented jeffs nits --- cmd/solana_exporter/slots.go | 2 +- cmd/solana_exporter/utils.go | 18 ++---------------- pkg/rpc/client.go | 9 +++++---- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/cmd/solana_exporter/slots.go b/cmd/solana_exporter/slots.go index f8cf953..75ddf73 100644 --- a/cmd/solana_exporter/slots.go +++ b/cmd/solana_exporter/slots.go @@ -208,7 +208,7 @@ func (c *SlotWatcher) fetchAndEmitBlockProduction(ctx context.Context, endSlot i // make sure the bounds are contained within the epoch we are currently watching: if err := c.checkValidSlotRange(startSlot, endSlot); err != nil { - panic(err) + klog.Fatalf("invalid slot range: %v", err) } // fetch block production: diff --git a/cmd/solana_exporter/utils.go b/cmd/solana_exporter/utils.go index 35e9adf..0ea2505 100644 --- a/cmd/solana_exporter/utils.go +++ b/cmd/solana_exporter/utils.go @@ -1,25 +1,11 @@ package main import ( - "encoding/json" - "fmt" + "k8s.io/klog/v2" ) func assertf(condition bool, format string, args ...any) { if !condition { - panic(fmt.Errorf(format, args...)) + klog.Fatalf(format, args...) } } - -// prettyPrint is just a useful debugging function -func prettyPrint(obj any) { - // For pretty-printed JSON, use json.MarshalIndent - prettyJSON, err := json.MarshalIndent(obj, "", " ") - if err != nil { - fmt.Println("Error marshalling to pretty JSON:", err, ". obj: ", obj) - return - } - - // Print the pretty JSON string - fmt.Println(string(prettyJSON)) -} diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index 6126d1f..ee7367f 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client.go @@ -92,14 +92,14 @@ func (c *Client) getResponse(ctx context.Context, method string, params []any, r request := &rpcRequest{Version: "2.0", ID: 1, Method: method, Params: params} buffer, err := json.Marshal(request) if err != nil { - panic(err) + klog.Fatalf("failed to marshal request: %v", err) } klog.V(2).Infof("jsonrpc request: %s", string(buffer)) // make request: req, err := http.NewRequestWithContext(ctx, "POST", c.rpcAddr, bytes.NewBuffer(buffer)) if err != nil { - panic(err) + klog.Fatalf("failed to create request: %v", err) } req.Header.Set("content-type", "application/json") @@ -177,7 +177,7 @@ func (c *Client) GetBlockProduction( ) (*BlockProduction, error) { // can't provide a last slot without a first: if firstSlot == nil && lastSlot != nil { - panic("can't provide a last slot without a first!") + klog.Fatalf("can't provide a last slot without a first!") } // format params: @@ -190,7 +190,8 @@ func (c *Client) GetBlockProduction( if lastSlot != nil { // make sure first and last slot are in order: if *firstSlot > *lastSlot { - panic(fmt.Errorf("last slot %v is greater than first slot %v", *lastSlot, *firstSlot)) + err := fmt.Errorf("last slot %v is greater than first slot %v", *lastSlot, *firstSlot) + klog.Fatalf("%v", err) } blockRange["lastSlot"] = *lastSlot }