main.rs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. #![allow(clippy::arithmetic_side_effects)]
  2. use {
  3. agave_banking_stage_ingress_types::BankingPacketBatch,
  4. assert_matches::assert_matches,
  5. clap::{crate_description, crate_name, Arg, ArgEnum, Command},
  6. crossbeam_channel::{unbounded, Receiver},
  7. log::*,
  8. rand::{thread_rng, Rng},
  9. rayon::prelude::*,
  10. solana_compute_budget_interface::ComputeBudgetInstruction,
  11. solana_core::{
  12. banking_stage::{
  13. transaction_scheduler::scheduler_controller::SchedulerConfig,
  14. update_bank_forks_and_poh_recorder_for_new_tpu_bank, BankingStage,
  15. },
  16. banking_trace::{BankingTracer, Channels, BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT},
  17. validator::{BlockProductionMethod, SchedulerPacing, TransactionStructure},
  18. },
  19. solana_hash::Hash,
  20. solana_keypair::Keypair,
  21. solana_ledger::{
  22. blockstore::Blockstore,
  23. genesis_utils::{create_genesis_config, GenesisConfigInfo},
  24. get_tmp_ledger_path_auto_delete,
  25. leader_schedule_cache::LeaderScheduleCache,
  26. },
  27. solana_measure::measure::Measure,
  28. solana_message::Message,
  29. solana_perf::packet::{to_packet_batches, PacketBatch},
  30. solana_poh::poh_recorder::{create_test_recorder, PohRecorder, WorkingBankEntry},
  31. solana_pubkey::{self as pubkey, Pubkey},
  32. solana_runtime::{
  33. bank::Bank, bank_forks::BankForks, prioritization_fee_cache::PrioritizationFeeCache,
  34. },
  35. solana_signature::Signature,
  36. solana_signer::Signer,
  37. solana_system_interface::instruction as system_instruction,
  38. solana_system_transaction as system_transaction,
  39. solana_time_utils::timestamp,
  40. solana_transaction::Transaction,
  41. std::{
  42. num::NonZeroUsize,
  43. sync::{atomic::Ordering, Arc, RwLock},
  44. thread::sleep,
  45. time::{Duration, Instant},
  46. },
  47. };
  48. // transfer transaction cost = 1 * SIGNATURE_COST +
  49. // 2 * WRITE_LOCK_UNITS +
  50. // 1 * system_program
  51. // = 1470 CU
  52. const TRANSFER_TRANSACTION_COST: u32 = 1470;
  53. fn check_txs(
  54. receiver: &Arc<Receiver<WorkingBankEntry>>,
  55. ref_tx_count: usize,
  56. poh_recorder: &Arc<RwLock<PohRecorder>>,
  57. ) -> bool {
  58. let mut total = 0;
  59. let now = Instant::now();
  60. let mut no_bank = false;
  61. loop {
  62. if let Ok((_bank, (entry, _tick_height))) = receiver.recv_timeout(Duration::from_millis(10))
  63. {
  64. total += entry.transactions.len();
  65. }
  66. if total >= ref_tx_count {
  67. break;
  68. }
  69. if now.elapsed().as_secs() > 60 {
  70. break;
  71. }
  72. if poh_recorder.read().unwrap().bank().is_none() {
  73. no_bank = true;
  74. break;
  75. }
  76. }
  77. if !no_bank {
  78. assert!(total >= ref_tx_count);
  79. }
  80. no_bank
  81. }
  82. #[derive(ArgEnum, Clone, Copy, PartialEq, Eq)]
  83. enum WriteLockContention {
  84. /// No transactions lock the same accounts.
  85. None,
  86. /// Transactions don't lock the same account, unless they belong to the same batch.
  87. SameBatchOnly,
  88. /// All transactions write lock the same account.
  89. Full,
  90. }
  91. impl WriteLockContention {
  92. fn possible_values<'a>() -> impl Iterator<Item = clap::PossibleValue<'a>> {
  93. Self::value_variants()
  94. .iter()
  95. .filter_map(|v| v.to_possible_value())
  96. }
  97. }
  98. impl std::str::FromStr for WriteLockContention {
  99. type Err = String;
  100. fn from_str(input: &str) -> Result<Self, String> {
  101. ArgEnum::from_str(input, false)
  102. }
  103. }
  104. fn make_accounts_txs(
  105. total_num_transactions: usize,
  106. packets_per_batch: usize,
  107. hash: Hash,
  108. contention: WriteLockContention,
  109. simulate_mint: bool,
  110. mint_txs_percentage: usize,
  111. ) -> Vec<Transaction> {
  112. let to_pubkey = pubkey::new_rand();
  113. let chunk_pubkeys: Vec<pubkey::Pubkey> = (0..total_num_transactions / packets_per_batch)
  114. .map(|_| pubkey::new_rand())
  115. .collect();
  116. let payer_key = Keypair::new();
  117. (0..total_num_transactions)
  118. .into_par_iter()
  119. .map(|i| {
  120. let is_simulated_mint = is_simulated_mint_transaction(
  121. simulate_mint,
  122. i,
  123. packets_per_batch,
  124. mint_txs_percentage,
  125. );
  126. // simulated mint transactions have higher compute-unit-price
  127. let compute_unit_price = if is_simulated_mint { 5 } else { 1 };
  128. let mut new = make_transfer_transaction_with_compute_unit_price(
  129. &payer_key,
  130. &to_pubkey,
  131. 1,
  132. hash,
  133. compute_unit_price,
  134. );
  135. let sig: [u8; 64] = std::array::from_fn(|_| thread_rng().gen::<u8>());
  136. new.message.account_keys[0] = pubkey::new_rand();
  137. new.message.account_keys[1] = match contention {
  138. WriteLockContention::None => pubkey::new_rand(),
  139. WriteLockContention::SameBatchOnly => {
  140. // simulated mint transactions have conflict accounts
  141. if is_simulated_mint {
  142. chunk_pubkeys[i / packets_per_batch]
  143. } else {
  144. pubkey::new_rand()
  145. }
  146. }
  147. WriteLockContention::Full => to_pubkey,
  148. };
  149. new.signatures = vec![Signature::from(sig)];
  150. new
  151. })
  152. .collect()
  153. }
  154. // In simulating mint, `mint_txs_percentage` transactions in a batch are mint transaction
  155. // (eg., have conflicting account and higher priority) and remaining percentage regular
  156. // transactions (eg., non-conflict and low priority)
  157. fn is_simulated_mint_transaction(
  158. simulate_mint: bool,
  159. index: usize,
  160. packets_per_batch: usize,
  161. mint_txs_percentage: usize,
  162. ) -> bool {
  163. simulate_mint && (index % packets_per_batch <= packets_per_batch * mint_txs_percentage / 100)
  164. }
  165. fn make_transfer_transaction_with_compute_unit_price(
  166. from_keypair: &Keypair,
  167. to: &Pubkey,
  168. lamports: u64,
  169. recent_blockhash: Hash,
  170. compute_unit_price: u64,
  171. ) -> Transaction {
  172. let from_pubkey = from_keypair.pubkey();
  173. let instructions = vec![
  174. system_instruction::transfer(&from_pubkey, to, lamports),
  175. ComputeBudgetInstruction::set_compute_unit_price(compute_unit_price),
  176. ComputeBudgetInstruction::set_compute_unit_limit(TRANSFER_TRANSACTION_COST),
  177. ];
  178. let message = Message::new(&instructions, Some(&from_pubkey));
  179. Transaction::new(&[from_keypair], message, recent_blockhash)
  180. }
  181. struct PacketsPerIteration {
  182. packet_batches: Vec<PacketBatch>,
  183. transactions: Vec<Transaction>,
  184. packets_per_batch: usize,
  185. }
  186. impl PacketsPerIteration {
  187. fn new(
  188. packets_per_batch: usize,
  189. batches_per_iteration: usize,
  190. genesis_hash: Hash,
  191. write_lock_contention: WriteLockContention,
  192. simulate_mint: bool,
  193. mint_txs_percentage: usize,
  194. ) -> Self {
  195. let total_num_transactions = packets_per_batch * batches_per_iteration;
  196. let transactions = make_accounts_txs(
  197. total_num_transactions,
  198. packets_per_batch,
  199. genesis_hash,
  200. write_lock_contention,
  201. simulate_mint,
  202. mint_txs_percentage,
  203. );
  204. let packet_batches: Vec<PacketBatch> = to_packet_batches(&transactions, packets_per_batch);
  205. assert_eq!(packet_batches.len(), batches_per_iteration);
  206. Self {
  207. packet_batches,
  208. transactions,
  209. packets_per_batch,
  210. }
  211. }
  212. fn refresh_blockhash(&mut self, new_blockhash: Hash) {
  213. for tx in self.transactions.iter_mut() {
  214. tx.message.recent_blockhash = new_blockhash;
  215. let sig: [u8; 64] = std::array::from_fn(|_| thread_rng().gen::<u8>());
  216. tx.signatures[0] = Signature::from(sig);
  217. }
  218. self.packet_batches = to_packet_batches(&self.transactions, self.packets_per_batch);
  219. }
  220. }
  221. #[allow(clippy::cognitive_complexity)]
  222. fn main() {
  223. agave_logger::setup();
  224. let matches = Command::new(crate_name!())
  225. .about(crate_description!())
  226. .version(solana_version::version!())
  227. .arg(
  228. Arg::new("iterations")
  229. .long("iterations")
  230. .takes_value(true)
  231. .help("Number of test iterations"),
  232. )
  233. .arg(
  234. Arg::new("num_chunks")
  235. .long("num-chunks")
  236. .takes_value(true)
  237. .value_name("SIZE")
  238. .help("Number of transaction chunks."),
  239. )
  240. .arg(
  241. Arg::new("packets_per_batch")
  242. .long("packets-per-batch")
  243. .takes_value(true)
  244. .value_name("SIZE")
  245. .help("Packets per batch"),
  246. )
  247. .arg(
  248. Arg::new("skip_sanity")
  249. .long("skip-sanity")
  250. .takes_value(false)
  251. .help("Skip transaction sanity execution"),
  252. )
  253. .arg(
  254. Arg::new("trace_banking")
  255. .long("trace-banking")
  256. .takes_value(false)
  257. .help("Enable banking tracing"),
  258. )
  259. .arg(
  260. Arg::new("write_lock_contention")
  261. .long("write-lock-contention")
  262. .takes_value(true)
  263. .possible_values(WriteLockContention::possible_values())
  264. .help("Accounts that test transactions write lock"),
  265. )
  266. .arg(
  267. Arg::new("batches_per_iteration")
  268. .long("batches-per-iteration")
  269. .takes_value(true)
  270. .help("Number of batches to send in each iteration"),
  271. )
  272. .arg(
  273. Arg::with_name("block_production_method")
  274. .long("block-production-method")
  275. .value_name("METHOD")
  276. .takes_value(true)
  277. .possible_values(BlockProductionMethod::cli_names())
  278. .help(BlockProductionMethod::cli_message()),
  279. )
  280. .arg(
  281. Arg::with_name("block_production_num_workers")
  282. .long("block-production-num-workers")
  283. .takes_value(true)
  284. .value_name("NUMBER")
  285. .help("Number of worker threads to use for block production"),
  286. )
  287. .arg(
  288. Arg::with_name("transaction_struct")
  289. .long("transaction-structure")
  290. .value_name("STRUCT")
  291. .takes_value(true)
  292. .possible_values(TransactionStructure::cli_names())
  293. .help(TransactionStructure::cli_message()),
  294. )
  295. .arg(
  296. Arg::new("simulate_mint")
  297. .long("simulate-mint")
  298. .takes_value(false)
  299. .help("Simulate mint transactions to have higher priority"),
  300. )
  301. .arg(
  302. Arg::new("mint_txs_percentage")
  303. .long("mint-txs-percentage")
  304. .takes_value(true)
  305. .requires("simulate_mint")
  306. .help("In simulating mint, number of mint transactions out of 100."),
  307. )
  308. .get_matches();
  309. let block_production_method = matches
  310. .value_of_t::<BlockProductionMethod>("block_production_method")
  311. .unwrap_or_default();
  312. let block_production_num_workers = matches
  313. .value_of_t::<NonZeroUsize>("block_production_num_workers")
  314. .unwrap_or_else(|_| BankingStage::default_num_workers());
  315. // a multiple of packet chunk duplicates to avoid races
  316. let num_chunks = matches.value_of_t::<usize>("num_chunks").unwrap_or(16);
  317. let packets_per_batch = matches
  318. .value_of_t::<usize>("packets_per_batch")
  319. .unwrap_or(192);
  320. let iterations = matches.value_of_t::<usize>("iterations").unwrap_or(1000);
  321. let batches_per_iteration = matches
  322. .value_of_t::<usize>("batches_per_iteration")
  323. .unwrap_or(BankingStage::default_num_workers().get());
  324. let write_lock_contention = matches
  325. .value_of_t::<WriteLockContention>("write_lock_contention")
  326. .unwrap_or(WriteLockContention::None);
  327. let mint_txs_percentage = matches
  328. .value_of_t::<usize>("mint_txs_percentage")
  329. .unwrap_or(99);
  330. let mint_total = 1_000_000_000_000;
  331. let GenesisConfigInfo {
  332. genesis_config,
  333. mint_keypair,
  334. ..
  335. } = create_genesis_config(mint_total);
  336. let (replay_vote_sender, _replay_vote_receiver) = unbounded();
  337. let bank0 = Bank::new_for_benches(&genesis_config);
  338. let bank_forks = BankForks::new_rw_arc(bank0);
  339. let mut bank = bank_forks.read().unwrap().working_bank_with_scheduler();
  340. // set cost tracker limits to MAX so it will not filter out TXs
  341. bank.write_cost_tracker()
  342. .unwrap()
  343. .set_limits(u64::MAX, u64::MAX, u64::MAX);
  344. let mut all_packets: Vec<PacketsPerIteration> = std::iter::from_fn(|| {
  345. Some(PacketsPerIteration::new(
  346. packets_per_batch,
  347. batches_per_iteration,
  348. genesis_config.hash(),
  349. write_lock_contention,
  350. matches.is_present("simulate_mint"),
  351. mint_txs_percentage,
  352. ))
  353. })
  354. .take(num_chunks)
  355. .collect();
  356. let total_num_transactions: u64 = all_packets
  357. .iter()
  358. .map(|packets_for_single_iteration| packets_for_single_iteration.transactions.len() as u64)
  359. .sum();
  360. info!("worker threads: {block_production_num_workers} txs: {total_num_transactions}");
  361. // fund all the accounts
  362. all_packets.iter().for_each(|packets_for_single_iteration| {
  363. packets_for_single_iteration
  364. .transactions
  365. .iter()
  366. .for_each(|tx| {
  367. let mut fund = system_transaction::transfer(
  368. &mint_keypair,
  369. &tx.message.account_keys[0],
  370. mint_total / total_num_transactions,
  371. genesis_config.hash(),
  372. );
  373. // Ignore any pesky duplicate signature errors in the case we are using single-payer
  374. let sig: [u8; 64] = std::array::from_fn(|_| thread_rng().gen::<u8>());
  375. fund.signatures = vec![Signature::from(sig)];
  376. bank.process_transaction(&fund).unwrap();
  377. });
  378. });
  379. let skip_sanity = matches.is_present("skip_sanity");
  380. if !skip_sanity {
  381. all_packets.iter().for_each(|packets_for_single_iteration| {
  382. //sanity check, make sure all the transactions can execute sequentially
  383. packets_for_single_iteration
  384. .transactions
  385. .iter()
  386. .for_each(|tx| {
  387. let res = bank.process_transaction(tx);
  388. assert!(res.is_ok(), "sanity test transactions error: {res:?}");
  389. });
  390. });
  391. bank.clear_signatures();
  392. if write_lock_contention == WriteLockContention::None {
  393. all_packets.iter().for_each(|packets_for_single_iteration| {
  394. //sanity check, make sure all the transactions can execute in parallel
  395. let res =
  396. bank.process_transactions(packets_for_single_iteration.transactions.iter());
  397. for r in res {
  398. assert!(r.is_ok(), "sanity parallel execution error: {r:?}");
  399. }
  400. bank.clear_signatures();
  401. });
  402. }
  403. }
  404. let ledger_path = get_tmp_ledger_path_auto_delete!();
  405. let blockstore = Arc::new(
  406. Blockstore::open(ledger_path.path()).expect("Expected to be able to open database ledger"),
  407. );
  408. let leader_schedule_cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank));
  409. let (
  410. exit,
  411. poh_recorder,
  412. mut poh_controller,
  413. transaction_recorder,
  414. poh_service,
  415. signal_receiver,
  416. ) = create_test_recorder(
  417. bank.clone(),
  418. blockstore.clone(),
  419. None,
  420. Some(leader_schedule_cache),
  421. );
  422. let (banking_tracer, tracer_thread) =
  423. BankingTracer::new(matches.is_present("trace_banking").then_some((
  424. &blockstore.banking_trace_path(),
  425. exit.clone(),
  426. BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT,
  427. )))
  428. .unwrap();
  429. let prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
  430. let Channels {
  431. non_vote_sender,
  432. non_vote_receiver,
  433. tpu_vote_sender,
  434. tpu_vote_receiver,
  435. gossip_vote_sender,
  436. gossip_vote_receiver,
  437. } = banking_tracer.create_channels(false);
  438. let banking_stage = BankingStage::new_num_threads(
  439. block_production_method,
  440. poh_recorder.clone(),
  441. transaction_recorder,
  442. non_vote_receiver,
  443. tpu_vote_receiver,
  444. gossip_vote_receiver,
  445. block_production_num_workers,
  446. SchedulerConfig {
  447. scheduler_pacing: SchedulerPacing::Disabled,
  448. },
  449. None,
  450. replay_vote_sender,
  451. None,
  452. bank_forks.clone(),
  453. prioritization_fee_cache,
  454. );
  455. // This is so that the signal_receiver does not go out of scope after the closure.
  456. // If it is dropped before poh_service, then poh_service will error when
  457. // calling send() on the channel.
  458. let signal_receiver = Arc::new(signal_receiver);
  459. let mut total_us = 0;
  460. let mut tx_total_us = 0;
  461. let base_tx_count = bank.transaction_count();
  462. let mut txs_processed = 0;
  463. let collector = solana_pubkey::new_rand();
  464. let mut total_sent = 0;
  465. for current_iteration_index in 0..iterations {
  466. trace!("RUNNING ITERATION {current_iteration_index}");
  467. let now = Instant::now();
  468. let mut sent = 0;
  469. let packets_for_this_iteration = &all_packets[current_iteration_index % num_chunks];
  470. for (packet_batch_index, packet_batch) in
  471. packets_for_this_iteration.packet_batches.iter().enumerate()
  472. {
  473. sent += packet_batch.len();
  474. trace!(
  475. "Sending PacketBatch index {}, {}",
  476. packet_batch_index,
  477. timestamp(),
  478. );
  479. non_vote_sender
  480. .send(BankingPacketBatch::new(vec![packet_batch.clone()]))
  481. .unwrap();
  482. }
  483. for tx in &packets_for_this_iteration.transactions {
  484. loop {
  485. if bank.get_signature_status(&tx.signatures[0]).is_some() {
  486. break;
  487. }
  488. if poh_recorder.read().unwrap().bank().is_none() {
  489. break;
  490. }
  491. sleep(Duration::from_millis(5));
  492. }
  493. }
  494. // check if txs had been processed by bank. Returns when all transactions are
  495. // processed, with `FALSE` indicate there is still bank. or returns TRUE indicate a
  496. // bank has expired before receiving all txs.
  497. if check_txs(
  498. &signal_receiver,
  499. packets_for_this_iteration.transactions.len(),
  500. &poh_recorder,
  501. ) {
  502. eprintln!(
  503. "[iteration {}, tx sent {}, slot {} expired, bank tx count {}]",
  504. current_iteration_index,
  505. sent,
  506. bank.slot(),
  507. bank.transaction_count(),
  508. );
  509. tx_total_us += now.elapsed().as_micros() as u64;
  510. let mut poh_time = Measure::start("poh_time");
  511. poh_controller
  512. .reset_sync(bank.clone(), Some((bank.slot(), bank.slot() + 1)))
  513. .unwrap();
  514. poh_time.stop();
  515. let mut new_bank_time = Measure::start("new_bank");
  516. if let Some((result, _timings)) = bank.wait_for_completed_scheduler() {
  517. assert_matches!(result, Ok(_));
  518. }
  519. let new_slot = bank.slot() + 1;
  520. let new_bank = Bank::new_from_parent(bank.clone(), &collector, new_slot);
  521. new_bank_time.stop();
  522. let mut insert_time = Measure::start("insert_time");
  523. assert_matches!(poh_recorder.read().unwrap().bank(), None);
  524. update_bank_forks_and_poh_recorder_for_new_tpu_bank(
  525. &bank_forks,
  526. &mut poh_controller,
  527. new_bank,
  528. );
  529. bank = bank_forks.read().unwrap().working_bank_with_scheduler();
  530. assert_matches!(poh_recorder.read().unwrap().bank(), Some(_));
  531. insert_time.stop();
  532. debug!(
  533. "new_bank_time: {}us insert_time: {}us poh_time: {}us",
  534. new_bank_time.as_us(),
  535. insert_time.as_us(),
  536. poh_time.as_us(),
  537. );
  538. } else {
  539. eprintln!(
  540. "[iteration {}, tx sent {}, slot {} active, bank tx count {}]",
  541. current_iteration_index,
  542. sent,
  543. bank.slot(),
  544. bank.transaction_count(),
  545. );
  546. tx_total_us += now.elapsed().as_micros() as u64;
  547. }
  548. // This signature clear may not actually clear the signatures
  549. // in this chunk, but since we rotate between CHUNKS then
  550. // we should clear them by the time we come around again to re-use that chunk.
  551. bank.clear_signatures();
  552. total_us += now.elapsed().as_micros() as u64;
  553. total_sent += sent;
  554. if current_iteration_index % num_chunks == 0 {
  555. let last_blockhash = bank.last_blockhash();
  556. for packets_for_single_iteration in all_packets.iter_mut() {
  557. packets_for_single_iteration.refresh_blockhash(last_blockhash);
  558. }
  559. }
  560. }
  561. txs_processed += bank_forks
  562. .read()
  563. .unwrap()
  564. .working_bank()
  565. .transaction_count();
  566. debug!("processed: {txs_processed} base: {base_tx_count}");
  567. eprintln!(
  568. "[total_sent: {}, base_tx_count: {}, txs_processed: {}, txs_landed: {}, total_us: {}, \
  569. tx_total_us: {}]",
  570. total_sent,
  571. base_tx_count,
  572. txs_processed,
  573. (txs_processed - base_tx_count),
  574. total_us,
  575. tx_total_us
  576. );
  577. eprintln!(
  578. "{{'name': 'banking_bench_total', 'median': '{:.2}'}}",
  579. (1000.0 * 1000.0 * total_sent as f64) / (total_us as f64),
  580. );
  581. eprintln!(
  582. "{{'name': 'banking_bench_tx_total', 'median': '{:.2}'}}",
  583. (1000.0 * 1000.0 * total_sent as f64) / (tx_total_us as f64),
  584. );
  585. eprintln!(
  586. "{{'name': 'banking_bench_success_tx_total', 'median': '{:.2}'}}",
  587. (1000.0 * 1000.0 * (txs_processed - base_tx_count) as f64) / (total_us as f64),
  588. );
  589. drop(non_vote_sender);
  590. drop(tpu_vote_sender);
  591. drop(gossip_vote_sender);
  592. exit.store(true, Ordering::Relaxed);
  593. banking_stage.join().unwrap();
  594. debug!("waited for banking_stage");
  595. poh_service.join().unwrap();
  596. sleep(Duration::from_secs(1));
  597. debug!("waited for poh_service");
  598. if let Some(tracer_thread) = tracer_thread {
  599. tracer_thread.join().unwrap().unwrap();
  600. }
  601. }