From 739a5f036a43e7ba4f29a4ab527711d799abcbcd Mon Sep 17 00:00:00 2001 From: Jo D Date: Mon, 8 Jun 2026 14:48:57 -0400 Subject: [PATCH 1/8] refactor(cu-tracker): take explicit instruction name Make the CU tracker generic (record_cu(name, cus)) instead of decoding the program instruction enum inside the tracker, so the same module can be shared across program repos. The SubscriptionsInstruction::from_bytes name decode now lives in the build_and_send_transaction test glue. Behavior and the cu_report.md output format are unchanged. --- .../integration-tests/src/utils/cu_tracker.rs | 66 +++++-------------- .../src/utils/test_helpers.rs | 10 ++- 2 files changed, 23 insertions(+), 53 deletions(-) diff --git a/tests/integration-tests/src/utils/cu_tracker.rs b/tests/integration-tests/src/utils/cu_tracker.rs index 397b3a1..6e23635 100644 --- a/tests/integration-tests/src/utils/cu_tracker.rs +++ b/tests/integration-tests/src/utils/cu_tracker.rs @@ -6,13 +6,9 @@ //! //! # 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 inside `build_and_send_transaction` when +//! `CU_REPORT` is set; the report is written when the test binary exits. Call +//! `record_cu` directly only when sending a transaction by some other path. use std::borrow::ToOwned; use std::collections::HashMap; @@ -23,18 +19,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 +36,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 +61,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 +74,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). @@ -120,22 +112,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. @@ -216,19 +195,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 } From 833d7e50a7d42430620832f3de14f78c2cdf964c Mon Sep 17 00:00:00 2001 From: Jo D Date: Mon, 8 Jun 2026 15:38:01 -0400 Subject: [PATCH 2/8] ci: post CU report via shared cu-benchmark action Replace the inline gh-api comment step with solana-developers/github-actions/cu-benchmark, which diffs against a committed cu_baseline.md and posts per-instruction deltas. --- .github/workflows/benchmark.yml | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index dc962d1..8e9926f 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -8,8 +8,7 @@ concurrency: cancel-in-progress: true permissions: - contents: read - issues: write + contents: write pull-requests: write env: @@ -22,7 +21,7 @@ jobs: steps: - uses: actions/checkout@v6 with: - fetch-depth: 1 + fetch-depth: 0 - uses: ./.github/actions/setup with: @@ -32,23 +31,7 @@ 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 + with: + commit-baseline: ${{ github.event.pull_request.head.repo.full_name == github.repository }} From 6da3cab3dd0cf3e22c9be916906efa413eb537fc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 19:40:45 +0000 Subject: [PATCH 3/8] chore(cu): update CU baseline --- tests/integration-tests/cu_baseline.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/integration-tests/cu_baseline.md diff --git a/tests/integration-tests/cu_baseline.md b/tests/integration-tests/cu_baseline.md new file mode 100644 index 0000000..c9a3b1c --- /dev/null +++ b/tests/integration-tests/cu_baseline.md @@ -0,0 +1,20 @@ +# Compute Unit Report + +| Instruction | Samples | Min CUs | Max CUs | Avg CUs | Est Cost (Low) [SOL] | Est Cost (Med) [SOL] | Est Cost (High) [SOL] | +|------------------------------|---------|---------|---------|---------|----------------------|----------------------|-----------------------| +| cancel_subscription | 22 | 1720 | 2030 | 1919 | 0.000005000 | 0.000005076 | 0.000005959 | +| close_subscription_authority | 10 | 1803 | 1833 | 1806 | 0.000005000 | 0.000005072 | 0.000005903 | +| create_fixed_delegation | 41 | 3517 | 9522 | 5375 | 0.000005001 | 0.000005215 | 0.000007687 | +| create_plan | 97 | 3436 | 12449 | 5124 | 0.000005001 | 0.000005204 | 0.000007562 | +| create_recurring_delegation | 30 | 3550 | 15555 | 4706 | 0.000005001 | 0.000005188 | 0.000007353 | +| delete_plan | 9 | 359 | 359 | 359 | 0.000005000 | 0.000005014 | 0.000005179 | +| init_subscription_authority | 174 | 6226 | 22726 | 9342 | 0.000005002 | 0.000005373 | 0.000009671 | +| resume_subscription | 3 | 1723 | 1723 | 1723 | 0.000005000 | 0.000005068 | 0.000005861 | +| revoke_delegation | 19 | 255 | 519 | 353 | 0.000005000 | 0.000005014 | 0.000005176 | +| subscribe | 32 | 6485 | 14009 | 8038 | 0.000005002 | 0.000005321 | 0.000009019 | +| transfer_fixed | 9 | 5476 | 10603 | 6864 | 0.000005002 | 0.000005274 | 0.000008432 | +| transfer_recurring | 20 | 5591 | 14684 | 8642 | 0.000005002 | 0.000005345 | 0.000009321 | +| transfer_subscription | 10 | 5799 | 7341 | 6143 | 0.000005001 | 0.000005245 | 0.000008071 | +| update_plan | 22 | 424 | 503 | 477 | 0.000005000 | 0.000005019 | 0.000005238 | + +*Generated: 2026-06-08* From 2b73bc9eb89be47aaa0dcd5d792d7b0fa1aa5a8b Mon Sep 17 00:00:00 2001 From: Jo D Date: Mon, 8 Jun 2026 15:56:49 -0400 Subject: [PATCH 4/8] docs(cu-tracker): make recording-site comment generic --- tests/integration-tests/src/utils/cu_tracker.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/integration-tests/src/utils/cu_tracker.rs b/tests/integration-tests/src/utils/cu_tracker.rs index 6e23635..38e2ccb 100644 --- a/tests/integration-tests/src/utils/cu_tracker.rs +++ b/tests/integration-tests/src/utils/cu_tracker.rs @@ -6,9 +6,10 @@ //! //! # Usage //! -//! Recording happens automatically inside `build_and_send_transaction` when -//! `CU_REPORT` is set; the report is written when the test binary exits. Call -//! `record_cu` directly only when sending a transaction by some other path. +//! 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; From ae33c65025612e36c86e65c63b858ed320fb6697 Mon Sep 17 00:00:00 2001 From: Jo D Date: Mon, 8 Jun 2026 16:00:24 -0400 Subject: [PATCH 5/8] ci: prettier-ignore generated CU baseline/report --- .prettierignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.prettierignore b/.prettierignore index 91eefa0..0118f1e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,10 @@ idl/ # Generated files clients/typescript/src/generated/ +# CU benchmark (generated) +**/cu_baseline.md +**/cu_report.md + # Build outputs dist/ build/ From 5f5e48d370506855fb694666b4f41ced7fd80c4e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 20:03:10 +0000 Subject: [PATCH 6/8] chore(cu): update CU baseline --- tests/integration-tests/cu_baseline.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/integration-tests/cu_baseline.md b/tests/integration-tests/cu_baseline.md index c9a3b1c..85174e7 100644 --- a/tests/integration-tests/cu_baseline.md +++ b/tests/integration-tests/cu_baseline.md @@ -4,17 +4,17 @@ |------------------------------|---------|---------|---------|---------|----------------------|----------------------|-----------------------| | cancel_subscription | 22 | 1720 | 2030 | 1919 | 0.000005000 | 0.000005076 | 0.000005959 | | close_subscription_authority | 10 | 1803 | 1833 | 1806 | 0.000005000 | 0.000005072 | 0.000005903 | -| create_fixed_delegation | 41 | 3517 | 9522 | 5375 | 0.000005001 | 0.000005215 | 0.000007687 | -| create_plan | 97 | 3436 | 12449 | 5124 | 0.000005001 | 0.000005204 | 0.000007562 | -| create_recurring_delegation | 30 | 3550 | 15555 | 4706 | 0.000005001 | 0.000005188 | 0.000007353 | +| create_fixed_delegation | 41 | 3517 | 17050 | 5009 | 0.000005001 | 0.000005200 | 0.000007504 | +| create_plan | 97 | 3436 | 13949 | 4939 | 0.000005001 | 0.000005197 | 0.000007469 | +| create_recurring_delegation | 30 | 3550 | 8055 | 4606 | 0.000005001 | 0.000005184 | 0.000007303 | | delete_plan | 9 | 359 | 359 | 359 | 0.000005000 | 0.000005014 | 0.000005179 | -| init_subscription_authority | 174 | 6226 | 22726 | 9342 | 0.000005002 | 0.000005373 | 0.000009671 | +| init_subscription_authority | 174 | 4772 | 22468 | 9687 | 0.000005002 | 0.000005387 | 0.000009843 | | resume_subscription | 3 | 1723 | 1723 | 1723 | 0.000005000 | 0.000005068 | 0.000005861 | | revoke_delegation | 19 | 255 | 519 | 353 | 0.000005000 | 0.000005014 | 0.000005176 | -| subscribe | 32 | 6485 | 14009 | 8038 | 0.000005002 | 0.000005321 | 0.000009019 | -| transfer_fixed | 9 | 5476 | 10603 | 6864 | 0.000005002 | 0.000005274 | 0.000008432 | -| transfer_recurring | 20 | 5591 | 14684 | 8642 | 0.000005002 | 0.000005345 | 0.000009321 | -| transfer_subscription | 10 | 5799 | 7341 | 6143 | 0.000005001 | 0.000005245 | 0.000008071 | +| subscribe | 32 | 6485 | 18485 | 7898 | 0.000005002 | 0.000005315 | 0.000008949 | +| transfer_fixed | 9 | 5476 | 14479 | 8531 | 0.000005002 | 0.000005341 | 0.000009265 | +| transfer_recurring | 20 | 5591 | 14591 | 7592 | 0.000005002 | 0.000005303 | 0.000008796 | +| transfer_subscription | 10 | 5799 | 8841 | 6743 | 0.000005002 | 0.000005269 | 0.000008371 | | update_plan | 22 | 424 | 503 | 477 | 0.000005000 | 0.000005019 | 0.000005238 | *Generated: 2026-06-08* From 268ac04717f8697329b6aab62b690bf9d24f99dd Mon Sep 17 00:00:00 2001 From: Jo D Date: Mon, 8 Jun 2026 16:32:09 -0400 Subject: [PATCH 7/8] =?UTF-8?q?ci(cu):=20report-only=20benchmark=20?= =?UTF-8?q?=E2=80=94=20disable=20baseline=20commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CU is not deterministic across runs (on-chain ATA find_program_address bump search over random keypair owners), so a committed baseline churns every run. Switch to report-only: commit-baseline off, drop contents:write, remove cu_baseline.md, track Min CUs (most stable single value). --- .github/workflows/benchmark.yml | 3 -- tests/integration-tests/cu_baseline.md | 20 ----------- .../integration-tests/src/utils/cu_tracker.rs | 33 +++++-------------- 3 files changed, 9 insertions(+), 47 deletions(-) delete mode 100644 tests/integration-tests/cu_baseline.md diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 8e9926f..bf91219 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -8,7 +8,6 @@ concurrency: cancel-in-progress: true permissions: - contents: write pull-requests: write env: @@ -33,5 +32,3 @@ jobs: - name: Report compute units uses: solana-developers/github-actions/cu-benchmark@e846103a578b3170a9a14824bcdf38d5dcf59ac0 - with: - commit-baseline: ${{ github.event.pull_request.head.repo.full_name == github.repository }} diff --git a/tests/integration-tests/cu_baseline.md b/tests/integration-tests/cu_baseline.md deleted file mode 100644 index 85174e7..0000000 --- a/tests/integration-tests/cu_baseline.md +++ /dev/null @@ -1,20 +0,0 @@ -# Compute Unit Report - -| Instruction | Samples | Min CUs | Max CUs | Avg CUs | Est Cost (Low) [SOL] | Est Cost (Med) [SOL] | Est Cost (High) [SOL] | -|------------------------------|---------|---------|---------|---------|----------------------|----------------------|-----------------------| -| cancel_subscription | 22 | 1720 | 2030 | 1919 | 0.000005000 | 0.000005076 | 0.000005959 | -| close_subscription_authority | 10 | 1803 | 1833 | 1806 | 0.000005000 | 0.000005072 | 0.000005903 | -| create_fixed_delegation | 41 | 3517 | 17050 | 5009 | 0.000005001 | 0.000005200 | 0.000007504 | -| create_plan | 97 | 3436 | 13949 | 4939 | 0.000005001 | 0.000005197 | 0.000007469 | -| create_recurring_delegation | 30 | 3550 | 8055 | 4606 | 0.000005001 | 0.000005184 | 0.000007303 | -| delete_plan | 9 | 359 | 359 | 359 | 0.000005000 | 0.000005014 | 0.000005179 | -| init_subscription_authority | 174 | 4772 | 22468 | 9687 | 0.000005002 | 0.000005387 | 0.000009843 | -| resume_subscription | 3 | 1723 | 1723 | 1723 | 0.000005000 | 0.000005068 | 0.000005861 | -| revoke_delegation | 19 | 255 | 519 | 353 | 0.000005000 | 0.000005014 | 0.000005176 | -| subscribe | 32 | 6485 | 18485 | 7898 | 0.000005002 | 0.000005315 | 0.000008949 | -| transfer_fixed | 9 | 5476 | 14479 | 8531 | 0.000005002 | 0.000005341 | 0.000009265 | -| transfer_recurring | 20 | 5591 | 14591 | 7592 | 0.000005002 | 0.000005303 | 0.000008796 | -| transfer_subscription | 10 | 5799 | 8841 | 6743 | 0.000005002 | 0.000005269 | 0.000008371 | -| update_plan | 22 | 424 | 503 | 477 | 0.000005000 | 0.000005019 | 0.000005238 | - -*Generated: 2026-06-08* diff --git a/tests/integration-tests/src/utils/cu_tracker.rs b/tests/integration-tests/src/utils/cu_tracker.rs index 38e2ccb..5764141 100644 --- a/tests/integration-tests/src/utils/cu_tracker.rs +++ b/tests/integration-tests/src/utils/cu_tracker.rs @@ -85,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]")] @@ -135,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(); From c739dca322667b93c3bca3919a39a7b79c2274ad Mon Sep 17 00:00:00 2001 From: Jo D Date: Mon, 8 Jun 2026 16:42:45 -0400 Subject: [PATCH 8/8] ci: drop unused CU prettier-ignore (report-only, no committed baseline) --- .prettierignore | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.prettierignore b/.prettierignore index 0118f1e..91eefa0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,10 +4,6 @@ idl/ # Generated files clients/typescript/src/generated/ -# CU benchmark (generated) -**/cu_baseline.md -**/cu_report.md - # Build outputs dist/ build/