Skip to main content

solana_core/
sample_performance_service.rs

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