Skip to content

Instantly share code, notes, and snippets.

@adrena-orex
Last active January 14, 2025 18:26
Show Gist options
  • Select an option

  • Save adrena-orex/d5de51e3c1f409954003bf3272e92651 to your computer and use it in GitHub Desktop.

Select an option

Save adrena-orex/d5de51e3c1f409954003bf3272e92651 to your computer and use it in GitHub Desktop.
Track instruction computing unit consumption and size in Rust integration tests
// Execute instruction and get back instruction size and used compute units
let (ix_size, tx_used_cu) = utils::create_and_execute_adrena_ix(
program_test_ctx,
adrena::accounts::AddCollateralLong {
// ...
}
.to_account_metas(None),
adrena::instruction::AddCollateralLong { params },
Some(&payer.pubkey()),
&[owner, payer],
None,
None,
)
.await?;
// Log usage
utils::log_instruction("add_collateral_long", "public", ix_size, tx_used_cu);
# Only package specifics to tracking
[dev-dependencies]
colored = "=2.2.0" # Bring some colors to the terminal
ctor = "=0.2.9" # Used to execute a function when tests finishes
once_cell = "=1.20.2"
lazy_static = "=1.5.0"
//
// Globally store information about instruction sizes and compute units
// When tests finish, print the log of instructions with their sizes and compute units
//
#[derive(Debug, Default, Clone)]
pub struct InstructionInfo {
name: String,
category: String,
max_size_bytes: usize,
min_size_bytes: usize,
max_compute_units: u64,
min_compute_units: u64,
}
impl InstructionInfo {
pub fn new(name: &str, category: &str, size_bytes: usize, compute_units: u64) -> Self {
Self {
name: name.to_string(),
category: category.to_string(),
max_size_bytes: size_bytes,
min_size_bytes: size_bytes,
max_compute_units: compute_units,
min_compute_units: compute_units,
}
}
}
// Store info globally
pub static INSTRUCTION_LOG: Lazy<Arc<Mutex<Vec<InstructionInfo>>>> =
Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
// Function that runs when the process stops
#[dtor]
fn print_instruction_log() {
const MAX_NAME_LENGTH: usize = 40; // Set the max display width for names
let log = INSTRUCTION_LOG.lock().unwrap();
// Clone and group by category
let mut grouped_by_category: HashMap<String, Vec<InstructionInfo>> = HashMap::new();
for entry in log.iter() {
grouped_by_category
.entry(entry.category.clone())
.or_default()
.push(entry.clone());
}
// Iterate over each category
for (category, mut instructions) in grouped_by_category {
println!("\nCategory: {}", category.bold());
// Sort each category by max_compute_units in descending order
instructions.sort_by(|a, b| b.max_compute_units.cmp(&a.max_compute_units));
// Print the header
println!(
"{:<42} {:<29} {:<28}",
"Instruction".bold(),
"Compute Units".bold(),
"TX Byte Size".bold()
);
println!(
"{:<40} {:<14} {:<14} {:<13} {:<13}",
"",
"Max".bold(),
"Min".bold(),
"Max".bold(),
"Min".bold()
);
println!("{}", "-".repeat(84));
// Print each instruction
for info in instructions.iter() {
let truncated_name = if info.name.len() > MAX_NAME_LENGTH {
format!("{}...", &info.name[..MAX_NAME_LENGTH - 3])
} else {
info.name.clone()
};
// Apply colors based on thresholds
let max_cu_color = if info.max_compute_units > 1_000_000 {
info.max_compute_units.to_string().red()
} else if info.max_compute_units > 300_000 {
info.max_compute_units.to_string().yellow()
} else {
info.max_compute_units.to_string().green()
};
let min_cu_color = if info.min_compute_units > 1_000_000 {
info.min_compute_units.to_string().red()
} else if info.min_compute_units > 300_000 {
info.min_compute_units.to_string().yellow()
} else {
info.min_compute_units.to_string().green()
};
let max_size_color = if info.max_size_bytes > 1000 {
info.max_size_bytes.to_string().red()
} else if info.max_size_bytes > 600 {
info.max_size_bytes.to_string().yellow()
} else {
info.max_size_bytes.to_string().green()
};
let min_size_color = if info.min_size_bytes > 1000 {
info.min_size_bytes.to_string().red()
} else if info.min_size_bytes > 600 {
info.min_size_bytes.to_string().yellow()
} else {
info.min_size_bytes.to_string().green()
};
println!(
"{:<40} {:<14} {:<14} {:<13} {:<13}",
truncated_name, max_cu_color, min_cu_color, max_size_color, min_size_color
);
}
}
}
#[tokio::test]
pub async fn test_integration_part1() {
// Your tests here
tests_suite::position::add_collateral_long().await;
}
// Return the instruction size and the compute units consumed
pub async fn create_and_execute_adrena_ix<T: InstructionData, U: Signers>(
program_test_ctx: &RwLock<ProgramTestContext>,
accounts_meta: Vec<AccountMeta>,
args: T,
payer: Option<&Pubkey>,
signing_keypairs: &U,
pre_ix: Option<solana_sdk::instruction::Instruction>,
post_ix: Option<solana_sdk::instruction::Instruction>,
) -> std::result::Result<(usize, u64), BanksClientError> {
// Returns main instruction size
let ix = solana_sdk::instruction::Instruction {
program_id: adrena::id(),
accounts: accounts_meta,
data: args.data(),
};
let ix_size = serialize(&ix).unwrap().len();
let mut ctx = program_test_ctx.write().await;
let last_blockhash = ctx.last_blockhash;
let banks_client = &mut ctx.banks_client;
let mut instructions: Vec<solana_sdk::instruction::Instruction> = Vec::new();
// Max allowed compute unit
// https://github.com/solana-labs/solana/blob/5c2d7b6b8ae2afb443facfa5e7f608dbb41d3fdc/program-runtime/src/compute_budget_processor.rs#L19C1-L19C51
instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(1_400_000));
if let Some(pre_ix) = pre_ix {
instructions.push(pre_ix);
}
instructions.push(ix.clone());
if let Some(post_ix) = post_ix {
instructions.push(post_ix);
}
let tx: Transaction = solana_sdk::transaction::Transaction::new_signed_with_payer(
instructions.as_slice(),
payer,
signing_keypairs,
last_blockhash,
);
let ret = banks_client
.process_transaction_with_metadata(tx)
.await
.unwrap();
if ret.result.is_err() {
return Err(ret.result.err().unwrap().into());
}
Ok((ix_size, ret.metadata.unwrap().compute_units_consumed))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment