]>
Commit | Line | Data |
---|---|---|
5ba3f43e A |
1 | #include <unistd.h> |
2 | #include <stdio.h> | |
3 | #include <stdlib.h> | |
4 | #include <pthread.h> | |
5 | #include <errno.h> | |
6 | #include <err.h> | |
7 | #include <string.h> | |
8 | #include <assert.h> | |
9 | #include <sysexits.h> | |
10 | #include <getopt.h> | |
11 | #include <spawn.h> | |
12 | #include <stdbool.h> | |
13 | #include <sys/sysctl.h> | |
14 | #include <mach/mach_time.h> | |
15 | #include <mach/mach.h> | |
16 | #include <mach/semaphore.h> | |
17 | #include <TargetConditionals.h> | |
18 | ||
19 | #ifdef T_NAMESPACE | |
20 | #undef T_NAMESPACE | |
21 | #endif | |
22 | ||
23 | #include <darwintest.h> | |
24 | #include <stdatomic.h> | |
25 | ||
0a7de745 A |
26 | #define MAX_THREADS 32 |
27 | #define SPIN_SECS 6 | |
28 | #define THR_SPINNER_PRI 63 | |
29 | #define THR_MANAGER_PRI 62 | |
5ba3f43e A |
30 | #define WARMUP_ITERATIONS 100 |
31 | #define POWERCTRL_SUCCESS_STR "Factor1: 1.000000" | |
32 | ||
33 | static mach_timebase_info_data_t timebase_info; | |
34 | static semaphore_t semaphore; | |
35 | static semaphore_t worker_sem; | |
36 | static uint32_t g_numcpus; | |
37 | static _Atomic uint32_t keep_going = 1; | |
38 | static dt_stat_time_t s; | |
39 | ||
40 | static struct { | |
0a7de745 A |
41 | pthread_t thread; |
42 | bool measure_thread; | |
5ba3f43e A |
43 | } threads[MAX_THREADS]; |
44 | ||
0a7de745 A |
45 | static uint64_t |
46 | nanos_to_abs(uint64_t nanos) | |
47 | { | |
48 | return nanos * timebase_info.denom / timebase_info.numer; | |
5ba3f43e A |
49 | } |
50 | ||
51 | extern char **environ; | |
52 | ||
53 | static void | |
54 | csw_perf_test_init(void) | |
55 | { | |
0a7de745 A |
56 | int spawn_ret, pid; |
57 | char *const clpcctrl_args[] = {"/usr/local/bin/clpcctrl", "-f", "5000", NULL}; | |
58 | spawn_ret = posix_spawn(&pid, clpcctrl_args[0], NULL, NULL, clpcctrl_args, environ); | |
59 | waitpid(pid, &spawn_ret, 0); | |
5ba3f43e A |
60 | } |
61 | ||
62 | static void | |
63 | csw_perf_test_cleanup(void) | |
64 | { | |
0a7de745 A |
65 | int spawn_ret, pid; |
66 | char *const clpcctrl_args[] = {"/usr/local/bin/clpcctrl", "-d", NULL}; | |
67 | spawn_ret = posix_spawn(&pid, clpcctrl_args[0], NULL, NULL, clpcctrl_args, environ); | |
68 | waitpid(pid, &spawn_ret, 0); | |
5ba3f43e A |
69 | } |
70 | ||
71 | static pthread_t | |
0a7de745 A |
72 | create_thread(uint32_t thread_id, uint32_t priority, bool fixpri, |
73 | void *(*start_routine)(void *)) | |
5ba3f43e | 74 | { |
0a7de745 A |
75 | int rv; |
76 | pthread_t new_thread; | |
77 | struct sched_param param = { .sched_priority = (int)priority }; | |
78 | pthread_attr_t attr; | |
5ba3f43e | 79 | |
0a7de745 | 80 | T_ASSERT_POSIX_ZERO(pthread_attr_init(&attr), "pthread_attr_init"); |
5ba3f43e | 81 | |
0a7de745 A |
82 | T_ASSERT_POSIX_ZERO(pthread_attr_setschedparam(&attr, ¶m), |
83 | "pthread_attr_setschedparam"); | |
5ba3f43e | 84 | |
0a7de745 A |
85 | if (fixpri) { |
86 | T_ASSERT_POSIX_ZERO(pthread_attr_setschedpolicy(&attr, SCHED_RR), | |
87 | "pthread_attr_setschedpolicy"); | |
88 | } | |
5ba3f43e | 89 | |
0a7de745 A |
90 | T_ASSERT_POSIX_ZERO(pthread_create(&new_thread, &attr, start_routine, |
91 | (void*)(uintptr_t)thread_id), "pthread_create"); | |
5ba3f43e | 92 | |
0a7de745 | 93 | T_ASSERT_POSIX_ZERO(pthread_attr_destroy(&attr), "pthread_attr_destroy"); |
5ba3f43e | 94 | |
0a7de745 | 95 | threads[thread_id].thread = new_thread; |
5ba3f43e | 96 | |
0a7de745 | 97 | return new_thread; |
5ba3f43e A |
98 | } |
99 | ||
100 | /* Spin until a specified number of seconds elapses */ | |
101 | static void | |
102 | spin_for_duration(uint32_t seconds) | |
103 | { | |
0a7de745 A |
104 | uint64_t duration = nanos_to_abs((uint64_t)seconds * NSEC_PER_SEC); |
105 | uint64_t current_time = mach_absolute_time(); | |
106 | uint64_t timeout = duration + current_time; | |
5ba3f43e | 107 | |
0a7de745 | 108 | uint64_t spin_count = 0; |
5ba3f43e | 109 | |
0a7de745 A |
110 | while (mach_absolute_time() < timeout && atomic_load_explicit(&keep_going, |
111 | memory_order_relaxed)) { | |
112 | spin_count++; | |
113 | } | |
5ba3f43e A |
114 | } |
115 | ||
116 | static void * | |
117 | spin_thread(void *arg) | |
118 | { | |
0a7de745 A |
119 | uint32_t thread_id = (uint32_t) arg; |
120 | char name[30] = ""; | |
5ba3f43e | 121 | |
0a7de745 A |
122 | snprintf(name, sizeof(name), "spin thread %2d", thread_id); |
123 | pthread_setname_np(name); | |
124 | T_ASSERT_MACH_SUCCESS(semaphore_wait_signal(semaphore, worker_sem), | |
5ba3f43e | 125 | "semaphore_wait_signal"); |
0a7de745 A |
126 | spin_for_duration(SPIN_SECS); |
127 | return NULL; | |
5ba3f43e A |
128 | } |
129 | ||
130 | static void * | |
131 | thread(void *arg) | |
132 | { | |
0a7de745 A |
133 | uint32_t thread_id = (uint32_t) arg; |
134 | char name[30] = ""; | |
135 | ||
136 | snprintf(name, sizeof(name), "thread %2d", thread_id); | |
137 | pthread_setname_np(name); | |
138 | T_ASSERT_MACH_SUCCESS(semaphore_wait_signal(semaphore, worker_sem), "semaphore_wait"); | |
139 | ||
140 | if (threads[thread_id].measure_thread) { | |
141 | for (int i = 0; i < WARMUP_ITERATIONS; i++) { | |
142 | thread_switch(THREAD_NULL, SWITCH_OPTION_NONE, 0); | |
143 | } | |
144 | T_STAT_MEASURE_LOOP(s) { | |
145 | if (thread_switch(THREAD_NULL, SWITCH_OPTION_NONE, 0)) { | |
146 | T_ASSERT_FAIL("thread_switch"); | |
147 | } | |
148 | } | |
149 | atomic_store_explicit(&keep_going, 0, memory_order_relaxed); | |
150 | } else { | |
151 | while (atomic_load_explicit(&keep_going, memory_order_relaxed)) { | |
152 | if (thread_switch(THREAD_NULL, SWITCH_OPTION_NONE, 0)) { | |
153 | T_ASSERT_FAIL("thread_switch"); | |
154 | } | |
155 | } | |
156 | } | |
157 | return NULL; | |
5ba3f43e A |
158 | } |
159 | ||
0a7de745 A |
160 | void |
161 | check_device_temperature(void) | |
5ba3f43e | 162 | { |
0a7de745 A |
163 | char buffer[256]; |
164 | FILE *pipe = popen("powerctrl Factor1", "r"); | |
165 | ||
166 | if (pipe == NULL) { | |
167 | T_FAIL("Failed to check device temperature"); | |
168 | T_END; | |
169 | } | |
170 | ||
171 | fgets(buffer, sizeof(buffer), pipe); | |
172 | ||
173 | if (strncmp(POWERCTRL_SUCCESS_STR, buffer, strlen(POWERCTRL_SUCCESS_STR))) { | |
174 | T_PERF("temperature", 0.0, "factor", "device temperature"); | |
175 | } else { | |
176 | T_PASS("Device temperature check pass"); | |
177 | T_PERF("temperature", 1.0, "factor", "device temperature"); | |
178 | } | |
179 | pclose(pipe); | |
5ba3f43e A |
180 | } |
181 | ||
0a7de745 A |
182 | void |
183 | record_perfcontrol_stats(const char *sysctlname, const char *units, const char *info) | |
5ba3f43e | 184 | { |
0a7de745 A |
185 | int data = 0; |
186 | size_t data_size = sizeof(data); | |
187 | T_ASSERT_POSIX_ZERO(sysctlbyname(sysctlname, | |
188 | &data, &data_size, NULL, 0), | |
5ba3f43e | 189 | "%s", sysctlname); |
0a7de745 | 190 | T_PERF(info, data, units, info); |
5ba3f43e A |
191 | } |
192 | ||
193 | ||
194 | T_GLOBAL_META(T_META_NAMESPACE("xnu.scheduler")); | |
195 | ||
196 | /* Disable the test on MacOS for now */ | |
d9a64523 | 197 | T_DECL(perf_csw, "context switch performance", T_META_TAG_PERF, T_META_CHECK_LEAKS(false), T_META_ASROOT(true)) |
5ba3f43e | 198 | { |
5ba3f43e | 199 | #if !CONFIG_EMBEDDED |
0a7de745 A |
200 | T_SKIP("Not supported on MacOS"); |
201 | return; | |
5ba3f43e | 202 | #endif /* CONFIG_EMBEDDED */ |
0a7de745 A |
203 | check_device_temperature(); |
204 | ||
205 | T_ATEND(csw_perf_test_cleanup); | |
206 | ||
207 | csw_perf_test_init(); | |
208 | pthread_setname_np("main thread"); | |
209 | ||
210 | T_ASSERT_MACH_SUCCESS(mach_timebase_info(&timebase_info), "mach_timebase_info"); | |
211 | ||
212 | struct sched_param param = {.sched_priority = 48}; | |
213 | ||
214 | T_ASSERT_POSIX_ZERO(pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m), | |
215 | "pthread_setschedparam"); | |
216 | ||
217 | T_ASSERT_MACH_SUCCESS(semaphore_create(mach_task_self(), &semaphore, | |
218 | SYNC_POLICY_FIFO, 0), "semaphore_create"); | |
219 | ||
220 | T_ASSERT_MACH_SUCCESS(semaphore_create(mach_task_self(), &worker_sem, | |
221 | SYNC_POLICY_FIFO, 0), "semaphore_create"); | |
222 | ||
223 | size_t ncpu_size = sizeof(g_numcpus); | |
224 | T_ASSERT_POSIX_ZERO(sysctlbyname("hw.ncpu", &g_numcpus, &ncpu_size, NULL, 0), | |
225 | "sysctlbyname hw.ncpu"); | |
226 | ||
227 | printf("hw.ncpu: %d\n", g_numcpus); | |
228 | uint32_t n_spinners = g_numcpus - 1; | |
229 | ||
230 | int mt_supported = 0; | |
231 | size_t mt_supported_size = sizeof(mt_supported); | |
232 | T_ASSERT_POSIX_ZERO(sysctlbyname("kern.monotonic.supported", &mt_supported, | |
233 | &mt_supported_size, NULL, 0), "sysctlbyname kern.monotonic.supported"); | |
234 | ||
235 | for (uint32_t thread_id = 0; thread_id < n_spinners; thread_id++) { | |
236 | threads[thread_id].thread = create_thread(thread_id, THR_SPINNER_PRI, | |
237 | true, &spin_thread); | |
238 | } | |
239 | ||
240 | s = dt_stat_time_create("context switch time"); | |
241 | ||
242 | create_thread(n_spinners, THR_MANAGER_PRI, true, &thread); | |
243 | threads[n_spinners].measure_thread = true; | |
244 | create_thread(n_spinners + 1, THR_MANAGER_PRI, true, &thread); | |
245 | ||
246 | /* Allow the context switch threads to get into sem_wait() */ | |
247 | for (uint32_t thread_id = 0; thread_id < n_spinners + 2; thread_id++) { | |
248 | T_ASSERT_MACH_SUCCESS(semaphore_wait(worker_sem), "semaphore_wait"); | |
249 | } | |
250 | ||
251 | int enable_callout_stats = 1; | |
252 | size_t enable_size = sizeof(enable_callout_stats); | |
253 | ||
254 | if (mt_supported) { | |
255 | /* Enable callout stat collection */ | |
256 | T_ASSERT_POSIX_ZERO(sysctlbyname("kern.perfcontrol_callout.stats_enabled", | |
257 | NULL, 0, &enable_callout_stats, enable_size), | |
258 | "sysctlbyname kern.perfcontrol_callout.stats_enabled"); | |
259 | } | |
260 | ||
261 | T_ASSERT_MACH_SUCCESS(semaphore_signal_all(semaphore), "semaphore_signal"); | |
262 | ||
263 | ||
264 | for (uint32_t thread_id = 0; thread_id < n_spinners + 2; thread_id++) { | |
265 | T_ASSERT_POSIX_ZERO(pthread_join(threads[thread_id].thread, NULL), | |
266 | "pthread_join %d", thread_id); | |
267 | } | |
268 | ||
269 | if (mt_supported) { | |
270 | record_perfcontrol_stats("kern.perfcontrol_callout.oncore_instr", | |
271 | "instructions", "oncore.instructions"); | |
272 | record_perfcontrol_stats("kern.perfcontrol_callout.offcore_instr", | |
273 | "instructions", "offcore.instructions"); | |
274 | record_perfcontrol_stats("kern.perfcontrol_callout.oncore_cycles", | |
275 | "cycles", "oncore.cycles"); | |
276 | record_perfcontrol_stats("kern.perfcontrol_callout.offcore_cycles", | |
277 | "cycles", "offcore.cycles"); | |
278 | ||
279 | /* Disable callout stat collection */ | |
280 | enable_callout_stats = 0; | |
281 | T_ASSERT_POSIX_ZERO(sysctlbyname("kern.perfcontrol_callout.stats_enabled", | |
282 | NULL, 0, &enable_callout_stats, enable_size), | |
283 | "sysctlbyname kern.perfcontrol_callout.stats_enabled"); | |
284 | } | |
285 | ||
286 | check_device_temperature(); | |
287 | dt_stat_finalize(s); | |
5ba3f43e | 288 | } |