1use std::collections::VecDeque;
2use std::fmt::Debug;
3use std::num::NonZeroUsize;
4
5use foldhash::{HashMap, HashMapExt, HashSet, HashSetExt};
6use itertools::Itertools;
7use nonempty::NonEmpty;
8use rand::prelude::*;
9use rand::rng;
10
11use crate::pal::{Platform, PlatformFacade};
12use crate::{
13 EfficiencyClass, HardwareTrackerClientFacade, MemoryRegionId, Processor, ProcessorId,
14 ProcessorSet,
15};
16
17#[doc = include_str!("../docs/snippets/external_constraints.md")]
23#[derive(Clone, Debug)]
37pub struct ProcessorSetBuilder {
38 processor_type_selector: ProcessorTypeSelector,
39 memory_region_selector: MemoryRegionSelector,
40
41 except_indexes: HashSet<ProcessorId>,
42
43 obey_resource_quota: bool,
44
45 tracker_client: HardwareTrackerClientFacade,
49
50 pal: PlatformFacade,
51}
52
53impl ProcessorSetBuilder {
54 #[must_use]
60 pub fn new() -> Self {
61 Self::with_internals(HardwareTrackerClientFacade::real(), PlatformFacade::real())
62 }
63
64 #[must_use]
65 pub(crate) fn with_internals(
66 tracker_client: HardwareTrackerClientFacade,
67 pal: PlatformFacade,
68 ) -> Self {
69 Self {
70 processor_type_selector: ProcessorTypeSelector::Any,
71 memory_region_selector: MemoryRegionSelector::Any,
72 except_indexes: HashSet::new(),
73 obey_resource_quota: true,
74 tracker_client,
75 pal,
76 }
77 }
78
79 #[must_use]
102 pub fn performance_processors_only(mut self) -> Self {
103 self.processor_type_selector = ProcessorTypeSelector::Performance;
104 self
105 }
106
107 #[must_use]
141 pub fn efficiency_processors_only(mut self) -> Self {
142 self.processor_type_selector = ProcessorTypeSelector::Efficiency;
143 self
144 }
145
146 #[must_use]
179 pub fn different_memory_regions(mut self) -> Self {
180 self.memory_region_selector = MemoryRegionSelector::RequireDifferent;
181 self
182 }
183
184 #[must_use]
221 pub fn same_memory_region(mut self) -> Self {
222 self.memory_region_selector = MemoryRegionSelector::RequireSame;
223 self
224 }
225
226 #[must_use]
230 pub fn prefer_different_memory_regions(mut self) -> Self {
231 self.memory_region_selector = MemoryRegionSelector::PreferDifferent;
232 self
233 }
234
235 #[must_use]
239 pub fn prefer_same_memory_region(mut self) -> Self {
240 self.memory_region_selector = MemoryRegionSelector::PreferSame;
241 self
242 }
243
244 #[must_use]
275 pub fn filter(mut self, predicate: impl Fn(&Processor) -> bool) -> Self {
276 for processor in self.all_processors() {
280 if !predicate(&processor) {
281 self.except_indexes.insert(processor.id());
282 }
283 }
284
285 self
286 }
287
288 #[must_use]
315 pub fn except<'a, I>(mut self, processors: I) -> Self
316 where
317 I: IntoIterator<Item = &'a Processor>,
318 {
319 for processor in processors {
320 self.except_indexes.insert(processor.id());
321 }
322
323 self
324 }
325
326 #[must_use]
361 pub fn where_available_for_current_thread(mut self) -> Self {
362 let current_thread_processors = self.pal.current_thread_processors();
363
364 for processor in self.all_processors() {
365 if !current_thread_processors.contains(&processor.id()) {
366 self.except_indexes.insert(processor.id());
367 }
368 }
369
370 self
371 }
372
373 #[must_use]
402 pub fn ignoring_resource_quota(mut self) -> Self {
403 self.obey_resource_quota = false;
404 self
405 }
406
407 #[must_use]
423 pub fn take(self, count: NonZeroUsize) -> Option<ProcessorSet> {
424 if let Some(max_count) = self.resource_quota_processor_count_limit()
425 && count.get() > max_count
426 {
427 return None;
429 }
430
431 let candidates = self.candidates_by_memory_region();
432
433 if candidates.is_empty() {
434 return None;
436 }
437
438 let processors = match self.memory_region_selector {
439 MemoryRegionSelector::Any => {
440 let all_processors = candidates
443 .values()
444 .flat_map(|x| x.iter().cloned())
445 .collect::<Vec<_>>();
446
447 if all_processors.len() < count.get() {
448 return None;
450 }
451
452 all_processors
453 .choose_multiple(&mut rng(), count.get())
454 .cloned()
455 .collect_vec()
456 }
457 MemoryRegionSelector::PreferSame => {
458 let count = count.get();
460
461 let mut remaining_memory_regions = candidates.keys().copied().collect_vec();
466 remaining_memory_regions.shuffle(&mut rng());
467 remaining_memory_regions.sort_unstable_by_key(|x| {
468 candidates
469 .get(x)
470 .expect("region must exist - we just got it from there")
471 .len()
472 .min(count)
474 });
475 remaining_memory_regions.reverse();
477
478 let mut remaining_memory_regions = VecDeque::from(remaining_memory_regions);
479
480 let mut processors: Vec<Processor> = Vec::with_capacity(count);
481
482 while processors.len() < count {
483 let memory_region = remaining_memory_regions.pop_front()?;
484
485 let processors_in_region = candidates.get(&memory_region).expect(
486 "we picked an existing key from an existing HashSet - the values must exist",
487 );
488
489 let choose_count = count.min(processors_in_region.len());
491
492 let region_processors = processors_in_region
493 .choose_multiple(&mut rng(), choose_count)
494 .cloned();
495
496 processors.extend(region_processors);
497 }
498
499 processors
500 }
501 MemoryRegionSelector::RequireSame => {
502 let qualifying_memory_regions = candidates
505 .iter()
506 .filter_map(|(region, processors)| {
507 if processors.len() < count.get() {
508 return None;
509 }
510
511 Some(region)
512 })
513 .collect_vec();
514
515 let memory_region = qualifying_memory_regions.choose(&mut rng())?;
516
517 let processors = candidates.get(memory_region).expect(
518 "we picked an existing key for an existing HashSet - the values must exist",
519 );
520
521 processors
522 .choose_multiple(&mut rng(), count.get())
523 .cloned()
524 .collect_vec()
525 }
526 MemoryRegionSelector::PreferDifferent => {
527 let mut candidates = candidates;
532
533 let mut processors = Vec::with_capacity(count.get());
534
535 while processors.len() < count.get() {
536 if candidates.is_empty() {
537 return None;
539 }
540
541 for remaining_processors in candidates.values_mut() {
542 let (index, processor) =
543 remaining_processors.iter().enumerate().choose(&mut rng())?;
544
545 let processor = processor.clone();
546
547 remaining_processors.remove(index);
548
549 processors.push(processor);
550
551 if processors.len() == count.get() {
552 break;
553 }
554 }
555
556 candidates.retain(|_, remaining_processors| !remaining_processors.is_empty());
558 }
559
560 processors
561 }
562 MemoryRegionSelector::RequireDifferent => {
563 if candidates.len() < count.get() {
566 return None;
568 }
569
570 candidates
571 .iter()
572 .choose_multiple(&mut rng(), count.get())
573 .into_iter()
574 .map(|(_, processors)| {
575 processors.iter().choose(&mut rng()).cloned().expect(
576 "we are picking one item from a non-empty list - item must exist",
577 )
578 })
579 .collect_vec()
580 }
581 };
582
583 Some(ProcessorSet::new(
584 NonEmpty::from_vec(processors)?,
585 self.tracker_client,
586 self.pal,
587 ))
588 }
589
590 #[must_use]
641 #[cfg_attr(test, mutants::skip)] pub fn take_all(self) -> Option<ProcessorSet> {
643 let candidates = self.candidates_by_memory_region();
644
645 if candidates.is_empty() {
646 return None;
648 }
649
650 let processors = match self.memory_region_selector {
651 MemoryRegionSelector::Any
652 | MemoryRegionSelector::PreferSame
653 | MemoryRegionSelector::PreferDifferent => {
654 candidates
657 .values()
658 .flat_map(|x| x.iter().cloned())
659 .collect()
660 }
661 MemoryRegionSelector::RequireSame => {
662 let memory_region = candidates
667 .keys()
668 .choose(&mut rng())
669 .expect("we picked a random existing index - element must exist");
670
671 let processors = candidates.get(memory_region).expect(
672 "we picked an existing key for an existing HashSet - the values must exist",
673 );
674
675 processors.clone()
676 }
677 MemoryRegionSelector::RequireDifferent => {
678 let processors = candidates.values().map(|processors| {
682 processors
683 .choose(&mut rng())
684 .cloned()
685 .expect("we picked a random item from a non-empty list - item must exist")
686 });
687
688 processors.collect()
689 }
690 };
691
692 let processors = self.reduce_processors_until_under_quota(processors);
693
694 Some(ProcessorSet::new(
695 NonEmpty::from_vec(processors)?,
696 self.tracker_client,
697 self.pal,
698 ))
699 }
700
701 fn reduce_processors_until_under_quota(&self, processors: Vec<Processor>) -> Vec<Processor> {
702 let Some(max_count) = self.resource_quota_processor_count_limit() else {
703 return processors;
704 };
705
706 let mut processors = processors;
707
708 while processors.len() > max_count {
710 processors
711 .pop()
712 .expect("guarded by len-check in loop condition");
713 }
714
715 processors
716 }
717
718 fn candidates_by_memory_region(&self) -> HashMap<MemoryRegionId, Vec<Processor>> {
725 let candidates_iter = self.all_processors().into_iter().filter_map(move |p| {
726 if self.except_indexes.contains(&p.id()) {
727 return None;
728 }
729
730 let is_acceptable_type = match self.processor_type_selector {
731 ProcessorTypeSelector::Any => true,
732 ProcessorTypeSelector::Performance => {
733 p.efficiency_class() == EfficiencyClass::Performance
734 }
735 ProcessorTypeSelector::Efficiency => {
736 p.efficiency_class() == EfficiencyClass::Efficiency
737 }
738 };
739
740 if !is_acceptable_type {
741 return None;
742 }
743
744 Some((p.memory_region_id(), p))
745 });
746
747 let mut candidates = HashMap::new();
748 for (region, processor) in candidates_iter {
749 candidates
750 .entry(region)
751 .or_insert_with(Vec::new)
752 .push(processor);
753 }
754
755 candidates
756 }
757
758 fn all_processors(&self) -> NonEmpty<Processor> {
759 self.pal.get_all_processors().map(Processor::new)
762 }
763
764 fn resource_quota_processor_count_limit(&self) -> Option<usize> {
765 if self.obey_resource_quota {
766 let max_processor_time = self.pal.max_processor_time();
767
768 #[expect(clippy::cast_sign_loss, reason = "quota cannot be negative")]
772 #[expect(
773 clippy::cast_possible_truncation,
774 reason = "we are correctly rounding to avoid the problem"
775 )]
776 let max_processor_count = max_processor_time.floor() as usize;
777
778 Some(max_processor_count.max(1))
782 } else {
783 None
784 }
785 }
786}
787
788impl Default for ProcessorSetBuilder {
789 #[inline]
790 fn default() -> Self {
791 Self::new()
792 }
793}
794
795#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
796enum MemoryRegionSelector {
797 #[default]
799 Any,
800
801 RequireSame,
804
805 RequireDifferent,
808
809 PreferSame,
813
814 PreferDifferent,
818}
819
820#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
821enum ProcessorTypeSelector {
822 #[default]
824 Any,
825
826 Performance,
832
833 Efficiency,
837}
838
839#[cfg(not(miri))] #[cfg(test)]
841mod tests_real {
842 use new_zealand::nz;
843
844 use super::*;
845
846 #[test]
847 fn spawn_on_any_processor() {
848 let set = ProcessorSet::builder().take_all().unwrap();
849 let result = set.spawn_thread(move |_| 1234).join().unwrap();
850
851 assert_eq!(result, 1234);
852 }
853
854 #[test]
855 fn spawn_on_every_processor() {
856 let set = ProcessorSet::builder().take_all().unwrap();
857 let processor_count = set.len();
858
859 let join_handles = set.spawn_threads(move |_| 4567);
860
861 assert_eq!(join_handles.len(), processor_count);
862
863 for handle in join_handles {
864 let result = handle.join().unwrap();
865 assert_eq!(result, 4567);
866 }
867 }
868
869 #[test]
870 fn filter_by_memory_region_real() {
871 ProcessorSet::builder()
873 .same_memory_region()
874 .take_all()
875 .unwrap();
876 ProcessorSet::builder()
877 .same_memory_region()
878 .take(nz!(1))
879 .unwrap();
880 ProcessorSet::builder()
881 .different_memory_regions()
882 .take_all()
883 .unwrap();
884 ProcessorSet::builder()
885 .different_memory_regions()
886 .take(nz!(1))
887 .unwrap();
888 }
889
890 #[test]
891 fn filter_by_efficiency_class_real() {
892 ProcessorSet::builder()
894 .performance_processors_only()
895 .take_all()
896 .unwrap();
897 ProcessorSet::builder()
898 .performance_processors_only()
899 .take(nz!(1))
900 .unwrap();
901
902 drop(
905 ProcessorSet::builder()
906 .efficiency_processors_only()
907 .take_all(),
908 );
909 drop(
910 ProcessorSet::builder()
911 .efficiency_processors_only()
912 .take(nz!(1)),
913 );
914 }
915
916 #[test]
917 fn filter_in_all() {
918 let starting_set = ProcessorSet::builder().take_all().unwrap();
920
921 let processors = starting_set
923 .to_builder()
924 .filter(|_| true)
925 .take_all()
926 .unwrap();
927 let processor_count = starting_set.len();
928
929 assert_eq!(processors.len(), processor_count);
930 }
931
932 #[test]
933 fn filter_out_all() {
934 assert!(
936 ProcessorSet::builder()
937 .filter(|_| false)
938 .take_all()
939 .is_none()
940 );
941 }
942
943 #[test]
944 fn except_all() {
945 let starting_set = ProcessorSet::builder().take_all().unwrap();
947
948 assert!(
950 starting_set
951 .to_builder()
952 .except(starting_set.processors().iter())
953 .take_all()
954 .is_none()
955 );
956 }
957}
958
959#[cfg(test)]
960mod tests {
961 use new_zealand::nz;
962 use nonempty::nonempty;
963
964 use super::*;
965 use crate::pal::{FakeProcessor, MockPlatform, ProcessorFacade};
966
967 #[test]
968 fn smoke_test() {
969 let pal_processors = nonempty![
970 FakeProcessor {
971 index: 0,
972 memory_region: 0,
973 efficiency_class: EfficiencyClass::Efficiency,
974 },
975 FakeProcessor {
976 index: 1,
977 memory_region: 0,
978 efficiency_class: EfficiencyClass::Performance,
979 }
980 ];
981
982 let platform = new_mock_platform(pal_processors);
983
984 let builder = ProcessorSetBuilder::with_internals(
985 HardwareTrackerClientFacade::default_mock(),
986 platform.into(),
987 );
988
989 let set = builder.take_all().unwrap();
991 assert_eq!(set.len(), 2);
992 }
993
994 #[test]
995 fn efficiency_class_filter_take() {
996 let pal_processors = nonempty![
997 FakeProcessor {
998 index: 0,
999 memory_region: 0,
1000 efficiency_class: EfficiencyClass::Efficiency,
1001 },
1002 FakeProcessor {
1003 index: 1,
1004 memory_region: 0,
1005 efficiency_class: EfficiencyClass::Performance,
1006 }
1007 ];
1008
1009 let platform = new_mock_platform(pal_processors);
1010
1011 let builder = ProcessorSetBuilder::with_internals(
1012 HardwareTrackerClientFacade::default_mock(),
1013 platform.into(),
1014 );
1015 let set = builder.efficiency_processors_only().take_all().unwrap();
1016 assert_eq!(set.len(), 1);
1017 assert_eq!(set.processors().first().id(), 0);
1018 }
1019
1020 #[test]
1021 fn efficiency_class_filter_take_all() {
1022 let pal_processors = nonempty![
1023 FakeProcessor {
1024 index: 0,
1025 memory_region: 0,
1026 efficiency_class: EfficiencyClass::Efficiency,
1027 },
1028 FakeProcessor {
1029 index: 1,
1030 memory_region: 0,
1031 efficiency_class: EfficiencyClass::Performance,
1032 }
1033 ];
1034
1035 let platform = new_mock_platform(pal_processors);
1036
1037 let builder = ProcessorSetBuilder::with_internals(
1038 HardwareTrackerClientFacade::default_mock(),
1039 platform.into(),
1040 );
1041 let set = builder.efficiency_processors_only().take_all().unwrap();
1042 assert_eq!(set.len(), 1);
1043 assert_eq!(set.processors().first().id(), 0);
1044 }
1045
1046 #[test]
1047 fn take_n_processors() {
1048 let pal_processors = nonempty![
1049 FakeProcessor {
1050 index: 0,
1051 memory_region: 0,
1052 efficiency_class: EfficiencyClass::Efficiency,
1053 },
1054 FakeProcessor {
1055 index: 1,
1056 memory_region: 0,
1057 efficiency_class: EfficiencyClass::Performance,
1058 },
1059 FakeProcessor {
1060 index: 2,
1061 memory_region: 0,
1062 efficiency_class: EfficiencyClass::Efficiency,
1063 }
1064 ];
1065
1066 let platform = new_mock_platform(pal_processors);
1067
1068 let builder = ProcessorSetBuilder::with_internals(
1069 HardwareTrackerClientFacade::default_mock(),
1070 platform.into(),
1071 );
1072 let set = builder.take(nz!(2)).unwrap();
1073 assert_eq!(set.len(), 2);
1074 }
1075
1076 #[test]
1077 fn take_n_not_enough_processors() {
1078 let pal_processors = nonempty![
1079 FakeProcessor {
1080 index: 0,
1081 memory_region: 0,
1082 efficiency_class: EfficiencyClass::Efficiency,
1083 },
1084 FakeProcessor {
1085 index: 1,
1086 memory_region: 0,
1087 efficiency_class: EfficiencyClass::Performance,
1088 }
1089 ];
1090
1091 let platform = new_mock_platform_with_get_count(pal_processors, 0, 1);
1094
1095 let builder = ProcessorSetBuilder::with_internals(
1096 HardwareTrackerClientFacade::default_mock(),
1097 platform.into(),
1098 );
1099 let set = builder.take(nz!(3));
1100 assert!(set.is_none());
1101 }
1102
1103 #[test]
1104 fn take_n_not_enough_processor_time_quota() {
1105 let mut platform = MockPlatform::new();
1106
1107 platform
1109 .expect_max_processor_time()
1110 .times(1)
1111 .return_const(1.0);
1112
1113 let builder = ProcessorSetBuilder::with_internals(
1114 HardwareTrackerClientFacade::default_mock(),
1115 platform.into(),
1116 );
1117 let set = builder.take(nz!(2));
1118 assert!(set.is_none());
1119 }
1120
1121 #[test]
1122 fn take_n_not_enough_processor_time_quota_but_ignoring_quota() {
1123 let pal_processors = nonempty![
1124 FakeProcessor {
1125 index: 0,
1126 memory_region: 0,
1127 efficiency_class: EfficiencyClass::Efficiency,
1128 },
1129 FakeProcessor {
1130 index: 1,
1131 memory_region: 0,
1132 efficiency_class: EfficiencyClass::Performance,
1133 }
1134 ];
1135
1136 let mut platform = MockPlatform::new();
1137 let pal_processors = pal_processors.map(ProcessorFacade::Fake);
1138
1139 platform.expect_max_processor_time().return_const(1.0);
1143
1144 platform
1145 .expect_get_all_processors_core()
1146 .times(1)
1147 .return_const(pal_processors);
1148
1149 let builder = ProcessorSetBuilder::with_internals(
1150 HardwareTrackerClientFacade::default_mock(),
1151 platform.into(),
1152 );
1153 let set = builder.ignoring_resource_quota().take(nz!(2));
1154
1155 assert_eq!(set.unwrap().len(), 2);
1156 }
1157
1158 #[test]
1159 fn take_n_quota_limit_min_1() {
1160 let pal_processors = nonempty![
1161 FakeProcessor {
1162 index: 0,
1163 memory_region: 0,
1164 efficiency_class: EfficiencyClass::Efficiency,
1165 },
1166 FakeProcessor {
1167 index: 1,
1168 memory_region: 0,
1169 efficiency_class: EfficiencyClass::Performance,
1170 }
1171 ];
1172
1173 let mut platform = MockPlatform::new();
1174 let pal_processors = pal_processors.map(ProcessorFacade::Fake);
1175
1176 platform.expect_max_processor_time().return_const(0.001);
1181
1182 platform
1183 .expect_get_all_processors_core()
1184 .times(1)
1185 .return_const(pal_processors);
1186
1187 let builder = ProcessorSetBuilder::with_internals(
1188 HardwareTrackerClientFacade::default_mock(),
1189 platform.into(),
1190 );
1191 let set = builder.take(nz!(1));
1192
1193 assert_eq!(set.unwrap().len(), 1);
1194 }
1195
1196 #[test]
1197 fn take_all_rounds_down_quota() {
1198 let pal_processors = nonempty![
1199 FakeProcessor {
1200 index: 0,
1201 memory_region: 0,
1202 efficiency_class: EfficiencyClass::Efficiency,
1203 },
1204 FakeProcessor {
1205 index: 1,
1206 memory_region: 0,
1207 efficiency_class: EfficiencyClass::Performance,
1208 }
1209 ];
1210
1211 let mut platform = MockPlatform::new();
1212 let pal_processors = pal_processors.map(ProcessorFacade::Fake);
1213
1214 platform
1216 .expect_max_processor_time()
1217 .times(1)
1218 .return_const(1.999);
1219
1220 platform
1221 .expect_get_all_processors_core()
1222 .times(1)
1223 .return_const(pal_processors);
1224
1225 let builder = ProcessorSetBuilder::with_internals(
1226 HardwareTrackerClientFacade::default_mock(),
1227 platform.into(),
1228 );
1229 let set = builder.take_all().unwrap();
1230 assert_eq!(set.len(), 1);
1231 }
1232
1233 #[test]
1234 fn take_all_min_1_despite_quota() {
1235 let pal_processors = nonempty![
1236 FakeProcessor {
1237 index: 0,
1238 memory_region: 0,
1239 efficiency_class: EfficiencyClass::Efficiency,
1240 },
1241 FakeProcessor {
1242 index: 1,
1243 memory_region: 0,
1244 efficiency_class: EfficiencyClass::Performance,
1245 }
1246 ];
1247
1248 let mut platform = MockPlatform::new();
1249 let pal_processors = pal_processors.map(ProcessorFacade::Fake);
1250
1251 platform
1256 .expect_max_processor_time()
1257 .times(1)
1258 .return_const(0.001);
1259
1260 platform
1261 .expect_get_all_processors_core()
1262 .times(1)
1263 .return_const(pal_processors);
1264
1265 let builder = ProcessorSetBuilder::with_internals(
1266 HardwareTrackerClientFacade::default_mock(),
1267 platform.into(),
1268 );
1269 let set = builder.take_all().unwrap();
1270 assert_eq!(set.len(), 1);
1271 }
1272
1273 #[test]
1274 fn take_all_not_enough_processors() {
1275 let pal_processors = nonempty![FakeProcessor {
1276 index: 0,
1277 memory_region: 0,
1278 efficiency_class: EfficiencyClass::Efficiency,
1279 }];
1280
1281 let platform = new_mock_platform_with_get_count(pal_processors, 1, 0);
1283
1284 let builder = ProcessorSetBuilder::with_internals(
1285 HardwareTrackerClientFacade::default_mock(),
1286 platform.into(),
1287 );
1288 let set = builder.performance_processors_only().take_all();
1289 assert!(set.is_none());
1290 }
1291
1292 #[test]
1293 fn except_filter_take() {
1294 let pal_processors = nonempty![
1295 FakeProcessor {
1296 index: 0,
1297 memory_region: 0,
1298 efficiency_class: EfficiencyClass::Efficiency,
1299 },
1300 FakeProcessor {
1301 index: 1,
1302 memory_region: 0,
1303 efficiency_class: EfficiencyClass::Performance,
1304 }
1305 ];
1306
1307 let platform = new_mock_platform_with_get_count(pal_processors, 3, 2);
1310
1311 let builder = ProcessorSetBuilder::with_internals(
1312 HardwareTrackerClientFacade::default_mock(),
1313 platform.into(),
1314 );
1315
1316 let except_set = builder.clone().filter(|p| p.id() == 0).take_all().unwrap();
1317 assert_eq!(except_set.len(), 1);
1318
1319 let set = builder.except(except_set.processors()).take_all().unwrap();
1320 assert_eq!(set.len(), 1);
1321 assert_eq!(set.processors().first().id(), 1);
1322 }
1323
1324 #[test]
1325 fn except_filter_take_all() {
1326 let pal_processors = nonempty![
1327 FakeProcessor {
1328 index: 0,
1329 memory_region: 0,
1330 efficiency_class: EfficiencyClass::Efficiency,
1331 },
1332 FakeProcessor {
1333 index: 1,
1334 memory_region: 0,
1335 efficiency_class: EfficiencyClass::Performance,
1336 }
1337 ];
1338
1339 let platform = new_mock_platform_with_get_count(pal_processors, 3, 2);
1342
1343 let builder = ProcessorSetBuilder::with_internals(
1344 HardwareTrackerClientFacade::default_mock(),
1345 platform.into(),
1346 );
1347 let except_set = builder.clone().filter(|p| p.id() == 0).take_all().unwrap();
1348 assert_eq!(except_set.len(), 1);
1349
1350 let set = builder.except(except_set.processors()).take_all().unwrap();
1351 assert_eq!(set.len(), 1);
1352 assert_eq!(set.processors().first().id(), 1);
1353 }
1354
1355 #[test]
1356 fn custom_filter_take() {
1357 let pal_processors = nonempty![
1358 FakeProcessor {
1359 index: 0,
1360 memory_region: 0,
1361 efficiency_class: EfficiencyClass::Efficiency,
1362 },
1363 FakeProcessor {
1364 index: 1,
1365 memory_region: 0,
1366 efficiency_class: EfficiencyClass::Performance,
1367 }
1368 ];
1369
1370 let platform = new_mock_platform_with_get_count(pal_processors, 2, 1);
1372
1373 let builder = ProcessorSetBuilder::with_internals(
1374 HardwareTrackerClientFacade::default_mock(),
1375 platform.into(),
1376 );
1377 let set = builder.filter(|p| p.id() == 1).take_all().unwrap();
1378 assert_eq!(set.len(), 1);
1379 assert_eq!(set.processors().first().id(), 1);
1380 }
1381
1382 #[test]
1383 fn custom_filter_take_all() {
1384 let pal_processors = nonempty![
1385 FakeProcessor {
1386 index: 0,
1387 memory_region: 0,
1388 efficiency_class: EfficiencyClass::Efficiency,
1389 },
1390 FakeProcessor {
1391 index: 1,
1392 memory_region: 0,
1393 efficiency_class: EfficiencyClass::Performance,
1394 }
1395 ];
1396
1397 let platform = new_mock_platform_with_get_count(pal_processors, 2, 1);
1399
1400 let builder = ProcessorSetBuilder::with_internals(
1401 HardwareTrackerClientFacade::default_mock(),
1402 platform.into(),
1403 );
1404 let set = builder.filter(|p| p.id() == 1).take_all().unwrap();
1405 assert_eq!(set.len(), 1);
1406 assert_eq!(set.processors().first().id(), 1);
1407 }
1408
1409 #[test]
1410 fn same_memory_region_filter_take() {
1411 let pal_processors = nonempty![
1412 FakeProcessor {
1413 index: 0,
1414 memory_region: 0,
1415 efficiency_class: EfficiencyClass::Efficiency,
1416 },
1417 FakeProcessor {
1418 index: 1,
1419 memory_region: 1,
1420 efficiency_class: EfficiencyClass::Performance,
1421 }
1422 ];
1423
1424 let platform = new_mock_platform(pal_processors);
1425
1426 let builder = ProcessorSetBuilder::with_internals(
1427 HardwareTrackerClientFacade::default_mock(),
1428 platform.into(),
1429 );
1430 let set = builder.same_memory_region().take_all().unwrap();
1431 assert_eq!(set.len(), 1);
1432 }
1433
1434 #[test]
1435 fn same_memory_region_filter_take_all() {
1436 let pal_processors = nonempty![
1437 FakeProcessor {
1438 index: 0,
1439 memory_region: 0,
1440 efficiency_class: EfficiencyClass::Efficiency,
1441 },
1442 FakeProcessor {
1443 index: 1,
1444 memory_region: 1,
1445 efficiency_class: EfficiencyClass::Performance,
1446 }
1447 ];
1448
1449 let platform = new_mock_platform(pal_processors);
1450
1451 let builder = ProcessorSetBuilder::with_internals(
1452 HardwareTrackerClientFacade::default_mock(),
1453 platform.into(),
1454 );
1455 let set = builder.same_memory_region().take_all().unwrap();
1456 assert_eq!(set.len(), 1);
1457 }
1458
1459 #[test]
1460 fn different_memory_region_filter_take() {
1461 let pal_processors = nonempty![
1462 FakeProcessor {
1463 index: 0,
1464 memory_region: 0,
1465 efficiency_class: EfficiencyClass::Efficiency,
1466 },
1467 FakeProcessor {
1468 index: 1,
1469 memory_region: 0,
1470 efficiency_class: EfficiencyClass::Efficiency,
1471 },
1472 FakeProcessor {
1473 index: 2,
1474 memory_region: 1,
1475 efficiency_class: EfficiencyClass::Performance,
1476 },
1477 FakeProcessor {
1478 index: 3,
1479 memory_region: 1,
1480 efficiency_class: EfficiencyClass::Performance,
1481 }
1482 ];
1483
1484 let platform = new_mock_platform(pal_processors);
1485
1486 let builder = ProcessorSetBuilder::with_internals(
1487 HardwareTrackerClientFacade::default_mock(),
1488 platform.into(),
1489 );
1490 let set = builder.different_memory_regions().take_all().unwrap();
1491 assert_eq!(set.len(), 2);
1492
1493 assert_ne!(
1494 set.processors().first().memory_region_id(),
1495 set.processors().last().memory_region_id()
1496 );
1497 }
1498
1499 #[test]
1500 fn different_memory_region_filter_take_all() {
1501 let pal_processors = nonempty![
1502 FakeProcessor {
1503 index: 0,
1504 memory_region: 0,
1505 efficiency_class: EfficiencyClass::Efficiency,
1506 },
1507 FakeProcessor {
1508 index: 1,
1509 memory_region: 0,
1510 efficiency_class: EfficiencyClass::Efficiency,
1511 },
1512 FakeProcessor {
1513 index: 2,
1514 memory_region: 1,
1515 efficiency_class: EfficiencyClass::Performance,
1516 },
1517 FakeProcessor {
1518 index: 3,
1519 memory_region: 1,
1520 efficiency_class: EfficiencyClass::Performance,
1521 }
1522 ];
1523
1524 let platform = new_mock_platform(pal_processors);
1525
1526 let builder = ProcessorSetBuilder::with_internals(
1527 HardwareTrackerClientFacade::default_mock(),
1528 platform.into(),
1529 );
1530 let set = builder.different_memory_regions().take_all().unwrap();
1531 assert_eq!(set.len(), 2);
1532
1533 assert_ne!(
1534 set.processors().first().memory_region_id(),
1535 set.processors().last().memory_region_id()
1536 );
1537 }
1538
1539 #[test]
1540 fn filter_combinations() {
1541 let pal_processors = nonempty![
1542 FakeProcessor {
1543 index: 0,
1544 memory_region: 0,
1545 efficiency_class: EfficiencyClass::Efficiency,
1546 },
1547 FakeProcessor {
1548 index: 1,
1549 memory_region: 1,
1550 efficiency_class: EfficiencyClass::Performance,
1551 },
1552 FakeProcessor {
1553 index: 2,
1554 memory_region: 1,
1555 efficiency_class: EfficiencyClass::Efficiency,
1556 }
1557 ];
1558
1559 let platform = new_mock_platform_with_get_count(pal_processors, 3, 2);
1562
1563 let builder = ProcessorSetBuilder::with_internals(
1564 HardwareTrackerClientFacade::default_mock(),
1565 platform.into(),
1566 );
1567 let except_set = builder.clone().filter(|p| p.id() == 0).take_all().unwrap();
1568 let set = builder
1569 .efficiency_processors_only()
1570 .except(except_set.processors())
1571 .different_memory_regions()
1572 .take_all()
1573 .unwrap();
1574 assert_eq!(set.len(), 1);
1575 assert_eq!(set.processors().first().id(), 2);
1576 }
1577
1578 #[test]
1579 fn same_memory_region_take_two_processors() {
1580 let pal_processors = nonempty![
1581 FakeProcessor {
1582 index: 0,
1583 memory_region: 0,
1584 efficiency_class: EfficiencyClass::Efficiency,
1585 },
1586 FakeProcessor {
1587 index: 1,
1588 memory_region: 1,
1589 efficiency_class: EfficiencyClass::Performance,
1590 },
1591 FakeProcessor {
1592 index: 2,
1593 memory_region: 1,
1594 efficiency_class: EfficiencyClass::Efficiency,
1595 }
1596 ];
1597
1598 let platform = new_mock_platform(pal_processors);
1599
1600 let builder = ProcessorSetBuilder::with_internals(
1601 HardwareTrackerClientFacade::default_mock(),
1602 platform.into(),
1603 );
1604 let set = builder.same_memory_region().take(nz!(2)).unwrap();
1605 assert_eq!(set.len(), 2);
1606 assert!(set.processors().iter().any(|p| p.id() == 1));
1607 assert!(set.processors().iter().any(|p| p.id() == 2));
1608 }
1609
1610 #[test]
1611 fn different_memory_region_and_efficiency_class_filters() {
1612 let pal_processors = nonempty![
1613 FakeProcessor {
1614 index: 0,
1615 memory_region: 0,
1616 efficiency_class: EfficiencyClass::Efficiency,
1617 },
1618 FakeProcessor {
1619 index: 1,
1620 memory_region: 1,
1621 efficiency_class: EfficiencyClass::Performance,
1622 },
1623 FakeProcessor {
1624 index: 2,
1625 memory_region: 2,
1626 efficiency_class: EfficiencyClass::Efficiency,
1627 },
1628 FakeProcessor {
1629 index: 3,
1630 memory_region: 3,
1631 efficiency_class: EfficiencyClass::Performance,
1632 }
1633 ];
1634
1635 let platform = new_mock_platform(pal_processors);
1636
1637 let builder = ProcessorSetBuilder::with_internals(
1638 HardwareTrackerClientFacade::default_mock(),
1639 platform.into(),
1640 );
1641 let set = builder
1642 .different_memory_regions()
1643 .efficiency_processors_only()
1644 .take_all()
1645 .unwrap();
1646 assert_eq!(set.len(), 2);
1647 assert!(set.processors().iter().any(|p| p.id() == 0));
1648 assert!(set.processors().iter().any(|p| p.id() == 2));
1649 }
1650
1651 #[test]
1652 fn performance_processors_but_all_efficiency() {
1653 let pal_processors = nonempty![FakeProcessor {
1654 index: 0,
1655 memory_region: 0,
1656 efficiency_class: EfficiencyClass::Efficiency,
1657 }];
1658
1659 let platform = new_mock_platform_with_get_count(pal_processors, 1, 0);
1661
1662 let builder = ProcessorSetBuilder::with_internals(
1663 HardwareTrackerClientFacade::default_mock(),
1664 platform.into(),
1665 );
1666
1667 let set = builder.performance_processors_only().take_all();
1668 assert!(set.is_none(), "No performance processors should be found.");
1669 }
1670
1671 #[test]
1672 fn require_different_single_region() {
1673 let pal_processors = nonempty![
1674 FakeProcessor {
1675 index: 0,
1676 memory_region: 0,
1677 efficiency_class: EfficiencyClass::Efficiency,
1678 },
1679 FakeProcessor {
1680 index: 1,
1681 memory_region: 0,
1682 efficiency_class: EfficiencyClass::Efficiency,
1683 }
1684 ];
1685
1686 let platform = new_mock_platform(pal_processors);
1687
1688 let builder = ProcessorSetBuilder::with_internals(
1689 HardwareTrackerClientFacade::default_mock(),
1690 platform.into(),
1691 );
1692 let set = builder.different_memory_regions().take(nz!(2));
1693 assert!(
1694 set.is_none(),
1695 "Should fail because there's not enough distinct memory regions."
1696 );
1697 }
1698
1699 #[test]
1700 fn prefer_different_memory_regions_take_all() {
1701 let pal_processors = nonempty![
1702 FakeProcessor {
1703 index: 0,
1704 memory_region: 0,
1705 efficiency_class: EfficiencyClass::Efficiency,
1706 },
1707 FakeProcessor {
1708 index: 1,
1709 memory_region: 1,
1710 efficiency_class: EfficiencyClass::Performance,
1711 },
1712 FakeProcessor {
1713 index: 2,
1714 memory_region: 1,
1715 efficiency_class: EfficiencyClass::Efficiency,
1716 }
1717 ];
1718
1719 let platform = new_mock_platform(pal_processors);
1720
1721 let builder = ProcessorSetBuilder::with_internals(
1722 HardwareTrackerClientFacade::default_mock(),
1723 platform.into(),
1724 );
1725 let set = builder
1726 .prefer_different_memory_regions()
1727 .take_all()
1728 .unwrap();
1729 assert_eq!(set.len(), 3);
1730 }
1731
1732 #[test]
1733 fn prefer_different_memory_regions_take_n() {
1734 let pal_processors = nonempty![
1735 FakeProcessor {
1736 index: 0,
1737 memory_region: 0,
1738 efficiency_class: EfficiencyClass::Efficiency,
1739 },
1740 FakeProcessor {
1741 index: 1,
1742 memory_region: 1,
1743 efficiency_class: EfficiencyClass::Performance,
1744 },
1745 FakeProcessor {
1746 index: 2,
1747 memory_region: 1,
1748 efficiency_class: EfficiencyClass::Efficiency,
1749 },
1750 FakeProcessor {
1751 index: 3,
1752 memory_region: 2,
1753 efficiency_class: EfficiencyClass::Performance,
1754 }
1755 ];
1756
1757 let platform = new_mock_platform(pal_processors);
1758
1759 let builder = ProcessorSetBuilder::with_internals(
1760 HardwareTrackerClientFacade::default_mock(),
1761 platform.into(),
1762 );
1763 let set = builder
1764 .prefer_different_memory_regions()
1765 .take(nz!(2))
1766 .unwrap();
1767 assert_eq!(set.len(), 2);
1768 let regions: HashSet<_> = set
1769 .processors()
1770 .iter()
1771 .map(Processor::memory_region_id)
1772 .collect();
1773 assert_eq!(regions.len(), 2);
1774 }
1775
1776 #[test]
1777 fn prefer_same_memory_regions_take_n() {
1778 let pal_processors = nonempty![
1779 FakeProcessor {
1780 index: 0,
1781 memory_region: 0,
1782 efficiency_class: EfficiencyClass::Efficiency,
1783 },
1784 FakeProcessor {
1785 index: 1,
1786 memory_region: 1,
1787 efficiency_class: EfficiencyClass::Performance,
1788 },
1789 FakeProcessor {
1790 index: 2,
1791 memory_region: 1,
1792 efficiency_class: EfficiencyClass::Efficiency,
1793 },
1794 FakeProcessor {
1795 index: 3,
1796 memory_region: 2,
1797 efficiency_class: EfficiencyClass::Performance,
1798 }
1799 ];
1800
1801 let platform = new_mock_platform(pal_processors);
1802
1803 let builder = ProcessorSetBuilder::with_internals(
1804 HardwareTrackerClientFacade::default_mock(),
1805 platform.into(),
1806 );
1807 let set = builder.prefer_same_memory_region().take(nz!(2)).unwrap();
1808 assert_eq!(set.len(), 2);
1809 let regions: HashSet<_> = set
1810 .processors()
1811 .iter()
1812 .map(Processor::memory_region_id)
1813 .collect();
1814 assert_eq!(regions.len(), 1);
1815 }
1816
1817 #[test]
1818 fn prefer_different_memory_regions_take_n_not_enough() {
1819 let pal_processors = nonempty![
1820 FakeProcessor {
1821 index: 0,
1822 memory_region: 0,
1823 efficiency_class: EfficiencyClass::Efficiency,
1824 },
1825 FakeProcessor {
1826 index: 1,
1827 memory_region: 1,
1828 efficiency_class: EfficiencyClass::Performance,
1829 },
1830 FakeProcessor {
1831 index: 2,
1832 memory_region: 1,
1833 efficiency_class: EfficiencyClass::Efficiency,
1834 },
1835 FakeProcessor {
1836 index: 3,
1837 memory_region: 2,
1838 efficiency_class: EfficiencyClass::Performance,
1839 }
1840 ];
1841
1842 let platform = new_mock_platform(pal_processors);
1843
1844 let builder = ProcessorSetBuilder::with_internals(
1845 HardwareTrackerClientFacade::default_mock(),
1846 platform.into(),
1847 );
1848 let set = builder
1849 .prefer_different_memory_regions()
1850 .take(nz!(4))
1851 .unwrap();
1852 assert_eq!(set.len(), 4);
1853 }
1854
1855 #[test]
1856 fn prefer_same_memory_regions_take_n_not_enough() {
1857 let pal_processors = nonempty![
1858 FakeProcessor {
1859 index: 0,
1860 memory_region: 0,
1861 efficiency_class: EfficiencyClass::Efficiency,
1862 },
1863 FakeProcessor {
1864 index: 1,
1865 memory_region: 1,
1866 efficiency_class: EfficiencyClass::Performance,
1867 },
1868 FakeProcessor {
1869 index: 2,
1870 memory_region: 1,
1871 efficiency_class: EfficiencyClass::Efficiency,
1872 },
1873 FakeProcessor {
1874 index: 3,
1875 memory_region: 2,
1876 efficiency_class: EfficiencyClass::Performance,
1877 }
1878 ];
1879
1880 let platform = new_mock_platform(pal_processors);
1881
1882 let builder = ProcessorSetBuilder::with_internals(
1883 HardwareTrackerClientFacade::default_mock(),
1884 platform.into(),
1885 );
1886 let set = builder.prefer_same_memory_region().take(nz!(3)).unwrap();
1887 assert_eq!(set.len(), 3);
1888 let regions: HashSet<_> = set
1889 .processors()
1890 .iter()
1891 .map(Processor::memory_region_id)
1892 .collect();
1893 assert_eq!(
1894 2,
1895 regions.len(),
1896 "should have picked to minimize memory regions (biggest first)"
1897 );
1898 }
1899
1900 #[test]
1901 fn prefer_same_memory_regions_take_n_picks_best_fit() {
1902 let pal_processors = nonempty![
1903 FakeProcessor {
1904 index: 0,
1905 memory_region: 0,
1906 efficiency_class: EfficiencyClass::Efficiency,
1907 },
1908 FakeProcessor {
1909 index: 1,
1910 memory_region: 1,
1911 efficiency_class: EfficiencyClass::Performance,
1912 },
1913 FakeProcessor {
1914 index: 2,
1915 memory_region: 1,
1916 efficiency_class: EfficiencyClass::Efficiency,
1917 },
1918 FakeProcessor {
1919 index: 3,
1920 memory_region: 2,
1921 efficiency_class: EfficiencyClass::Performance,
1922 }
1923 ];
1924
1925 let platform = new_mock_platform(pal_processors);
1926
1927 let builder = ProcessorSetBuilder::with_internals(
1928 HardwareTrackerClientFacade::default_mock(),
1929 platform.into(),
1930 );
1931 let set = builder.prefer_same_memory_region().take(nz!(2)).unwrap();
1932 assert_eq!(set.len(), 2);
1933 let regions: HashSet<_> = set
1934 .processors()
1935 .iter()
1936 .map(Processor::memory_region_id)
1937 .collect();
1938 assert_eq!(
1939 1,
1940 regions.len(),
1941 "should have picked from memory region 1 which can accommodate the preference"
1942 );
1943 }
1944
1945 fn new_mock_platform(processors: NonEmpty<FakeProcessor>) -> MockPlatform {
1946 new_mock_platform_with_get_count(processors, 1, 1)
1947 }
1948
1949 #[allow(
1950 clippy::cast_precision_loss,
1951 reason = "unavoidable f64 conversion but all realistic values likely in safe bounds"
1952 )]
1953 fn new_mock_platform_with_get_count(
1954 processors: NonEmpty<FakeProcessor>,
1955 get_all_processors_count: usize,
1956 get_max_processor_time_count: usize,
1957 ) -> MockPlatform {
1958 let mut platform = MockPlatform::new();
1959 let pal_processors = processors.map(ProcessorFacade::Fake);
1960
1961 platform
1962 .expect_max_processor_time()
1963 .times(get_max_processor_time_count)
1964 .return_const(pal_processors.len() as f64);
1965
1966 platform
1967 .expect_get_all_processors_core()
1968 .times(get_all_processors_count)
1969 .return_const(pal_processors);
1970
1971 platform
1972 }
1973}