]>
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 | ||
26 | #define MAX_THREADS 32 | |
27 | #define SPIN_SECS 6 | |
28 | #define THR_SPINNER_PRI 63 | |
29 | #define THR_MANAGER_PRI 62 | |
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 { | |
41 | pthread_t thread; | |
42 | bool measure_thread; | |
43 | } threads[MAX_THREADS]; | |
44 | ||
45 | static uint64_t | |
46 | nanos_to_abs(uint64_t nanos) | |
47 | { | |
48 | return nanos * timebase_info.denom / timebase_info.numer; | |
49 | } | |
50 | ||
51 | extern char **environ; | |
52 | ||
53 | static void | |
54 | csw_perf_test_init(void) | |
55 | { | |
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); | |
60 | } | |
61 | ||
62 | static void | |
63 | csw_perf_test_cleanup(void) | |
64 | { | |
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); | |
69 | } | |
70 | ||
71 | static pthread_t | |
72 | create_thread(uint32_t thread_id, uint32_t priority, bool fixpri, | |
73 | void *(*start_routine)(void *)) | |
74 | { | |
75 | int rv; | |
76 | pthread_t new_thread; | |
77 | struct sched_param param = { .sched_priority = (int)priority }; | |
78 | pthread_attr_t attr; | |
79 | ||
80 | T_ASSERT_POSIX_ZERO(pthread_attr_init(&attr), "pthread_attr_init"); | |
81 | ||
82 | T_ASSERT_POSIX_ZERO(pthread_attr_setschedparam(&attr, ¶m), | |
83 | "pthread_attr_setschedparam"); | |
84 | ||
85 | if (fixpri) { | |
86 | T_ASSERT_POSIX_ZERO(pthread_attr_setschedpolicy(&attr, SCHED_RR), | |
87 | "pthread_attr_setschedpolicy"); | |
88 | } | |
89 | ||
90 | T_ASSERT_POSIX_ZERO(pthread_create(&new_thread, &attr, start_routine, | |
91 | (void*)(uintptr_t)thread_id), "pthread_create"); | |
92 | ||
93 | T_ASSERT_POSIX_ZERO(pthread_attr_destroy(&attr), "pthread_attr_destroy"); | |
94 | ||
95 | threads[thread_id].thread = new_thread; | |
96 | ||
97 | return new_thread; | |
98 | } | |
99 | ||
100 | /* Spin until a specified number of seconds elapses */ | |
101 | static void | |
102 | spin_for_duration(uint32_t seconds) | |
103 | { | |
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; | |
107 | ||
108 | uint64_t spin_count = 0; | |
109 | ||
110 | while (mach_absolute_time() < timeout && atomic_load_explicit(&keep_going, | |
111 | memory_order_relaxed)) { | |
112 | spin_count++; | |
113 | } | |
114 | } | |
115 | ||
116 | static void * | |
117 | spin_thread(void *arg) | |
118 | { | |
119 | uint32_t thread_id = (uint32_t) arg; | |
120 | char name[30] = ""; | |
121 | ||
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), | |
125 | "semaphore_wait_signal"); | |
126 | spin_for_duration(SPIN_SECS); | |
127 | return NULL; | |
128 | } | |
129 | ||
130 | static void * | |
131 | thread(void *arg) | |
132 | { | |
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 | atomic_store_explicit(&keep_going, 0, memory_order_relaxed); | |
149 | } else { | |
150 | while (atomic_load_explicit(&keep_going, memory_order_relaxed)) { | |
151 | if (thread_switch(THREAD_NULL, SWITCH_OPTION_NONE, 0)) | |
152 | T_ASSERT_FAIL("thread_switch"); | |
153 | } | |
154 | } | |
155 | return NULL; | |
156 | } | |
157 | ||
158 | void check_device_temperature(void) | |
159 | { | |
160 | char buffer[256]; | |
161 | FILE *pipe = popen("powerctrl Factor1", "r"); | |
162 | ||
163 | if (pipe == NULL) { | |
164 | T_FAIL("Failed to check device temperature"); | |
165 | T_END; | |
166 | } | |
167 | ||
168 | fgets(buffer, sizeof(buffer), pipe); | |
169 | ||
170 | if (strncmp(POWERCTRL_SUCCESS_STR, buffer, strlen(POWERCTRL_SUCCESS_STR))) { | |
171 | T_PERF("temperature", 0.0, "factor", "device temperature"); | |
172 | } else { | |
173 | T_PASS("Device temperature check pass"); | |
174 | T_PERF("temperature", 1.0, "factor", "device temperature"); | |
175 | } | |
176 | pclose(pipe); | |
177 | } | |
178 | ||
179 | void record_perfcontrol_stats(const char *sysctlname, const char *units, const char *info) | |
180 | { | |
181 | int data = 0; | |
182 | size_t data_size = sizeof(data); | |
183 | T_ASSERT_POSIX_ZERO(sysctlbyname(sysctlname, | |
184 | &data, &data_size, NULL, 0), | |
185 | "%s", sysctlname); | |
186 | T_PERF(info, data, units, info); | |
187 | } | |
188 | ||
189 | ||
190 | T_GLOBAL_META(T_META_NAMESPACE("xnu.scheduler")); | |
191 | ||
192 | /* Disable the test on MacOS for now */ | |
193 | T_DECL(perf_csw, "context switch performance", T_META_TYPE_PERF, T_META_CHECK_LEAKS(NO), T_META_ASROOT(YES)) | |
194 | { | |
195 | ||
196 | #if !CONFIG_EMBEDDED | |
197 | T_SKIP("Not supported on MacOS"); | |
198 | return; | |
199 | #endif /* CONFIG_EMBEDDED */ | |
200 | check_device_temperature(); | |
201 | ||
202 | T_ATEND(csw_perf_test_cleanup); | |
203 | ||
204 | csw_perf_test_init(); | |
205 | pthread_setname_np("main thread"); | |
206 | ||
207 | T_ASSERT_MACH_SUCCESS(mach_timebase_info(&timebase_info), "mach_timebase_info"); | |
208 | ||
209 | struct sched_param param = {.sched_priority = 48}; | |
210 | ||
211 | T_ASSERT_POSIX_ZERO(pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m), | |
212 | "pthread_setschedparam"); | |
213 | ||
214 | T_ASSERT_MACH_SUCCESS(semaphore_create(mach_task_self(), &semaphore, | |
215 | SYNC_POLICY_FIFO, 0), "semaphore_create"); | |
216 | ||
217 | T_ASSERT_MACH_SUCCESS(semaphore_create(mach_task_self(), &worker_sem, | |
218 | SYNC_POLICY_FIFO, 0), "semaphore_create"); | |
219 | ||
220 | size_t ncpu_size = sizeof(g_numcpus); | |
221 | T_ASSERT_POSIX_ZERO(sysctlbyname("hw.ncpu", &g_numcpus, &ncpu_size, NULL, 0), | |
222 | "sysctlbyname hw.ncpu"); | |
223 | ||
224 | printf("hw.ncpu: %d\n", g_numcpus); | |
225 | uint32_t n_spinners = g_numcpus - 1; | |
226 | ||
227 | int mt_supported = 0; | |
228 | size_t mt_supported_size = sizeof(mt_supported); | |
229 | T_ASSERT_POSIX_ZERO(sysctlbyname("kern.monotonic.supported", &mt_supported, | |
230 | &mt_supported_size, NULL, 0), "sysctlbyname kern.monotonic.supported"); | |
231 | ||
232 | for (uint32_t thread_id = 0; thread_id < n_spinners; thread_id++) { | |
233 | threads[thread_id].thread = create_thread(thread_id, THR_SPINNER_PRI, | |
234 | true, &spin_thread); | |
235 | } | |
236 | ||
237 | s = dt_stat_time_create("context switch time"); | |
238 | ||
239 | create_thread(n_spinners, THR_MANAGER_PRI, true, &thread); | |
240 | threads[n_spinners].measure_thread = true; | |
241 | create_thread(n_spinners + 1, THR_MANAGER_PRI, true, &thread); | |
242 | ||
243 | /* Allow the context switch threads to get into sem_wait() */ | |
244 | for (uint32_t thread_id = 0; thread_id < n_spinners + 2; thread_id++) { | |
245 | T_ASSERT_MACH_SUCCESS(semaphore_wait(worker_sem), "semaphore_wait"); | |
246 | } | |
247 | ||
248 | int enable_callout_stats = 1; | |
249 | size_t enable_size = sizeof(enable_callout_stats); | |
250 | ||
251 | if (mt_supported) { | |
252 | /* Enable callout stat collection */ | |
253 | T_ASSERT_POSIX_ZERO(sysctlbyname("kern.perfcontrol_callout.stats_enabled", | |
254 | NULL, 0, &enable_callout_stats, enable_size), | |
255 | "sysctlbyname kern.perfcontrol_callout.stats_enabled"); | |
256 | } | |
257 | ||
258 | T_ASSERT_MACH_SUCCESS(semaphore_signal_all(semaphore), "semaphore_signal"); | |
259 | ||
260 | ||
261 | for (uint32_t thread_id = 0; thread_id < n_spinners + 2; thread_id++) { | |
262 | T_ASSERT_POSIX_ZERO(pthread_join(threads[thread_id].thread, NULL), | |
263 | "pthread_join %d", thread_id); | |
264 | } | |
265 | ||
266 | if (mt_supported) { | |
267 | record_perfcontrol_stats("kern.perfcontrol_callout.oncore_instr", | |
268 | "instructions", "oncore.instructions"); | |
269 | record_perfcontrol_stats("kern.perfcontrol_callout.offcore_instr", | |
270 | "instructions", "offcore.instructions"); | |
271 | record_perfcontrol_stats("kern.perfcontrol_callout.oncore_cycles", | |
272 | "cycles", "oncore.cycles"); | |
273 | record_perfcontrol_stats("kern.perfcontrol_callout.offcore_cycles", | |
274 | "cycles", "offcore.cycles"); | |
275 | ||
276 | /* Disable callout stat collection */ | |
277 | enable_callout_stats = 0; | |
278 | T_ASSERT_POSIX_ZERO(sysctlbyname("kern.perfcontrol_callout.stats_enabled", | |
279 | NULL, 0, &enable_callout_stats, enable_size), | |
280 | "sysctlbyname kern.perfcontrol_callout.stats_enabled"); | |
281 | } | |
282 | ||
283 | check_device_temperature(); | |
284 | dt_stat_finalize(s); | |
285 | } |