1use std::{
6 cmp::{max, min},
7 hash::Hasher,
8 sync::{
9 Weak,
10 atomic::{AtomicBool, AtomicU32, Ordering},
11 },
12 time::{Duration, SystemTime, UNIX_EPOCH},
13};
14
15use iota_config::node::AuthorityOverloadConfig;
16use iota_metrics::monitored_scope;
17use iota_sdk_types::TransactionDigest;
18use iota_types::{
19 error::{IotaError, IotaResult},
20 fp_bail,
21};
22use tokio::time::sleep;
23use tracing::{debug, info};
24use twox_hash::XxHash64;
25
26use crate::{
27 authority::AuthorityState, consensus_adapter::ConsensusAdapter,
28 execution_scheduler::ExecutionSchedulerAPI,
29};
30
31#[derive(Default)]
32pub struct AuthorityOverloadInfo {
33 pub is_overload: AtomicBool,
35
36 pub local_load_shedding_percentage: AtomicU32,
42
43 pub latency_load_shedding_percentage: AtomicU32,
46}
47
48impl AuthorityOverloadInfo {
49 pub fn set_overload(&self, local_load_shedding_percentage: u32) {
50 self.is_overload.store(true, Ordering::Relaxed);
51 self.local_load_shedding_percentage
52 .store(min(local_load_shedding_percentage, 100), Ordering::Relaxed);
53 }
54
55 pub fn clear_overload(&self) {
56 self.is_overload.store(false, Ordering::Relaxed);
57 self.local_load_shedding_percentage
58 .store(0, Ordering::Relaxed);
59 self.latency_load_shedding_percentage
60 .store(0, Ordering::Relaxed);
61 }
62}
63
64const STEADY_OVERLOAD_REDUCTION_PERCENTAGE: u32 = 10;
65const EXECUTION_RATE_RATIO_FOR_COMPARISON: f64 = 0.95;
66const ADDITIONAL_LOAD_SHEDDING: f64 = 0.02;
67
68const SEED_UPDATE_DURATION_SECS: u64 = 30;
71
72pub async fn overload_monitor(
75 authority_state: Weak<AuthorityState>,
76 config: AuthorityOverloadConfig,
77) {
78 info!("Starting system overload monitor.");
79
80 loop {
81 let authority_exist = check_execution_overload(&authority_state, &config);
82 if !authority_exist {
83 break;
85 }
86 sleep(config.overload_monitor_interval).await;
87 }
88
89 info!("Shut down system overload monitor.");
90}
91
92pub async fn consensus_queue_overload_monitor(
99 authority_state: Weak<AuthorityState>,
100 consensus_adapter: Weak<ConsensusAdapter>,
101 interval: Duration,
102) {
103 info!("Starting consensus queue overload monitor.");
104
105 loop {
106 let (Some(state), Some(adapter)) = (authority_state.upgrade(), consensus_adapter.upgrade())
107 else {
108 break;
111 };
112
113 let num_inflight_txs = adapter.num_inflight_transactions() as usize;
114 let shedding_pct = compute_graduated_load_shedding_percentage(
115 num_inflight_txs,
116 adapter.max_pending_transactions(),
117 adapter.graduated_load_shedding_soft_limit_pct(),
118 );
119 state
120 .metrics
121 .consensus_queue_load_shedding_percentage
122 .set(shedding_pct as i64);
123
124 sleep(interval).await;
125 }
126
127 info!("Shut down consensus queue overload monitor.");
128}
129
130fn check_execution_overload(
133 authority_state: &Weak<AuthorityState>,
134 config: &AuthorityOverloadConfig,
135) -> bool {
136 let _scope = monitored_scope("OverloadMonitor::check_authority_overload");
137 let authority_arc = authority_state.upgrade();
138 if authority_arc.is_none() {
139 return false;
141 }
142
143 let authority = authority_arc.unwrap();
144 let queueing_latency = authority
145 .metrics
146 .execution_queueing_latency
147 .latency()
148 .unwrap_or_default();
149 let txn_ready_rate = authority.metrics.txn_ready_rate_tracker.lock().rate();
150 let execution_rate = authority.metrics.execution_rate_tracker.lock().rate();
151 let inflight_queue_len = authority.execution_scheduler().num_pending_transactions();
152 let cache_pending_count = authority
153 .get_cache_commit()
154 .approximate_pending_transaction_count() as usize;
155
156 debug!(
157 "Check authority overload signal, queueing latency {:?}, ready rate {:?}, execution rate {:?}, inflight queue len {:?}, cache pending count {:?}.",
158 queueing_latency, txn_ready_rate, execution_rate, inflight_queue_len, cache_pending_count
159 );
160
161 let previous_latency_based_percentage = authority
163 .overload_info
164 .latency_load_shedding_percentage
165 .load(Ordering::Relaxed);
166
167 let (_, latency_based_percentage) = compute_latency_load_shedding_percentage(
168 config,
169 previous_latency_based_percentage,
170 queueing_latency,
171 txn_ready_rate,
172 execution_rate,
173 );
174
175 authority
178 .overload_info
179 .latency_load_shedding_percentage
180 .store(latency_based_percentage, Ordering::Relaxed);
181
182 let queue_based_percentage = compute_graduated_load_shedding_percentage(
183 inflight_queue_len,
184 config.max_transaction_manager_queue_length,
185 config.max_transaction_manager_queue_length_soft_limit_pct(),
186 );
187
188 let cache_config = &authority.config.execution_cache_config.writeback_cache;
189 let cache_based_percentage = compute_graduated_load_shedding_percentage(
190 cache_pending_count,
191 cache_config.backpressure_threshold() as usize,
192 cache_config.backpressure_soft_limit_pct(),
193 );
194
195 let load_shedding_percentage = max(
210 max(latency_based_percentage, queue_based_percentage),
211 cache_based_percentage,
212 );
213 let is_overload = load_shedding_percentage > 0;
214
215 if is_overload {
216 authority
217 .overload_info
218 .set_overload(load_shedding_percentage);
219 } else {
220 authority.overload_info.clear_overload();
221 }
222
223 authority
224 .metrics
225 .authority_overload_status
226 .set(is_overload as i64);
227 authority
228 .metrics
229 .local_post_consensus_load_shedding_percentage
230 .set(load_shedding_percentage as i64);
231 true
232}
233
234fn calculate_load_shedding_percentage(txn_ready_rate: f64, execution_rate: f64) -> u32 {
237 if txn_ready_rate < 1e-10 {
241 return 0;
242 }
243
244 if execution_rate * EXECUTION_RATE_RATIO_FOR_COMPARISON > txn_ready_rate {
247 return 0;
248 }
249
250 (((1.0 - execution_rate * EXECUTION_RATE_RATIO_FOR_COMPARISON / txn_ready_rate)
254 + ADDITIONAL_LOAD_SHEDDING)
255 .min(1.0)
256 * 100.0)
257 .round() as u32
258}
259
260fn compute_latency_load_shedding_percentage(
270 config: &AuthorityOverloadConfig,
271 current_load_shedding_percentage: u32,
272 queueing_latency: Duration,
273 txn_ready_rate: f64,
274 execution_rate: f64,
275) -> (bool, u32) {
276 let additional_load_shedding_percentage =
281 if queueing_latency > config.execution_queue_latency_hard_limit {
282 let calculated_load_shedding_percentage =
283 calculate_load_shedding_percentage(txn_ready_rate, execution_rate);
284
285 if calculated_load_shedding_percentage > 0
286 || txn_ready_rate >= config.safe_transaction_ready_rate as f64
287 {
288 max(
289 calculated_load_shedding_percentage,
290 config.min_load_shedding_percentage_above_hard_limit,
291 )
292 } else {
293 0
294 }
295 } else if queueing_latency > config.execution_queue_latency_soft_limit {
296 calculate_load_shedding_percentage(txn_ready_rate, execution_rate)
297 } else {
298 0
299 };
300
301 let load_shedding_percentage = if additional_load_shedding_percentage > 0 {
303 current_load_shedding_percentage
309 + (100 - current_load_shedding_percentage) * additional_load_shedding_percentage / 100
310 } else if txn_ready_rate > config.safe_transaction_ready_rate as f64
311 && current_load_shedding_percentage > 10
312 {
313 current_load_shedding_percentage - STEADY_OVERLOAD_REDUCTION_PERCENTAGE
317 } else {
318 0
321 };
322
323 let load_shedding_percentage = min(
324 load_shedding_percentage,
325 config.max_load_shedding_percentage,
326 );
327 let overload_status = load_shedding_percentage > 0;
328 (overload_status, load_shedding_percentage)
329}
330
331pub(crate) fn should_reject_tx(
333 load_shedding_percentage: u32,
334 tx_digest: TransactionDigest,
335 temporal_seed: u64,
336) -> bool {
337 let mut hasher = XxHash64::with_seed(temporal_seed);
340 hasher.write(tx_digest.bytes());
341 let value = hasher.finish();
342 value % 100 < load_shedding_percentage as u64
343}
344
345pub fn overload_monitor_accept_tx(
347 load_shedding_percentage: u32,
348 tx_digest: TransactionDigest,
349) -> IotaResult {
350 let temporal_seed = SystemTime::now()
356 .duration_since(UNIX_EPOCH)
357 .expect("IOTA did not exist prior to 1970")
358 .as_secs()
359 / SEED_UPDATE_DURATION_SECS;
360
361 if should_reject_tx(load_shedding_percentage, tx_digest, temporal_seed) {
362 fp_bail!(IotaError::ValidatorOverloadedRetryAfter {
366 retry_after_secs: SEED_UPDATE_DURATION_SECS
367 });
368 }
369 Ok(())
370}
371
372pub(crate) fn compute_graduated_load_shedding_percentage(
389 current: usize,
390 hard_limit: usize,
391 soft_limit_pct: u32,
392) -> u32 {
393 debug_assert!(
394 soft_limit_pct <= 100,
395 "soft_limit_pct must be <= 100, got {soft_limit_pct}"
396 );
397 let soft_limit_pct = soft_limit_pct.min(100);
399 let soft_limit = hard_limit * soft_limit_pct as usize / 100;
401
402 if current >= hard_limit {
408 return 100;
409 }
410
411 if current <= soft_limit {
413 return 0;
414 }
415
416 let range = hard_limit - soft_limit;
420 let excess = current - soft_limit;
421
422 (excess * 100 / range) as u32
424}
425
426#[cfg(test)]
427#[expect(clippy::disallowed_methods)] mod tests {
430 use std::sync::Arc;
431
432 use iota_macros::sim_test;
433 use rand::{
434 Rng, SeedableRng,
435 rngs::{OsRng, StdRng},
436 };
437 use tokio::{
438 sync::{
439 mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel},
440 oneshot,
441 },
442 task::JoinHandle,
443 time::{Instant, MissedTickBehavior, interval},
444 };
445
446 use super::*;
447 use crate::authority::test_authority_builder::TestAuthorityBuilder;
448
449 #[test]
450 fn test_authority_overload_info() {
451 let overload_info = AuthorityOverloadInfo::default();
452 assert!(!overload_info.is_overload.load(Ordering::Relaxed));
453 assert_eq!(
454 overload_info
455 .local_load_shedding_percentage
456 .load(Ordering::Relaxed),
457 0
458 );
459
460 {
461 overload_info.set_overload(20);
462 assert!(overload_info.is_overload.load(Ordering::Relaxed));
463 assert_eq!(
464 overload_info
465 .local_load_shedding_percentage
466 .load(Ordering::Relaxed),
467 20
468 );
469 }
470
471 {
473 overload_info.set_overload(110);
474 assert!(overload_info.is_overload.load(Ordering::Relaxed));
475 assert_eq!(
476 overload_info
477 .local_load_shedding_percentage
478 .load(Ordering::Relaxed),
479 100
480 );
481 }
482
483 {
486 overload_info
487 .latency_load_shedding_percentage
488 .store(30, Ordering::Relaxed);
489 overload_info.clear_overload();
490 assert!(!overload_info.is_overload.load(Ordering::Relaxed));
491 assert_eq!(
492 overload_info
493 .local_load_shedding_percentage
494 .load(Ordering::Relaxed),
495 0
496 );
497 assert_eq!(
498 overload_info
499 .latency_load_shedding_percentage
500 .load(Ordering::Relaxed),
501 0
502 );
503 }
504 }
505
506 #[test]
507 fn test_calculate_load_shedding_ratio() {
508 assert_eq!(calculate_load_shedding_percentage(95.0, 100.1), 0);
509 assert_eq!(calculate_load_shedding_percentage(95.0, 100.0), 2);
510 assert_eq!(calculate_load_shedding_percentage(100.0, 100.0), 7);
511 assert_eq!(calculate_load_shedding_percentage(110.0, 100.0), 16);
512 assert_eq!(calculate_load_shedding_percentage(180.0, 100.0), 49);
513 assert_eq!(calculate_load_shedding_percentage(100.0, 0.0), 100);
514 assert_eq!(calculate_load_shedding_percentage(0.0, 1.0), 0);
515 }
516
517 #[test]
518 fn test_check_overload_signals() {
519 let config = AuthorityOverloadConfig {
520 execution_queue_latency_hard_limit: Duration::from_secs(10),
521 execution_queue_latency_soft_limit: Duration::from_secs(1),
522 max_load_shedding_percentage: 90,
523 ..Default::default()
524 };
525
526 assert_eq!(
529 compute_latency_load_shedding_percentage(
530 &config,
531 0,
532 Duration::from_millis(500),
533 1000.0,
534 10.0
535 ),
536 (false, 0)
537 );
538
539 assert_eq!(
542 compute_latency_load_shedding_percentage(
543 &config,
544 0,
545 Duration::from_secs(2),
546 100.0,
547 120.0
548 ),
549 (false, 0)
550 );
551
552 assert_eq!(
555 compute_latency_load_shedding_percentage(
556 &config,
557 0,
558 Duration::from_secs(2),
559 100.0,
560 100.0
561 ),
562 (true, 7)
563 );
564
565 assert_eq!(
568 compute_latency_load_shedding_percentage(
569 &config,
570 0,
571 Duration::from_secs(11),
572 100.0,
573 100.0
574 ),
575 (true, 50)
576 );
577
578 assert_eq!(
582 compute_latency_load_shedding_percentage(
583 &config,
584 0,
585 Duration::from_secs(11),
586 240.0,
587 100.0
588 ),
589 (true, 62)
590 );
591
592 assert_eq!(
595 compute_latency_load_shedding_percentage(
596 &config,
597 0,
598 Duration::from_secs(11),
599 20.0,
600 100.0
601 ),
602 (false, 0)
603 );
604
605 assert_eq!(
607 compute_latency_load_shedding_percentage(
608 &config,
609 0,
610 Duration::from_secs(11),
611 100.0,
612 0.0
613 ),
614 (true, 90)
615 );
616
617 assert_eq!(
621 compute_latency_load_shedding_percentage(
622 &config,
623 50,
624 Duration::from_secs(2),
625 116.0,
626 100.0
627 ),
628 (true, 60)
629 );
630
631 assert_eq!(
634 compute_latency_load_shedding_percentage(
635 &config,
636 90,
637 Duration::from_secs(2),
638 200.0,
639 300.0
640 ),
641 (true, 80)
642 );
643
644 assert_eq!(
646 compute_latency_load_shedding_percentage(
647 &config,
648 50,
649 Duration::from_secs(11),
650 100.0,
651 100.0
652 ),
653 (true, 75)
654 );
655 }
656
657 #[test]
663 fn test_compute_graduated_load_shedding_percentage() {
664 let hard_limit = 20_000;
665 let soft_limit_pct = 50;
666 let soft_limit = hard_limit * soft_limit_pct as usize / 100; for current in [0, soft_limit - 1, soft_limit] {
670 assert_eq!(
671 compute_graduated_load_shedding_percentage(current, hard_limit, soft_limit_pct),
672 0,
673 "no shedding expected at or below soft limit ({current} <= {soft_limit})",
674 );
675 }
676
677 for (current, expected_pct) in [
683 (12_500, 25),
684 (15_000, 50),
685 (17_500, 75),
686 (hard_limit - 1, 99),
687 ] {
688 assert_eq!(
689 compute_graduated_load_shedding_percentage(current, hard_limit, soft_limit_pct),
690 expected_pct,
691 "expected shedding percentage to be {expected_pct}% at current={current}",
692 );
693 }
694
695 for current in [hard_limit, hard_limit + 1, 30_000] {
697 assert_eq!(
698 compute_graduated_load_shedding_percentage(current, hard_limit, soft_limit_pct),
699 100,
700 "expected 100% shedding at/above hard limit ({current} >= {hard_limit})",
701 );
702 }
703
704 for current in [0, hard_limit - 1] {
707 assert_eq!(
708 compute_graduated_load_shedding_percentage(current, hard_limit, 100),
709 0,
710 "soft_limit_pct=100: no shedding expected below hard limit ({current} < {hard_limit})",
711 );
712 }
713 for current in [hard_limit, hard_limit + 1] {
714 assert_eq!(
715 compute_graduated_load_shedding_percentage(current, hard_limit, 100),
716 100,
717 "soft_limit_pct=100: full shedding expected at/above hard limit ({current} >= \
718 {hard_limit})",
719 );
720 }
721
722 assert_eq!(
724 compute_graduated_load_shedding_percentage(0, hard_limit, 0),
725 0,
726 "soft_limit_pct=0: at current=0, no shedding expected (current <= soft_limit=0)",
727 );
728 assert_eq!(
729 compute_graduated_load_shedding_percentage(hard_limit / 2, hard_limit, 0),
730 50,
731 "soft_limit_pct=0: at midpoint of hard_limit, 50% shedding expected",
732 );
733 assert_eq!(
734 compute_graduated_load_shedding_percentage(hard_limit, hard_limit, 0),
735 100,
736 "soft_limit_pct=0: at hard_limit, 100% shedding expected",
737 );
738 }
739
740 #[tokio::test(flavor = "current_thread")]
741 async fn test_check_authority_overload() {
742 telemetry_subscribers::init_for_testing();
743
744 let config = AuthorityOverloadConfig {
745 safe_transaction_ready_rate: 0,
746 ..Default::default()
747 };
748 let state = TestAuthorityBuilder::new()
749 .with_authority_overload_config(config.clone())
750 .build()
751 .await;
752
753 for _ in 0..1000 {
755 state
756 .metrics
757 .execution_queueing_latency
758 .report(Duration::from_secs(20));
759 }
760
761 let authority = Arc::downgrade(&state);
764 assert!(check_execution_overload(&authority, &config));
765 assert!(state.overload_info.is_overload.load(Ordering::Relaxed));
766 assert_eq!(
767 state
768 .overload_info
769 .local_load_shedding_percentage
770 .load(Ordering::Relaxed),
771 config.min_load_shedding_percentage_above_hard_limit
772 );
773
774 let authority = Arc::downgrade(&state);
777 drop(state);
778 assert!(!check_execution_overload(&authority, &config));
779 }
780
781 #[test]
787 fn test_writeback_cache_backpressure_soft_limit_pct() {
788 use iota_config::node::WritebackCacheConfig;
789
790 let default_config = WritebackCacheConfig::default();
792 assert_eq!(default_config.backpressure_soft_limit_pct(), 50);
793 assert_eq!(default_config.backpressure_threshold(), 100_000);
794
795 let config = WritebackCacheConfig {
798 backpressure_threshold: Some(1000),
799 backpressure_soft_limit_pct: Some(75),
800 ..Default::default()
801 };
802 assert_eq!(config.backpressure_threshold(), 1000);
803 assert_eq!(config.backpressure_soft_limit_pct(), 75);
804
805 for pending in [0u64, 100, 750] {
807 assert_eq!(
808 compute_graduated_load_shedding_percentage(
809 pending as usize,
810 config.backpressure_threshold() as usize,
811 config.backpressure_soft_limit_pct(),
812 ),
813 0,
814 "no shedding expected at pending={pending} <= soft_limit=750",
815 );
816 }
817
818 assert_eq!(
820 compute_graduated_load_shedding_percentage(
821 875,
822 config.backpressure_threshold() as usize,
823 config.backpressure_soft_limit_pct(),
824 ),
825 50,
826 );
827
828 for pending in [1000u64, 1500, 100_000] {
830 assert_eq!(
831 compute_graduated_load_shedding_percentage(
832 pending as usize,
833 config.backpressure_threshold() as usize,
834 config.backpressure_soft_limit_pct(),
835 ),
836 100,
837 "100% shedding expected at pending={pending} >= hard_limit=1000",
838 );
839 }
840
841 let clamped = WritebackCacheConfig {
843 backpressure_soft_limit_pct: Some(150),
844 ..Default::default()
845 };
846 assert_eq!(clamped.backpressure_soft_limit_pct(), 100);
847 }
848
849 #[tokio::test(flavor = "current_thread")]
863 async fn test_check_execution_overload_cache_signal_drives_shedding() {
864 use iota_config::node::{ExecutionCacheConfig, WritebackCacheConfig};
865
866 telemetry_subscribers::init_for_testing();
867
868 let cache_config = ExecutionCacheConfig {
869 writeback_cache: WritebackCacheConfig {
870 backpressure_threshold: Some(0),
871 ..Default::default()
872 },
873 };
874 let overload_config = AuthorityOverloadConfig::default();
875 let state = TestAuthorityBuilder::new()
876 .with_authority_overload_config(overload_config.clone())
877 .with_cache_config(cache_config)
878 .build()
879 .await;
880
881 let authority = Arc::downgrade(&state);
882 assert!(check_execution_overload(&authority, &overload_config));
883
884 assert!(state.overload_info.is_overload.load(Ordering::Relaxed));
887 assert_eq!(
888 state
889 .overload_info
890 .local_load_shedding_percentage
891 .load(Ordering::Relaxed),
892 100,
893 );
894 assert_eq!(
895 state
896 .metrics
897 .local_post_consensus_load_shedding_percentage
898 .get(),
899 100,
900 );
901 }
902
903 async fn start_overload_monitor() -> (Arc<AuthorityState>, JoinHandle<()>) {
906 let overload_config = AuthorityOverloadConfig::default();
907 let state = TestAuthorityBuilder::new()
908 .with_authority_overload_config(overload_config.clone())
909 .build()
910 .await;
911 let authority_state = Arc::downgrade(&state);
912 let monitor_handle = tokio::spawn(async move {
913 overload_monitor(authority_state, overload_config).await;
914 });
915 (state, monitor_handle)
916 }
917
918 fn start_load_generator(
922 steady_rate: f64,
923 tx: UnboundedSender<Instant>,
924 mut burst_rx: UnboundedReceiver<u32>,
925 authority: Arc<AuthorityState>,
926 enable_load_shedding: bool,
927 total_requests_arc: Arc<AtomicU32>,
928 dropped_requests_arc: Arc<AtomicU32>,
929 ) -> JoinHandle<()> {
930 tokio::spawn(async move {
931 let mut interval = interval(Duration::from_secs_f64(1.0 / steady_rate));
932 let mut rng = StdRng::from_rng(&mut OsRng).unwrap();
933 let mut total_requests: u32 = 0;
934 let mut total_dropped_requests: u32 = 0;
935
936 let mut do_send =
938 |enable_load_shedding: bool, authority: Arc<AuthorityState>| -> bool {
939 if enable_load_shedding {
940 let shedding_percentage = authority
941 .overload_info
942 .local_load_shedding_percentage
943 .load(Ordering::Relaxed);
944 !(shedding_percentage > 0 && rng.gen_range(0..100) < shedding_percentage)
945 } else {
946 true
947 }
948 };
949
950 loop {
951 tokio::select! {
952 now = interval.tick() => {
953 total_requests += 1;
954 if do_send(enable_load_shedding, authority.clone()) {
955 if tx.send(now).is_err() {
956 info!("Load generator stopping. Total requests {:?}, total dropped requests {:?}.", total_requests, total_dropped_requests);
957 total_requests_arc.store(total_requests, Ordering::SeqCst);
958 dropped_requests_arc.store(total_dropped_requests, Ordering::SeqCst);
959 return;
960 }
961 authority.metrics.txn_ready_rate_tracker.lock().record();
962 } else {
963 total_dropped_requests += 1;
964 }
965 }
966 Some(burst) = burst_rx.recv() => {
967 let now = Instant::now();
968 total_requests += burst;
969 for _ in 0..burst {
970 if do_send(enable_load_shedding, authority.clone()) {
971 if tx.send(now).is_err() {
972 info!("Load generator stopping. Total requests {:?}, total dropped requests {:?}.", total_requests, total_dropped_requests);
973 total_requests_arc.store(total_requests, Ordering::SeqCst);
974 dropped_requests_arc.store(total_dropped_requests, Ordering::SeqCst);
975 return;
976 }
977 authority.metrics.txn_ready_rate_tracker.lock().record();
978 } else {
979 total_dropped_requests += 1;
980 }
981 }
982 }
983 }
984 }
985 })
986 }
987
988 fn start_executor(
991 execution_rate: f64,
992 mut rx: UnboundedReceiver<Instant>,
993 mut stop_rx: oneshot::Receiver<()>,
994 authority: Arc<AuthorityState>,
995 ) -> JoinHandle<()> {
996 tokio::spawn(async move {
997 let mut interval = interval(Duration::from_secs_f64(1.0 / execution_rate));
998 interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
999 loop {
1000 tokio::select! {
1001 Some(start_time) = rx.recv() => {
1002 authority.metrics.execution_rate_tracker.lock().record();
1003 authority.metrics.execution_queueing_latency.report(start_time.elapsed());
1004 interval.tick().await;
1005 }
1006 _ = &mut stop_rx => {
1007 info!("Executor stopping");
1008 return;
1009 }
1010 }
1011 }
1012 })
1013 }
1014
1015 async fn sleep_and_print_stats(state: Arc<AuthorityState>, seconds: u32) {
1017 for _ in 0..seconds {
1018 info!(
1019 "Overload: {:?}. Shedding percentage: {:?}. Queue: {:?}, Ready rate: {:?}. Exec rate: {:?}.",
1020 state.overload_info.is_overload.load(Ordering::Relaxed),
1021 state
1022 .overload_info
1023 .local_load_shedding_percentage
1024 .load(Ordering::Relaxed),
1025 state.metrics.execution_queueing_latency.latency(),
1026 state.metrics.txn_ready_rate_tracker.lock().rate(),
1027 state.metrics.execution_rate_tracker.lock().rate(),
1028 );
1029 sleep(Duration::from_secs(1)).await;
1030 }
1031 }
1032
1033 async fn run_consistent_workload_test(
1037 generator_rate: f64,
1038 executor_rate: f64,
1039 min_dropping_rate: f64,
1040 max_dropping_rate: f64,
1041 ) {
1042 let (state, monitor_handle) = start_overload_monitor().await;
1043
1044 let (tx, rx) = unbounded_channel();
1045 let (_burst_tx, burst_rx) = unbounded_channel();
1046 let total_requests = Arc::new(AtomicU32::new(0));
1047 let dropped_requests = Arc::new(AtomicU32::new(0));
1048 let load_generator = start_load_generator(
1049 generator_rate,
1050 tx.clone(),
1051 burst_rx,
1052 state.clone(),
1053 true,
1054 total_requests.clone(),
1055 dropped_requests.clone(),
1056 );
1057
1058 let (stop_tx, stop_rx) = oneshot::channel();
1059 let executor = start_executor(executor_rate, rx, stop_rx, state.clone());
1060
1061 sleep_and_print_stats(state.clone(), 300).await;
1062
1063 stop_tx.send(()).unwrap();
1064 let _ = tokio::join!(load_generator, executor);
1065
1066 let dropped_ratio = dropped_requests.load(Ordering::SeqCst) as f64
1067 / total_requests.load(Ordering::SeqCst) as f64;
1068 assert!(min_dropping_rate <= dropped_ratio);
1069 assert!(dropped_ratio <= max_dropping_rate);
1070
1071 monitor_handle.abort();
1072 let _ = monitor_handle.await;
1073 }
1074
1075 #[tokio::test(flavor = "current_thread", start_paused = true)]
1078 async fn test_workload_consistent_no_overload() {
1079 telemetry_subscribers::init_for_testing();
1080 run_consistent_workload_test(900.0, 1000.0, 0.0, 0.0).await;
1081 }
1082
1083 #[tokio::test(flavor = "current_thread", start_paused = true)]
1086 async fn test_workload_consistent_slightly_overload() {
1087 telemetry_subscribers::init_for_testing();
1088 run_consistent_workload_test(1100.0, 1000.0, 0.05, 0.25).await;
1090 }
1091
1092 #[tokio::test(flavor = "current_thread", start_paused = true)]
1095 async fn test_workload_consistent_overload() {
1096 telemetry_subscribers::init_for_testing();
1097 run_consistent_workload_test(3000.0, 1000.0, 0.6, 0.8).await;
1099 }
1100
1101 #[tokio::test(flavor = "current_thread", start_paused = true)]
1104 async fn test_workload_single_spike() {
1105 telemetry_subscribers::init_for_testing();
1106 let (state, monitor_handle) = start_overload_monitor().await;
1107
1108 let (tx, rx) = unbounded_channel();
1109 let (burst_tx, burst_rx) = unbounded_channel();
1110 let total_requests = Arc::new(AtomicU32::new(0));
1111 let dropped_requests = Arc::new(AtomicU32::new(0));
1112 let load_generator = start_load_generator(
1113 10.0,
1114 tx.clone(),
1115 burst_rx,
1116 state.clone(),
1117 true,
1118 total_requests.clone(),
1119 dropped_requests.clone(),
1120 );
1121
1122 let (stop_tx, stop_rx) = oneshot::channel();
1123 let executor = start_executor(1000.0, rx, stop_rx, state.clone());
1124
1125 sleep_and_print_stats(state.clone(), 10).await;
1126 burst_tx.send(5000).unwrap();
1128 sleep_and_print_stats(state.clone(), 20).await;
1129
1130 stop_tx.send(()).unwrap();
1131 let _ = tokio::join!(load_generator, executor);
1132
1133 assert_eq!(dropped_requests.load(Ordering::SeqCst), 0);
1135
1136 monitor_handle.abort();
1137 let _ = monitor_handle.await;
1138 }
1139
1140 #[tokio::test(flavor = "current_thread", start_paused = true)]
1143 async fn test_workload_consistent_short_spike() {
1144 telemetry_subscribers::init_for_testing();
1145 let (state, monitor_handle) = start_overload_monitor().await;
1146
1147 let (tx, rx) = unbounded_channel();
1148 let (burst_tx, burst_rx) = unbounded_channel();
1149 let total_requests = Arc::new(AtomicU32::new(0));
1150 let dropped_requests = Arc::new(AtomicU32::new(0));
1151 let load_generator = start_load_generator(
1152 10.0,
1153 tx.clone(),
1154 burst_rx,
1155 state.clone(),
1156 true,
1157 total_requests.clone(),
1158 dropped_requests.clone(),
1159 );
1160
1161 let (stop_tx, stop_rx) = oneshot::channel();
1162 let executor = start_executor(1000.0, rx, stop_rx, state.clone());
1163
1164 sleep_and_print_stats(state.clone(), 15).await;
1165 for _ in 0..16 {
1166 burst_tx.send(10000).unwrap();
1168 sleep_and_print_stats(state.clone(), 5).await;
1169 }
1170
1171 stop_tx.send(()).unwrap();
1172 let _ = tokio::join!(load_generator, executor);
1173 let dropped_ratio = dropped_requests.load(Ordering::SeqCst) as f64
1174 / total_requests.load(Ordering::SeqCst) as f64;
1175
1176 assert!(0.4 < dropped_ratio);
1179 assert!(dropped_ratio < 0.6);
1180
1181 monitor_handle.abort();
1182 let _ = monitor_handle.await;
1183 }
1184
1185 #[test]
1188 fn test_txn_rejection_rate() {
1189 for rejection_percentage in 0..=100 {
1190 let mut reject_count = 0;
1191 for _ in 0..10000 {
1192 let digest = TransactionDigest::random();
1193 if should_reject_tx(rejection_percentage, digest, 28455473) {
1194 reject_count += 1;
1195 }
1196 }
1197
1198 debug!(
1199 "Rejection percentage: {:?}, reject count: {:?}.",
1200 rejection_percentage, reject_count
1201 );
1202 assert!(rejection_percentage as f32 / 100.0 - 0.03 < reject_count as f32 / 10000.0);
1204 assert!(reject_count as f32 / 10000.0 < rejection_percentage as f32 / 100.0 + 0.03);
1205 }
1206 }
1207
1208 #[sim_test]
1211 async fn test_txn_rejection_over_time() {
1212 let start_time = Instant::now();
1213 let mut digest = TransactionDigest::random();
1214 let mut temporal_seed = 1708108277 / SEED_UPDATE_DURATION_SECS;
1215 let load_shedding_percentage = 50;
1216
1217 while !should_reject_tx(load_shedding_percentage, digest, temporal_seed)
1219 && start_time.elapsed() < Duration::from_secs(30)
1220 {
1221 digest = TransactionDigest::random();
1222 }
1223
1224 for _ in 0..100 {
1226 assert!(should_reject_tx(
1227 load_shedding_percentage,
1228 digest,
1229 temporal_seed
1230 ));
1231 }
1232
1233 temporal_seed += 1;
1235 while should_reject_tx(load_shedding_percentage, digest, temporal_seed)
1236 && start_time.elapsed() < Duration::from_secs(30)
1237 {
1238 temporal_seed += 1;
1239 }
1240
1241 assert!(start_time.elapsed() < Duration::from_secs(30));
1243 }
1244}