]>
Commit | Line | Data |
---|---|---|
39037602 A |
1 | /* This program tests that kThreadIdleWorker is being set properly, so |
2 | * that idle and active threads can be appropriately identified. | |
3 | */ | |
4 | ||
5 | #include <darwintest.h> | |
6 | #include <dispatch/dispatch.h> | |
7 | #include <kdd.h> | |
8 | #include <kern/kcdata.h> | |
9 | #include <kern/debug.h> | |
10 | #include <mach/mach_init.h> | |
11 | #include <mach/mach_traps.h> | |
12 | #include <mach/semaphore.h> | |
13 | #include <mach/task.h> | |
14 | #include <pthread.h> | |
15 | #include <sys/stackshot.h> | |
16 | #include <stdlib.h> | |
17 | #include <unistd.h> | |
18 | ||
19 | #include <Foundation/Foundation.h> | |
20 | ||
21 | #define NUMRETRIES 5 // number of times to retry a stackshot | |
22 | #define NUMENQUEUES 16 // number of blocking jobs to enqueue | |
23 | #define NUMTHREADS (NUMENQUEUES + 2) // total number of threads (including numenqueues) | |
24 | ||
25 | volatile static int spin_threads = 1; | |
26 | ||
27 | static void * | |
28 | take_stackshot(uint32_t extra_flags, uint64_t since_timestamp) | |
29 | { | |
30 | void * stackshot; | |
31 | int ret, retries; | |
32 | uint32_t stackshot_flags = STACKSHOT_SAVE_LOADINFO | | |
33 | STACKSHOT_GET_GLOBAL_MEM_STATS | | |
34 | STACKSHOT_SAVE_IMP_DONATION_PIDS | | |
35 | STACKSHOT_KCDATA_FORMAT; | |
36 | ||
37 | if (since_timestamp != 0) | |
38 | stackshot_flags |= STACKSHOT_COLLECT_DELTA_SNAPSHOT; | |
39 | ||
40 | stackshot_flags |= extra_flags; | |
41 | ||
42 | stackshot = stackshot_config_create(); | |
43 | T_ASSERT_NOTNULL(stackshot, "Allocating stackshot config"); | |
44 | ||
45 | ret = stackshot_config_set_flags(stackshot, stackshot_flags); | |
46 | T_ASSERT_POSIX_ZERO(ret, "Setting flags on stackshot config"); | |
47 | ||
48 | ret = stackshot_config_set_pid(stackshot, getpid()); | |
49 | T_ASSERT_POSIX_ZERO(ret, "Setting target pid on stackshot config"); | |
50 | ||
51 | if (since_timestamp != 0) { | |
52 | ret = stackshot_config_set_delta_timestamp(stackshot, since_timestamp); | |
53 | T_ASSERT_POSIX_ZERO(ret, "Setting prev snapshot time on stackshot config"); | |
54 | } | |
55 | ||
56 | for (retries = NUMRETRIES; retries > 0; retries--) { | |
57 | ret = stackshot_capture_with_config(stackshot); | |
58 | T_ASSERT_TRUE(ret == 0 || ret == EBUSY || ret == ETIMEDOUT, "Attempting to take stackshot (error %d)...", ret); | |
59 | if (retries == 0 && (ret == EBUSY || ret == ETIMEDOUT)) | |
60 | T_ASSERT_FAIL("Failed to take stackshot after %d retries: %s", ret, strerror(ret)); | |
61 | if (ret == 0) | |
62 | break; | |
63 | } | |
64 | return stackshot; | |
65 | } | |
66 | ||
67 | static uint64_t get_stackshot_timestamp(void * stackshot) | |
68 | { | |
69 | kcdata_iter_t iter; | |
70 | void * buf; | |
71 | uint64_t default_time = 0; | |
72 | uint32_t t, buflen; | |
73 | ||
74 | buf = stackshot_config_get_stackshot_buffer(stackshot); | |
75 | T_ASSERT_NOTNULL(buf, "Getting stackshot buffer"); | |
76 | buflen = stackshot_config_get_stackshot_size(stackshot); | |
77 | ||
78 | iter = kcdata_iter(buf, buflen); | |
79 | t = kcdata_iter_type(iter); | |
80 | ||
81 | T_ASSERT_TRUE(t == KCDATA_BUFFER_BEGIN_STACKSHOT || t == KCDATA_BUFFER_BEGIN_DELTA_STACKSHOT, | |
82 | "Making sure stackshot data begins with \"begin\" flag"); | |
83 | T_ASSERT_TRUE(kcdata_iter_valid(iter = kcdata_iter_find_type(iter, KCDATA_TYPE_MACH_ABSOLUTE_TIME)), | |
84 | "Getting stackshot timestamp"); | |
85 | default_time = *(uint64_t *)kcdata_iter_payload(iter); | |
86 | return default_time; | |
87 | } | |
88 | ||
89 | static void | |
90 | get_thread_statuses(void * stackshot, int * num_idles, int * num_nonidles) | |
91 | { | |
92 | void *buf; | |
93 | uint32_t t, buflen; | |
94 | uint64_t thread_snap_flags; | |
95 | NSError *error = nil; | |
96 | NSMutableDictionary *parsed_container, *parsed_threads; | |
97 | ||
98 | *num_idles = 0; | |
99 | *num_nonidles = 0; | |
100 | ||
101 | buf = stackshot_config_get_stackshot_buffer(stackshot); | |
102 | T_ASSERT_NOTNULL(buf, "Getting stackshot buffer"); | |
103 | buflen = stackshot_config_get_stackshot_size(stackshot); | |
104 | ||
105 | kcdata_iter_t iter = kcdata_iter(buf, buflen); | |
106 | T_ASSERT_TRUE(kcdata_iter_type(iter) == KCDATA_BUFFER_BEGIN_STACKSHOT || | |
107 | kcdata_iter_type(iter) == KCDATA_BUFFER_BEGIN_DELTA_STACKSHOT, | |
108 | "Checking start of stackshot buffer"); | |
109 | ||
110 | iter = kcdata_iter_next(iter); | |
111 | KCDATA_ITER_FOREACH(iter) | |
112 | { | |
113 | t = kcdata_iter_type(iter); | |
114 | ||
115 | if (t != KCDATA_TYPE_CONTAINER_BEGIN) { | |
116 | continue; | |
117 | } | |
118 | ||
119 | if (kcdata_iter_container_type(iter) != STACKSHOT_KCCONTAINER_TASK) { | |
120 | continue; | |
121 | } | |
122 | ||
123 | parsed_container = parseKCDataContainer(&iter, &error); | |
124 | T_ASSERT_TRUE(parsed_container && !error, "Parsing container"); | |
125 | ||
126 | parsed_threads = parsed_container[@"task_snapshots"][@"thread_snapshots"]; | |
127 | for (id th_key in parsed_threads) { | |
128 | /* check to see that tid matches expected idle status */ | |
129 | thread_snap_flags = [parsed_threads[th_key][@"thread_snapshot"][@"ths_ss_flags"] unsignedLongLongValue]; | |
130 | (thread_snap_flags & kThreadIdleWorker) ? (*num_idles)++ : (*num_nonidles)++; | |
131 | } | |
132 | [parsed_container release]; | |
133 | } | |
134 | ||
135 | } | |
136 | ||
137 | /* Dispatch NUMENQUEUES jobs to a concurrent queue that immediately wait on a | |
138 | * shared semaphore. This should spin up plenty of threads! */ | |
139 | static void | |
140 | warm_up_threadpool(dispatch_queue_t q) | |
141 | { | |
142 | int i; | |
143 | dispatch_semaphore_t thread_wait = dispatch_semaphore_create(0); | |
144 | T_QUIET; T_ASSERT_NOTNULL(thread_wait, "Initializing work queue semaphore"); | |
145 | dispatch_semaphore_t main_wait = dispatch_semaphore_create(0); | |
146 | T_QUIET; T_ASSERT_NOTNULL(main_wait, "Initializing main thread semaphore"); | |
147 | ||
148 | for (i = 0; i < NUMENQUEUES; i++) { | |
149 | dispatch_async(q, ^{ | |
150 | dispatch_semaphore_wait(thread_wait, DISPATCH_TIME_FOREVER); | |
151 | dispatch_semaphore_signal(main_wait); | |
152 | }); | |
153 | } | |
154 | ||
155 | sleep(1); // give worker threads enough time to block | |
156 | ||
157 | for (i = 0; i < NUMENQUEUES; i++) { | |
158 | dispatch_semaphore_signal(thread_wait); | |
159 | dispatch_semaphore_wait(main_wait, DISPATCH_TIME_FOREVER); | |
160 | } | |
161 | ||
162 | dispatch_release(thread_wait); | |
163 | dispatch_release(main_wait); | |
164 | ||
165 | // Give enough time for worker threads to go idle again | |
166 | sleep(1); | |
167 | } | |
168 | ||
169 | /* Dispatch NUMENQUEUES jobs to a concurrent queue that spin in a tight loop. | |
170 | * Isn't guaranteed to occupy every worker thread, but it's enough so | |
171 | * that a thread will go from idle to nonidle. | |
172 | */ | |
173 | static void | |
174 | fill_threadpool_with_spinning(dispatch_queue_t q) | |
175 | { | |
176 | int i; | |
177 | for (i = 0; i < NUMENQUEUES; i++) { | |
178 | dispatch_async(q, ^{ | |
179 | while(spin_threads); // should now appear as non-idle in delta shot | |
180 | }); | |
181 | } | |
182 | sleep(1); // wait for jobs to enqueue | |
183 | } | |
184 | ||
185 | /* Take stackshot, count the number of idle and nonidle threads the stackshot records. | |
186 | * Where this is called, there should be NUMENQUEUES idle threads (thanks to warm_up_threadpool) | |
187 | * and 2 nonidle threads (the main thread, and the spinning pthread). | |
188 | */ | |
189 | static void | |
190 | take_and_verify_initial_stackshot(uint64_t * since_time) | |
191 | { | |
192 | void *stackshot; | |
193 | int num_init_idle_threads, num_init_nonidle_threads; | |
194 | ||
195 | stackshot = take_stackshot(0, 0); | |
196 | *since_time = get_stackshot_timestamp(stackshot); | |
197 | get_thread_statuses(stackshot, &num_init_idle_threads, &num_init_nonidle_threads); | |
198 | ||
199 | T_EXPECT_EQ(num_init_idle_threads, NUMENQUEUES, | |
200 | "Idle count of %d should match expected value of %d...", | |
201 | num_init_idle_threads, NUMENQUEUES); | |
202 | T_EXPECT_EQ(num_init_nonidle_threads, NUMTHREADS - NUMENQUEUES, | |
203 | "Non-idle count of %d should match expected value of %d...", | |
204 | num_init_nonidle_threads, NUMTHREADS - NUMENQUEUES); | |
205 | stackshot_config_dealloc(stackshot); | |
206 | } | |
207 | ||
208 | /* Take a stackshot and a delta stackshot, measuring what changed since the previous | |
209 | * stackshot. Where this is called, the blocking jobs have been cleared from the work queue, | |
210 | * and the work queue has NUMENQUEUES tight-spinning jobs on it. Make sure that | |
211 | * no new idle threads appear in the delta, and make sure that the delta shot isn't | |
212 | * ignoring the worker threads that have become active. | |
213 | */ | |
214 | static void | |
215 | take_and_verify_delta_stackshot(uint64_t since_time) | |
216 | { | |
217 | void *stackshot; | |
218 | void *delta_stackshot; | |
219 | ||
220 | int num_delta_idles, num_delta_nonidles, num_curr_idles, num_curr_nonidles; | |
221 | ||
222 | stackshot = take_stackshot(0, 0); | |
223 | delta_stackshot = take_stackshot(0, since_time); /* Threads should appear in delta stackshot as non-idle */ | |
224 | ||
225 | get_thread_statuses(stackshot, &num_curr_idles, &num_curr_nonidles); | |
226 | get_thread_statuses(delta_stackshot, &num_delta_idles, &num_delta_nonidles); | |
227 | ||
228 | T_EXPECT_EQ(num_delta_idles, 0, "Making sure there are no idles in delta shot"); | |
229 | T_EXPECT_EQ(num_delta_nonidles + num_curr_idles, NUMTHREADS, | |
230 | "Making sure delta shot isn't ignoring newly active threads"); | |
231 | stackshot_config_dealloc(stackshot); | |
232 | stackshot_config_dealloc(delta_stackshot); | |
233 | } | |
234 | ||
235 | static void * | |
236 | spinning_non_work_queue_thread(void * ignored) | |
237 | { | |
238 | (void)ignored; | |
239 | while(spin_threads); | |
240 | return NULL; | |
241 | } | |
242 | ||
813fb2f6 | 243 | T_DECL(stackshot_idle_25570396, "Tests that stackshot can properly recognize idle and non-idle threads", T_META_ASROOT(true)) |
39037602 A |
244 | { |
245 | int ret; | |
246 | uint64_t initial_stackshot_time; | |
247 | pthread_t spinning_thread; | |
248 | dispatch_queue_t q; | |
249 | ||
250 | ret = pthread_create(&spinning_thread, NULL, spinning_non_work_queue_thread, NULL); | |
251 | T_ASSERT_POSIX_ZERO(ret, "Spinning up non-work-queue thread"); | |
252 | ||
253 | q = dispatch_queue_create("com.apple.kernel.test.waiting_semaphores", DISPATCH_QUEUE_CONCURRENT); | |
254 | ||
255 | warm_up_threadpool(q); | |
256 | take_and_verify_initial_stackshot(&initial_stackshot_time); | |
257 | ||
258 | fill_threadpool_with_spinning(q); | |
259 | take_and_verify_delta_stackshot(initial_stackshot_time); | |
260 | ||
261 | spin_threads = 0; /* pthread-made thread should now exit */ | |
262 | ret = pthread_join(spinning_thread, NULL); | |
263 | T_ASSERT_POSIX_ZERO(ret, "Joining on non-work-queue thread"); | |
264 | } |