forked from asymmetric-research/solana-exporter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request asymmetric-research#25 from asymmetric-research/pr…
…ovider-interface Static tests
- Loading branch information
Showing
11 changed files
with
348 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"github.com/prometheus/client_golang/prometheus" | ||
"github.com/prometheus/client_golang/prometheus/testutil" | ||
"github.com/stretchr/testify/assert" | ||
"testing" | ||
"time" | ||
) | ||
|
||
var staticCollector = createSolanaCollector(&staticRPCClient{}) | ||
|
||
func TestSolanaCollector_Collect(t *testing.T) { | ||
prometheus.NewPedanticRegistry().MustRegister(staticCollector) | ||
|
||
testCases := map[string]string{ | ||
"solana_active_validators": ` | ||
# HELP solana_active_validators Total number of active validators by state | ||
# TYPE solana_active_validators gauge | ||
solana_active_validators{state="current"} 2 | ||
solana_active_validators{state="delinquent"} 1 | ||
`, | ||
"solana_validator_activated_stake": ` | ||
# HELP solana_validator_activated_stake Activated stake per validator | ||
# TYPE solana_validator_activated_stake gauge | ||
solana_validator_activated_stake{nodekey="4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae",pubkey="xKUz6fZ79SXnjGYaYhhYTYQBoRUBoCyuDMkBa1tL3zU"} 49 | ||
solana_validator_activated_stake{nodekey="B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 42 | ||
solana_validator_activated_stake{nodekey="C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 43 | ||
`, | ||
"solana_validator_last_vote": ` | ||
# HELP solana_validator_last_vote Last voted slot per validator | ||
# TYPE solana_validator_last_vote gauge | ||
solana_validator_last_vote{nodekey="4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae",pubkey="xKUz6fZ79SXnjGYaYhhYTYQBoRUBoCyuDMkBa1tL3zU"} 92 | ||
solana_validator_last_vote{nodekey="B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 147 | ||
solana_validator_last_vote{nodekey="C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 148 | ||
`, | ||
"solana_validator_root_slot": ` | ||
# HELP solana_validator_root_slot Root slot per validator | ||
# TYPE solana_validator_root_slot gauge | ||
solana_validator_root_slot{nodekey="4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae",pubkey="xKUz6fZ79SXnjGYaYhhYTYQBoRUBoCyuDMkBa1tL3zU"} 3 | ||
solana_validator_root_slot{nodekey="B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 18 | ||
solana_validator_root_slot{nodekey="C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 19 | ||
`, | ||
"solana_validator_delinquent": ` | ||
# HELP solana_validator_delinquent Whether a validator is delinquent | ||
# TYPE solana_validator_delinquent gauge | ||
solana_validator_delinquent{nodekey="4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae",pubkey="xKUz6fZ79SXnjGYaYhhYTYQBoRUBoCyuDMkBa1tL3zU"} 1 | ||
solana_validator_delinquent{nodekey="B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 0 | ||
solana_validator_delinquent{nodekey="C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 0 | ||
`, | ||
"solana_node_version": ` | ||
# HELP solana_node_version Node version of solana | ||
# TYPE solana_node_version gauge | ||
solana_node_version{version="1.16.7"} 1 | ||
`, | ||
} | ||
|
||
for testName, expectedValue := range testCases { | ||
t.Run( | ||
testName, | ||
func(t *testing.T) { | ||
if err := testutil.CollectAndCompare( | ||
staticCollector, | ||
bytes.NewBufferString(expectedValue), | ||
testName, | ||
); err != nil { | ||
t.Errorf("unexpected collecting result for %s: \n%s", testName, err) | ||
} | ||
}, | ||
) | ||
} | ||
} | ||
|
||
func TestSolanaCollector_WatchSlots(t *testing.T) { | ||
go staticCollector.WatchSlots() | ||
time.Sleep(1 * time.Second) | ||
|
||
tests := []struct { | ||
expectedValue float64 | ||
metric prometheus.Gauge | ||
}{ | ||
{ | ||
expectedValue: float64(staticEpochInfo.AbsoluteSlot), | ||
metric: confirmedSlotHeight, | ||
}, | ||
{ | ||
expectedValue: float64(staticEpochInfo.TransactionCount), | ||
metric: totalTransactionsTotal, | ||
}, | ||
{ | ||
expectedValue: float64(staticEpochInfo.Epoch), | ||
metric: currentEpochNumber, | ||
}, | ||
{ | ||
expectedValue: float64(staticEpochInfo.AbsoluteSlot - staticEpochInfo.SlotIndex), | ||
metric: epochFirstSlot, | ||
}, | ||
{ | ||
expectedValue: float64(staticEpochInfo.AbsoluteSlot - staticEpochInfo.SlotIndex + staticEpochInfo.SlotsInEpoch), | ||
metric: epochLastSlot, | ||
}, | ||
} | ||
|
||
for _, testCase := range tests { | ||
name := extractName(testCase.metric.Desc()) | ||
t.Run( | ||
name, | ||
func(t *testing.T) { | ||
assert.Equal(t, testCase.expectedValue, testutil.ToFloat64(testCase.metric)) | ||
}, | ||
) | ||
} | ||
|
||
hosts := []string{ | ||
"B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD", | ||
"C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD", | ||
"4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae", | ||
} | ||
metrics := map[string]*prometheus.CounterVec{ | ||
"solana_leader_slots_total": leaderSlotsTotal, | ||
"solana_leader_slots_by_epoch": leaderSlotsByEpoch, | ||
} | ||
statuses := []string{"valid", "skipped"} | ||
for name, metric := range metrics { | ||
// subtest for each metric: | ||
t.Run(name, func(t *testing.T) { | ||
for _, status := range statuses { | ||
// sub subtest for each status (as each one requires a different calc) | ||
t.Run(status, func(t *testing.T) { | ||
for _, host := range hosts { | ||
testBlockProductionMetric(t, metric, host, status) | ||
} | ||
}) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func testBlockProductionMetric( | ||
t *testing.T, | ||
metric *prometheus.CounterVec, | ||
host string, | ||
status string, | ||
) { | ||
hostInfo := staticBlockProduction.Hosts[host] | ||
// get expected value depending on status: | ||
var expectedValue float64 | ||
switch status { | ||
case "valid": | ||
expectedValue = float64(hostInfo.BlocksProduced) | ||
case "skipped": | ||
expectedValue = float64(hostInfo.LeaderSlots - hostInfo.BlocksProduced) | ||
} | ||
// get labels (leaderSlotsByEpoch requires an extra one) | ||
labels := []string{status, host} | ||
if metric == leaderSlotsByEpoch { | ||
labels = append(labels, fmt.Sprintf("%d", staticEpochInfo.Epoch)) | ||
} | ||
// now we can do the assertion: | ||
assert.Equal(t, expectedValue, testutil.ToFloat64(metric.WithLabelValues(labels...))) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"github.com/certusone/solana_exporter/pkg/rpc" | ||
"github.com/prometheus/client_golang/prometheus" | ||
"regexp" | ||
) | ||
|
||
type ( | ||
staticRPCClient struct{} | ||
// TODO: create dynamicRPCClient + according tests! | ||
) | ||
|
||
var ( | ||
staticEpochInfo = rpc.EpochInfo{ | ||
AbsoluteSlot: 166598, | ||
BlockHeight: 166500, | ||
Epoch: 27, | ||
SlotIndex: 2790, | ||
SlotsInEpoch: 8192, | ||
TransactionCount: 22661093, | ||
} | ||
staticBlockProduction = rpc.BlockProduction{ | ||
FirstSlot: 1000, | ||
LastSlot: 2000, | ||
Hosts: map[string]rpc.BlockProductionPerHost{ | ||
"B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD": { | ||
LeaderSlots: 400, | ||
BlocksProduced: 360, | ||
}, | ||
"C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD": { | ||
LeaderSlots: 300, | ||
BlocksProduced: 296, | ||
}, | ||
"4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae": { | ||
LeaderSlots: 300, | ||
BlocksProduced: 0, | ||
}, | ||
}, | ||
} | ||
) | ||
|
||
//goland:noinspection GoUnusedParameter | ||
func (c *staticRPCClient) GetEpochInfo(ctx context.Context, commitment rpc.Commitment) (*rpc.EpochInfo, error) { | ||
return &staticEpochInfo, nil | ||
} | ||
|
||
//goland:noinspection GoUnusedParameter | ||
func (c *staticRPCClient) GetSlot(ctx context.Context) (int64, error) { | ||
return staticEpochInfo.AbsoluteSlot, nil | ||
} | ||
|
||
//goland:noinspection GoUnusedParameter | ||
func (c *staticRPCClient) GetVersion(ctx context.Context) (*string, error) { | ||
version := "1.16.7" | ||
return &version, nil | ||
} | ||
|
||
//goland:noinspection GoUnusedParameter | ||
func (c *staticRPCClient) GetVoteAccounts( | ||
ctx context.Context, | ||
params []interface{}, | ||
) (*rpc.VoteAccounts, error) { | ||
voteAccounts := rpc.VoteAccounts{ | ||
Current: []rpc.VoteAccount{ | ||
{ | ||
ActivatedStake: 42, | ||
Commission: 0, | ||
EpochCredits: [][]int{ | ||
{1, 64, 0}, | ||
{2, 192, 64}, | ||
}, | ||
EpochVoteAccount: true, | ||
LastVote: 147, | ||
NodePubkey: "B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD", | ||
RootSlot: 18, | ||
VotePubkey: "3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw", | ||
}, | ||
{ | ||
ActivatedStake: 43, | ||
Commission: 1, | ||
EpochCredits: [][]int{ | ||
{2, 65, 1}, | ||
{3, 193, 65}, | ||
}, | ||
EpochVoteAccount: true, | ||
LastVote: 148, | ||
NodePubkey: "C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD", | ||
RootSlot: 19, | ||
VotePubkey: "4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw", | ||
}, | ||
}, | ||
Delinquent: []rpc.VoteAccount{ | ||
{ | ||
ActivatedStake: 49, | ||
Commission: 2, | ||
EpochCredits: [][]int{ | ||
{10, 594, 6}, | ||
{9, 98, 4}, | ||
}, | ||
EpochVoteAccount: true, | ||
LastVote: 92, | ||
NodePubkey: "4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae", | ||
RootSlot: 3, | ||
VotePubkey: "xKUz6fZ79SXnjGYaYhhYTYQBoRUBoCyuDMkBa1tL3zU", | ||
}, | ||
}, | ||
} | ||
return &voteAccounts, nil | ||
} | ||
|
||
//goland:noinspection GoUnusedParameter | ||
func (c *staticRPCClient) GetBlockProduction( | ||
ctx context.Context, | ||
firstSlot *int64, | ||
lastSlot *int64, | ||
) (rpc.BlockProduction, error) { | ||
return staticBlockProduction, nil | ||
} | ||
|
||
// extractName takes a Prometheus descriptor and returns its name | ||
func extractName(desc *prometheus.Desc) string { | ||
// Get the string representation of the descriptor | ||
descString := desc.String() | ||
|
||
// Use regex to extract the metric name and help message from the descriptor string | ||
reName := regexp.MustCompile(`fqName: "([^"]+)"`) | ||
|
||
nameMatch := reName.FindStringSubmatch(descString) | ||
|
||
var name string | ||
if len(nameMatch) > 1 { | ||
name = nameMatch[1] | ||
} | ||
|
||
return name | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.