solana_core/
sample_performance_service.rs

1use {
2    solana_ledger::{blockstore::Blockstore, blockstore_meta::PerfSampleV2},
3    solana_runtime::bank_forks::BankForks,
4    std::{
5        sync::{
6            atomic::{AtomicBool, Ordering},
7            Arc, RwLock,
8        },
9        thread::{self, sleep, Builder, JoinHandle},
10        time::{Duration, Instant},
11    },
12};
13
14const SAMPLE_INTERVAL: Duration = Duration::from_secs(60);
15const SLEEP_INTERVAL: Duration = Duration::from_millis(500);
16
17pub struct SamplePerformanceService {
18    thread_hdl: JoinHandle<()>,
19}
20
21impl SamplePerformanceService {
22    pub fn new(
23        bank_forks: &Arc<RwLock<BankForks>>,
24        blockstore: Arc<Blockstore>,
25        exit: Arc<AtomicBool>,
26    ) -> Self {
27        let bank_forks = bank_forks.clone();
28
29        let thread_hdl = Builder::new()
30            .name("solSamplePerf".to_string())
31            .spawn(move || {
32                info!("SamplePerformanceService has started");
33                Self::run(bank_forks, blockstore, exit);
34                info!("SamplePerformanceService has stopped");
35            })
36            .unwrap();
37
38        Self { thread_hdl }
39    }
40
41    fn run(bank_forks: Arc<RwLock<BankForks>>, blockstore: Arc<Blockstore>, exit: Arc<AtomicBool>) {
42        let mut snapshot = StatsSnapshot::from_forks(&bank_forks);
43        let mut last_sample_time = Instant::now();
44
45        while !exit.load(Ordering::Relaxed) {
46            let elapsed = last_sample_time.elapsed();
47            if elapsed >= SAMPLE_INTERVAL {
48                last_sample_time = Instant::now();
49                let new_snapshot = StatsSnapshot::from_forks(&bank_forks);
50
51                let (num_transactions, num_non_vote_transactions, num_slots) =
52                    new_snapshot.diff_since(&snapshot);
53
54                // Store the new snapshot to compare against in the next iteration of the loop.
55                snapshot = new_snapshot;
56
57                let perf_sample = PerfSampleV2 {
58                    // Note: since num_slots is computed from the highest slot and not the bank
59                    // slot, this value should not be used in conjunction with num_transactions or
60                    // num_non_vote_transactions to draw any conclusions about number of
61                    // transactions per slot.
62                    num_slots,
63                    num_transactions,
64                    num_non_vote_transactions,
65                    sample_period_secs: elapsed.as_secs() as u16,
66                };
67
68                let highest_slot = snapshot.highest_slot;
69                if let Err(e) = blockstore.write_perf_sample(highest_slot, &perf_sample) {
70                    error!("write_perf_sample failed: slot {highest_slot:?} {e:?}");
71                }
72            }
73            sleep(SLEEP_INTERVAL);
74        }
75    }
76
77    pub fn join(self) -> thread::Result<()> {
78        self.thread_hdl.join()
79    }
80}
81
82struct StatsSnapshot {
83    pub num_transactions: u64,
84    pub num_non_vote_transactions: u64,
85    pub highest_slot: u64,
86}
87
88impl StatsSnapshot {
89    fn from_forks(forks: &RwLock<BankForks>) -> Self {
90        let forks = forks.read().unwrap();
91        let bank = forks.root_bank();
92        Self {
93            num_transactions: bank.transaction_count(),
94            num_non_vote_transactions: bank.non_vote_transaction_count_since_restart(),
95            highest_slot: forks.highest_slot(),
96        }
97    }
98
99    fn diff_since(&self, predecessor: &Self) -> (u64, u64, u64) {
100        (
101            self.num_transactions
102                .saturating_sub(predecessor.num_transactions),
103            self.num_non_vote_transactions
104                .saturating_sub(predecessor.num_non_vote_transactions),
105            self.highest_slot.saturating_sub(predecessor.highest_slot),
106        )
107    }
108}