diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index dc962d1..bf91219 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -8,8 +8,6 @@ concurrency: cancel-in-progress: true permissions: - contents: read - issues: write pull-requests: write env: @@ -22,7 +20,7 @@ jobs: steps: - uses: actions/checkout@v6 with: - fetch-depth: 1 + fetch-depth: 0 - uses: ./.github/actions/setup with: @@ -32,23 +30,5 @@ jobs: - name: Run benchmark run: just test-and-benchmark - - name: Post CU report comment - if: github.event.pull_request.head.repo.full_name == github.repository - env: - GH_TOKEN: ${{ github.token }} - run: | - PR=${{ github.event.pull_request.number }} - MARKER="" - REPORT="$MARKER - $(cat tests/integration-tests/cu_report.md)" - - COMMENT_ID=$(gh api "repos/${{ github.repository }}/issues/${PR}/comments" \ - --jq ".[] | select(.body | contains(\"$MARKER\")) | .id" | head -1) - - if [ -n "$COMMENT_ID" ]; then - gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" \ - -X PATCH -f body="$REPORT" - else - gh api "repos/${{ github.repository }}/issues/${PR}/comments" \ - -f body="$REPORT" - fi + - name: Report compute units + uses: solana-developers/github-actions/cu-benchmark@e846103a578b3170a9a14824bcdf38d5dcf59ac0 diff --git a/tests/integration-tests/src/utils/cu_tracker.rs b/tests/integration-tests/src/utils/cu_tracker.rs index 397b3a1..5764141 100644 --- a/tests/integration-tests/src/utils/cu_tracker.rs +++ b/tests/integration-tests/src/utils/cu_tracker.rs @@ -6,13 +6,10 @@ //! //! # Usage //! -//! ```ignore -//! // Record directly from transaction result - instruction type is auto-detected -//! let result = build_and_send_transaction(...); -//! record_transaction(&result, &ix); -//! -//! // Report is automatically output when tests complete if CU_REPORT is set -//! ``` +//! Recording happens automatically when tests send transactions through the +//! shared test helpers (gated on `CU_REPORT`); the report is written when the +//! test binary exits. Call `record_cu` directly only for transactions sent by +//! some other path. use std::borrow::ToOwned; use std::collections::HashMap; @@ -23,18 +20,14 @@ use std::sync::Mutex; use std::sync::OnceLock; use std::vec::Vec; -use litesvm::types::TransactionResult; -use solana_instruction::Instruction; use tabled::settings::Style; use tabled::{Table, Tabled}; -use crate::SubscriptionsInstruction; - static TRACKER: OnceLock> = OnceLock::new(); /// Check if CU tracking is enabled via CU_REPORT environment variable. /// Caches the result to avoid repeated env lookups. -fn is_tracking_enabled() -> bool { +pub fn is_tracking_enabled() -> bool { static ENABLED: OnceLock = OnceLock::new(); ENABLED.get_or_init(|| std::env::var("CU_REPORT").is_ok()).to_owned() } @@ -44,15 +37,15 @@ fn global_tracker() -> &'static Mutex { TRACKER.get_or_init(|| Mutex::new(CuTracker::new())) } -/// Record a transaction result to the global tracker. -/// Parses instruction type from the provided instruction. -/// Only records if CU_REPORT environment variable is set. -/// Returns the CU consumed, or None if the transaction failed or tracking is disabled. -pub fn record_transaction(result: &TransactionResult, ix: &Instruction) -> Option { +/// Record a CU measurement for a named instruction to the global tracker. +/// Only records if the CU_REPORT environment variable is set. +pub fn record_cu(name: &str, cus: u64) { if !is_tracking_enabled() { - return None; + return; + } + if let Ok(mut tracker) = global_tracker().lock() { + tracker.record(name, cus); } - global_tracker().lock().ok().and_then(|mut tracker| tracker.record(result, ix)) } /// Output the CU report if the CU_REPORT environment variable is set. @@ -69,7 +62,7 @@ pub fn output_report_if_enabled() { } const MICRO_LAMPORTS: u64 = 1_000_000; -const LAMPOSTS_PER_SOL: f64 = 1_000_000_000.0; +const LAMPORTS_PER_SOL: f64 = 1_000_000_000.0; const BASE_FEE_LAMPORTS: u64 = 5_000; // Different rate for Microlamports per CU @@ -82,7 +75,7 @@ fn calculate_sol_cost(cu: u64, rate: u64) -> f64 { let priority_fee_micro = cu * rate; let priority_fee_lamports = priority_fee_micro / MICRO_LAMPORTS; let total_lamports = BASE_FEE_LAMPORTS + priority_fee_lamports; - total_lamports as f64 / LAMPOSTS_PER_SOL + total_lamports as f64 / LAMPORTS_PER_SOL } /// Statistics for a single instruction type (displayed in table). @@ -92,12 +85,8 @@ pub struct InstructionStats { pub instruction: String, #[tabled(rename = "Samples")] pub count: usize, - #[tabled(rename = "Min CUs")] - pub min: u64, - #[tabled(rename = "Max CUs")] - pub max: u64, - #[tabled(rename = "Avg CUs")] - pub avg: u64, + #[tabled(rename = "CUs")] + pub cus: u64, #[tabled(rename = "Est Cost (Low) [SOL]")] pub cost_low: String, #[tabled(rename = "Est Cost (Med) [SOL]")] @@ -120,22 +109,9 @@ impl CuTracker { Self { measurements: HashMap::new() } } - /// Record CU from a transaction result. - /// Parses instruction type from the provided instruction. - /// Returns the CU consumed, or None if the transaction failed. - pub fn record(&mut self, result: &TransactionResult, ix: &Instruction) -> Option { - if !is_tracking_enabled() { - return None; - } - - let tx = result.as_ref().ok()?; - - if let Ok(instruction) = SubscriptionsInstruction::from_bytes(&ix.data) { - let instruction_name = instruction.to_string(); - self.measurements.entry(instruction_name).or_default().push(tx.compute_units_consumed); - } - - Some(tx.compute_units_consumed) + /// Record a CU measurement for a named instruction. + pub fn record(&mut self, name: &str, cus: u64) { + self.measurements.entry(name.to_string()).or_default().push(cus); } /// Get the total number of recorded measurements. @@ -155,24 +131,13 @@ impl CuTracker { .iter() .map(|(instruction, measurements)| { let count = measurements.len(); - let min = *measurements.iter().min().unwrap_or(&0); - let max = *measurements.iter().max().unwrap_or(&0); - let avg = if count > 0 { measurements.iter().sum::() / count as u64 } else { 0 }; - - let cost_low = format!("{:.9}", calculate_sol_cost(avg, RATE_LOW)); - let cost_med = format!("{:.9}", calculate_sol_cost(avg, RATE_MED)); - let cost_high = format!("{:.9}", calculate_sol_cost(avg, RATE_HIGH)); - - InstructionStats { - instruction: instruction.clone(), - count, - min, - max, - avg, - cost_low, - cost_med, - cost_high, - } + let cus = *measurements.iter().min().unwrap_or(&0); + + let cost_low = format!("{:.9}", calculate_sol_cost(cus, RATE_LOW)); + let cost_med = format!("{:.9}", calculate_sol_cost(cus, RATE_MED)); + let cost_high = format!("{:.9}", calculate_sol_cost(cus, RATE_HIGH)); + + InstructionStats { instruction: instruction.clone(), count, cus, cost_low, cost_med, cost_high } }) .collect(); @@ -216,19 +181,6 @@ impl CuTracker { println!("CU report written to: {}", path); Ok(()) } - - /// Check if CU_REPORT environment variable is set and write report if so. - /// Returns true if report was written. - pub fn write_if_enabled(&self, path: &str) -> bool { - if is_tracking_enabled() { - if let Err(e) = self.write_to_file(path) { - eprintln!("Failed to write CU report: {}", e); - return false; - } - return true; - } - false - } } impl Default for CuTracker { diff --git a/tests/integration-tests/src/utils/test_helpers.rs b/tests/integration-tests/src/utils/test_helpers.rs index 2f4a491..c4d208b 100644 --- a/tests/integration-tests/src/utils/test_helpers.rs +++ b/tests/integration-tests/src/utils/test_helpers.rs @@ -44,9 +44,10 @@ use crate::{ state::common::PlanStatus, tests::{ constants::{PROGRAM_ID, SYSTEM_PROGRAM_ID}, - cu_tracker::record_transaction, + cu_tracker::{is_tracking_enabled, record_cu}, pda::{get_delegation_pda, get_plan_pda, get_subscription_authority_pda, get_subscription_pda}, }, + SubscriptionsInstruction, }; /// Converts number of minutes into seconds @@ -112,8 +113,11 @@ pub fn build_and_send_transaction( let result = litesvm.send_transaction(tx); litesvm.expire_blockhash(); - // Record CU consumption to global tracker - record_transaction(&result, ix); + if is_tracking_enabled() { + if let (Ok(meta), Ok(parsed)) = (result.as_ref(), SubscriptionsInstruction::from_bytes(&ix.data)) { + record_cu(&parsed.to_string(), meta.compute_units_consumed); + } + } result }