use std::{str::FromStr, time::Instant};
use bincode::Options as _;
use rand::thread_rng;
use solana_perf::test_tx::new_test_vote_tx;
use solana_sanitize::Sanitize as _;
use solana_transaction::Transaction;
use solana_vote::vote_parser;
use solana_vote_program::vote_instruction::VoteInstruction;
const DEFAULT_ITERATIONS: usize = 200_000;
const DEFAULT_COUNT: usize = 32;
enum ProfileStage {
TransactionDeserialize,
TransactionSanitize,
ParsedVoteSlot,
InstructionDeserialize,
}
impl ProfileStage {
const fn as_str(&self) -> &'static str {
match self {
Self::TransactionDeserialize => "transaction_deserialize",
Self::TransactionSanitize => "transaction_sanitize",
Self::ParsedVoteSlot => "parsed_vote_slot",
Self::InstructionDeserialize => "instruction_deserialize",
}
}
}
impl FromStr for ProfileStage {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"transaction_deserialize" => Ok(Self::TransactionDeserialize),
"transaction_sanitize" => Ok(Self::TransactionSanitize),
"parsed_vote_slot" => Ok(Self::ParsedVoteSlot),
"instruction_deserialize" => Ok(Self::InstructionDeserialize),
_ => Err(format!(
"unknown stage {value:?}; expected transaction_deserialize, transaction_sanitize, parsed_vote_slot, or instruction_deserialize"
)),
}
}
}
fn main() -> Result<(), String> {
let iterations = std::env::var("SOF_GOSSIP_VOTE_PROFILE_ITERS")
.ok()
.and_then(|raw| raw.parse::<usize>().ok())
.filter(|value| *value > 0)
.unwrap_or(DEFAULT_ITERATIONS);
let count = std::env::var("SOF_GOSSIP_VOTE_PROFILE_COUNT")
.ok()
.and_then(|raw| raw.parse::<usize>().ok())
.filter(|value| *value > 0)
.unwrap_or(DEFAULT_COUNT);
let stage = std::env::args()
.nth(1)
.as_deref()
.map(ProfileStage::from_str)
.transpose()?
.unwrap_or(ProfileStage::TransactionDeserialize);
let fixtures = build_vote_fixtures(count)?;
let started_at = Instant::now();
let mut work_items = 0usize;
for _ in 0..iterations {
match stage {
ProfileStage::TransactionDeserialize => {
for bytes in &fixtures.transaction_bytes {
let _: Transaction = bincode::deserialize(bytes).map_err(|error| {
format!("failed to deserialize vote transaction fixture: {error}")
})?;
work_items = work_items.saturating_add(1);
}
}
ProfileStage::TransactionSanitize => {
for transaction in &fixtures.transactions {
transaction.sanitize().map_err(|error| {
format!("failed to sanitize vote transaction fixture: {error}")
})?;
work_items = work_items.saturating_add(1);
}
}
ProfileStage::ParsedVoteSlot => {
for transaction in &fixtures.transactions {
let (_, vote_transaction, ..) =
vote_parser::parse_vote_transaction(transaction)
.ok_or_else(|| "failed to parse vote transaction fixture".to_owned())?;
let _ = vote_transaction.last_voted_slot();
work_items = work_items.saturating_add(1);
}
}
ProfileStage::InstructionDeserialize => {
for bytes in &fixtures.instruction_bytes {
let vote_instruction = bincode::options()
.with_limit(solana_packet::PACKET_DATA_SIZE as u64)
.with_fixint_encoding()
.allow_trailing_bytes()
.deserialize::<VoteInstruction>(bytes)
.map_err(|error| {
format!("failed to deserialize vote instruction fixture: {error}")
})?;
let _ = vote_instruction.last_voted_slot();
work_items = work_items.saturating_add(1);
}
}
}
}
let ns_per_iteration_x1000 = started_at
.elapsed()
.as_nanos()
.saturating_mul(1000)
.checked_div(iterations as u128)
.ok_or_else(|| "invalid iteration count".to_owned())?;
println!(
"vote_fixtures={} stage={} iterations={} ns_per_iteration={}.{:03} work_items={}",
count,
stage.as_str(),
iterations,
ns_per_iteration_x1000 / 1000,
ns_per_iteration_x1000 % 1000,
work_items
);
Ok(())
}
struct VoteFixtures {
transactions: Vec<Transaction>,
transaction_bytes: Vec<Vec<u8>>,
instruction_bytes: Vec<Vec<u8>>,
}
fn build_vote_fixtures(count: usize) -> Result<VoteFixtures, String> {
let mut rng = thread_rng();
let mut transactions = Vec::with_capacity(count);
let mut transaction_bytes = Vec::with_capacity(count);
let mut instruction_bytes = Vec::with_capacity(count);
for index in 0..count {
let transaction = new_test_vote_tx(&mut rng);
let first_instruction_data = transaction
.message()
.instructions
.first()
.map(|instruction| instruction.data.clone())
.ok_or_else(|| format!("vote transaction fixture {index} missing first instruction"))?;
let tx_bytes = bincode::serialize(&transaction).map_err(|error| {
format!("failed to serialize vote transaction fixture {index}: {error}")
})?;
transactions.push(transaction);
transaction_bytes.push(tx_bytes);
instruction_bytes.push(first_instruction_data);
}
Ok(VoteFixtures {
transactions,
transaction_bytes,
instruction_bytes,
})
}