xref: /xnu-11417.140.69/tests/sched/edge_migration.c (revision 43a90889846e00bfb5cf1d255cdc0a701a1e05a4)
1 // Copyright (c) 2024 Apple Inc.  All rights reserved.
2 
3 #include "sched_test_harness/sched_policy_darwintest.h"
4 #include "sched_test_harness/sched_edge_harness.h"
5 
6 T_GLOBAL_META(T_META_NAMESPACE("xnu.scheduler"),
7     T_META_RADAR_COMPONENT_NAME("xnu"),
8     T_META_RADAR_COMPONENT_VERSION("scheduler"),
9     T_META_RUN_CONCURRENTLY(true),
10     T_META_OWNER("emily_peterson"));
11 
12 SCHED_POLICY_T_DECL(migration_cluster_bound,
13     "Verify that cluster-bound threads always choose the bound "
14     "cluster except when its derecommended")
15 {
16 	int ret;
17 	init_migration_harness(dual_die);
18 	struct thread_group *tg = create_tg(0);
19 	test_thread_t threads[dual_die.num_psets];
20 	int idle_load = 0;
21 	int low_load = 100000;
22 	int high_load = 10000000;
23 	for (int i = 0; i < dual_die.num_psets; i++) {
24 		threads[i] = create_thread(TH_BUCKET_SHARE_DF, tg, root_bucket_to_highest_pri[TH_BUCKET_SHARE_DF]);
25 		set_thread_cluster_bound(threads[i], i);
26 		set_pset_load_avg(i, TH_BUCKET_SHARE_DF, low_load);
27 	}
28 	for (int i = 0; i < dual_die.num_psets; i++) {
29 		set_current_processor(cluster_id_to_cpu_id(i));
30 		for (int j = 0; j < dual_die.num_psets; j++) {
31 			/* Add extra load to the bound cluster, so we're definitely not just idle short-circuiting */
32 			set_pset_load_avg(j, TH_BUCKET_SHARE_DF, high_load);
33 			ret = choose_pset_for_thread_expect(threads[j], j);
34 			T_QUIET; T_EXPECT_TRUE(ret, "Expecting the bound cluster");
35 			set_pset_load_avg(j, TH_BUCKET_SHARE_DF, low_load);
36 		}
37 	}
38 	SCHED_POLICY_PASS("Cluster bound chooses bound cluster");
39 	/* Derecommend the bound cluster */
40 	for (int i = 0; i < dual_die.num_psets; i++) {
41 		set_pset_derecommended(i);
42 		int replacement_pset = -1;
43 		for (int j = 0; j < dual_die.num_psets; j++) {
44 			/* Find the first homogenous cluster and mark it as idle so we choose it */
45 			if ((i != j) && (dual_die.psets[i].cpu_type == dual_die.psets[j].cpu_type)) {
46 				replacement_pset = j;
47 				set_pset_load_avg(replacement_pset, TH_BUCKET_SHARE_DF, idle_load);
48 				break;
49 			}
50 		}
51 		ret = choose_pset_for_thread_expect(threads[i], replacement_pset);
52 		T_QUIET; T_EXPECT_TRUE(ret, "Expecting the idle pset when the bound cluster is derecommended");
53 		/* Restore pset conditions */
54 		set_pset_recommended(i);
55 		set_pset_load_avg(replacement_pset, TH_BUCKET_SHARE_DF, low_load);
56 	}
57 	SCHED_POLICY_PASS("Cluster binding is soft");
58 }
59 
60 SCHED_POLICY_T_DECL(migration_should_yield,
61     "Verify that we only yield if there's a \"good enough\" thread elsewhere "
62     "to switch to")
63 {
64 	int ret;
65 	init_migration_harness(basic_amp);
66 	struct thread_group *tg = create_tg(0);
67 	test_thread_t background = create_thread(TH_BUCKET_SHARE_BG, tg, root_bucket_to_highest_pri[TH_BUCKET_SHARE_BG]);
68 	test_thread_t yielder = create_thread(TH_BUCKET_SHARE_DF, tg, root_bucket_to_highest_pri[TH_BUCKET_SHARE_DF]);
69 	cpu_set_thread_current(0, yielder);
70 	ret = cpu_check_should_yield(0, false);
71 	T_QUIET; T_EXPECT_TRUE(ret, "No thread present to yield to");
72 	enqueue_thread(cluster_target(0), background);
73 	ret = cpu_check_should_yield(0, true);
74 	T_QUIET; T_EXPECT_TRUE(ret, "Should yield to a low priority thread on the current runqueue");
75 	SCHED_POLICY_PASS("Basic yield behavior on single pset");
76 
77 	ret = dequeue_thread_expect(cluster_target(0), background);
78 	T_QUIET; T_EXPECT_TRUE(ret, "Only background thread in runqueue");
79 	cpu_set_thread_current(0, yielder); /* Reset current thread */
80 	enqueue_thread(cluster_target(1), background);
81 	ret = cpu_check_should_yield(0, true);
82 	T_QUIET; T_EXPECT_TRUE(ret, "Should yield in order to steal thread");
83 	ret = dequeue_thread_expect(cluster_target(1), background);
84 	T_QUIET; T_EXPECT_TRUE(ret, "Only background thread in runqueue");
85 	cpu_set_thread_current(cluster_id_to_cpu_id(1), background);
86 	ret = cpu_check_should_yield(cluster_id_to_cpu_id(1), false);
87 	T_QUIET; T_EXPECT_TRUE(ret, "Should not yield in order to rebalance (presumed) native thread");
88 	SCHED_POLICY_PASS("Thread yields in order to steal from other psets");
89 }
90 
91 SCHED_POLICY_T_DECL(migration_ipi_policy,
92     "Verify we send the right type of IPI in different cross-core preemption scenarios")
93 {
94 	int ret;
95 	init_migration_harness(dual_die);
96 	struct thread_group *tg = create_tg(0);
97 	thread_t thread = create_thread(TH_BUCKET_SHARE_DF, tg, root_bucket_to_highest_pri[TH_BUCKET_SHARE_DF]);
98 	int dst_pcore = 3;
99 	int src_pcore = 0;
100 
101 	set_current_processor(src_pcore);
102 	cpu_send_ipi_for_thread(dst_pcore, thread, TEST_IPI_EVENT_PREEMPT);
103 	ret = ipi_expect(dst_pcore, TEST_IPI_IDLE);
104 	T_QUIET; T_EXPECT_TRUE(ret, "Idle CPU");
105 
106 	thread_t core_busy = create_thread(TH_BUCKET_SHARE_DF, tg, root_bucket_to_highest_pri[TH_BUCKET_SHARE_DF]);
107 	cpu_set_thread_current(dst_pcore, core_busy);
108 	set_current_processor(src_pcore);
109 	cpu_send_ipi_for_thread(dst_pcore, thread, TEST_IPI_EVENT_PREEMPT);
110 	ret = ipi_expect(dst_pcore, TEST_IPI_IMMEDIATE);
111 	T_QUIET; T_EXPECT_TRUE(ret, "Should immediate IPI to preempt on P-core");
112 	SCHED_POLICY_PASS("Immediate IPIs to preempt P-cores");
113 
114 	int dst_ecore = 13;
115 	int ecluster_id = 5;
116 	set_tg_sched_bucket_preferred_pset(tg, TH_BUCKET_SHARE_DF, ecluster_id);
117 	set_current_processor(src_pcore);
118 	cpu_send_ipi_for_thread(dst_ecore, thread, TEST_IPI_EVENT_PREEMPT);
119 	ret = ipi_expect(dst_ecore, TEST_IPI_IDLE);
120 	T_QUIET; T_EXPECT_TRUE(ret, "Idle CPU");
121 
122 	cpu_set_thread_current(dst_ecore, core_busy);
123 	set_current_processor(src_pcore);
124 	cpu_send_ipi_for_thread(dst_ecore, thread, TEST_IPI_EVENT_PREEMPT);
125 	ret = ipi_expect(dst_ecore, TEST_IPI_IMMEDIATE);
126 	T_QUIET; T_EXPECT_TRUE(ret, "Should immediate IPI to preempt for E->E");
127 	SCHED_POLICY_PASS("Immediate IPIs to cluster homogeneous with preferred");
128 }
129 
130 SCHED_POLICY_T_DECL(migration_max_parallelism,
131     "Verify we report expected values for recommended width of parallel workloads")
132 {
133 	int ret;
134 	init_migration_harness(dual_die);
135 	uint32_t num_pclusters = 4;
136 	uint32_t num_pcores = 4 * num_pclusters;
137 	uint32_t num_eclusters = 2;
138 	uint32_t num_ecores = 2 * num_eclusters;
139 	for (thread_qos_t qos = THREAD_QOS_UNSPECIFIED; qos < THREAD_QOS_LAST; qos++) {
140 		for (int shared_rsrc = 0; shared_rsrc < 2; shared_rsrc++) {
141 			for (int rt = 0; rt < 2; rt++) {
142 				uint64_t options = 0;
143 				uint32_t expected_width = 0;
144 				if (shared_rsrc) {
145 					options |= QOS_PARALLELISM_CLUSTER_SHARED_RESOURCE;
146 				}
147 				if (rt) {
148 					options |= QOS_PARALLELISM_REALTIME;
149 					/* Recommend P-width */
150 					expected_width = shared_rsrc ? num_pclusters : num_pcores;
151 				} else if (qos == THREAD_QOS_BACKGROUND || qos == THREAD_QOS_MAINTENANCE) {
152 					/* Recommend E-width */
153 					expected_width = shared_rsrc ? num_eclusters : num_ecores;
154 				} else {
155 					/* Recommend full width */
156 					expected_width = shared_rsrc ? (num_eclusters + num_pclusters) : (num_pcores + num_ecores);
157 				}
158 				ret = max_parallelism_expect(qos, options, expected_width);
159 				T_QUIET; T_EXPECT_TRUE(ret, "Unexpected width for QoS %d shared_rsrc %d RT %d",
160 				    qos, shared_rsrc, rt);
161 			}
162 		}
163 	}
164 	SCHED_POLICY_PASS("Correct recommended parallel width for all configurations");
165 }
166