1 /*
2 * Copyright (c) 2023, Meta
3 * Copyright (c) 2024, Tenstorrent AI ULC
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include <pthread.h>
9 #include <stdio.h>
10
11 #include <zephyr/sys/__assert.h>
12 #include <zephyr/sys/util.h>
13
14 #define STACK_SIZE K_THREAD_STACK_LEN(CONFIG_TEST_STACK_SIZE)
15
16 /* update interval for printing stats */
17 #if CONFIG_TEST_DURATION_S >= 60
18 #define UPDATE_INTERVAL_S 10
19 #elif CONFIG_TEST_DURATION_S >= 30
20 #define UPDATE_INTERVAL_S 5
21 #else
22 #define UPDATE_INTERVAL_S 1
23 #endif
24
25 /* 32 threads is mainly a limitation of find_lsb_set() */
26 #define NUM_CPUS MIN(32, MIN(CONFIG_MP_MAX_NUM_CPUS, CONFIG_POSIX_THREAD_THREADS_MAX))
27
28 typedef int (*create_fn)(int i);
29 typedef int (*join_fn)(int i);
30
31 static void before(void);
32
33 /* bitmask of available threads */
34 static bool alive[NUM_CPUS];
35
36 /* array of thread stacks */
37 static K_THREAD_STACK_ARRAY_DEFINE(thread_stacks, NUM_CPUS, STACK_SIZE);
38
39 static struct k_thread k_threads[NUM_CPUS];
40 static uint64_t counters[NUM_CPUS];
41 static uint64_t prev_counters[NUM_CPUS];
42
print_stats(const char * tag,uint64_t now,uint64_t end)43 static void print_stats(const char *tag, uint64_t now, uint64_t end)
44 {
45 for (int i = 0; i < NUM_CPUS; ++i) {
46 printf("%s, %d, %u, %llu, 1, %llu\n", tag, i, UPDATE_INTERVAL_S, counters[i],
47 (counters[i] - prev_counters[i]) / UPDATE_INTERVAL_S);
48 prev_counters[i] = counters[i];
49 }
50 }
51
print_group_stats(const char * tag)52 static void print_group_stats(const char *tag)
53 {
54 uint64_t count = 0;
55
56 for (int i = 0; i < NUM_CPUS; ++i) {
57 count += counters[i];
58 }
59
60 printf("%s, ALL, %u, %llu, %u, %llu\n", tag, CONFIG_TEST_DURATION_S, count, NUM_CPUS,
61 count / CONFIG_TEST_DURATION_S / NUM_CPUS);
62 }
63
create_join_common(const char * tag,create_fn create,join_fn join)64 static void create_join_common(const char *tag, create_fn create, join_fn join)
65 {
66 int i;
67 int __maybe_unused ret;
68 uint64_t now_ms = k_uptime_get();
69 const uint64_t end_ms = now_ms + MSEC_PER_SEC * CONFIG_TEST_DURATION_S;
70 uint64_t update_ms = now_ms + MSEC_PER_SEC * UPDATE_INTERVAL_S;
71
72 for (i = 0; i < NUM_CPUS; ++i) {
73 /* spawn thread i */
74 prev_counters[i] = 0;
75 ret = create(i);
76 __ASSERT(ret == 0, "%s_create(%d)[%zu] failed: %d", tag, i, counters[i], ret);
77 }
78
79 do {
80 if (!IS_ENABLED(CONFIG_SMP)) {
81 /* allow the test thread to be swapped-out */
82 k_yield();
83 }
84
85 for (i = 0; i < NUM_CPUS; ++i) {
86 if (alive[i]) {
87 ret = join(i);
88 __ASSERT(ret, "%s_join(%d)[%zu] failed: %d", tag, i, counters[i],
89 ret);
90 alive[i] = false;
91
92 /* update counter i after each (create,join) pair */
93 ++counters[i];
94
95 if (IS_ENABLED(CONFIG_TEST_DELAY_US)) {
96 /* success with 0 delay means we are ~raceless */
97 k_busy_wait(CONFIG_TEST_DELAY_US);
98 }
99
100 /* re-spawn thread i */
101 ret = create(i);
102 __ASSERT(ret == 0, "%s_create(%d)[%zu] failed: %d", tag, i,
103 counters[i], ret);
104 }
105 }
106
107 /* are we there yet? */
108 now_ms = k_uptime_get();
109
110 /* dump some stats periodically */
111 if (now_ms > update_ms) {
112 update_ms += MSEC_PER_SEC * UPDATE_INTERVAL_S;
113
114 /* at this point, we should have seen many context switches */
115 for (i = 0; IS_ENABLED(CONFIG_ASSERT) && i < NUM_CPUS; ++i) {
116 __ASSERT(counters[i] > 0, "%s %d was never scheduled", tag, i);
117 }
118
119 if (IS_ENABLED(CONFIG_TEST_PERIODIC_STATS)) {
120 print_stats(tag, now_ms, end_ms);
121 }
122 }
123 Z_SPIN_DELAY(100);
124 } while (end_ms > now_ms);
125
126 print_group_stats(tag);
127 }
128
129 /*
130 * Wrappers for k_threads
131 */
132
k_thread_fun(void * arg1,void * arg2,void * arg3)133 static void k_thread_fun(void *arg1, void *arg2, void *arg3)
134 {
135 int i = POINTER_TO_INT(arg1);
136
137 alive[i] = true;
138 }
139
k_thread_create_wrapper(int i)140 static int k_thread_create_wrapper(int i)
141 {
142 k_thread_create(&k_threads[i], thread_stacks[i], STACK_SIZE, k_thread_fun,
143 INT_TO_POINTER(i), NULL, NULL, K_HIGHEST_APPLICATION_THREAD_PRIO, 0,
144 K_NO_WAIT);
145
146 return 0;
147 }
148
k_thread_join_wrapper(int i)149 static int k_thread_join_wrapper(int i)
150 {
151 return k_thread_join(&k_threads[i], K_FOREVER);
152 }
153
create_join_kthread(void)154 static void create_join_kthread(void)
155 {
156 if (IS_ENABLED(CONFIG_TEST_KTHREADS)) {
157 before();
158 create_join_common("k_thread", k_thread_create_wrapper, k_thread_join_wrapper);
159 }
160 }
161
162 /*
163 * Wrappers for pthreads
164 */
165
166 static pthread_t pthreads[NUM_CPUS];
167 static pthread_attr_t pthread_attrs[NUM_CPUS];
168
pthread_fun(void * arg)169 static void *pthread_fun(void *arg)
170 {
171 k_thread_fun(arg, NULL, NULL);
172 return NULL;
173 }
174
pthread_create_wrapper(int i)175 static int pthread_create_wrapper(int i)
176 {
177 return pthread_create(&pthreads[i], &pthread_attrs[i], pthread_fun, INT_TO_POINTER(i));
178 }
179
pthread_join_wrapper(int i)180 static int pthread_join_wrapper(int i)
181 {
182 return pthread_join(pthreads[i], NULL);
183 }
184
create_join_pthread(void)185 static void create_join_pthread(void)
186 {
187 if (IS_ENABLED(CONFIG_TEST_PTHREADS)) {
188 before();
189 create_join_common("pthread", pthread_create_wrapper, pthread_join_wrapper);
190 }
191 }
192
setup(void)193 static void setup(void)
194 {
195 printf("ASSERT: %c\n", IS_ENABLED(CONFIG_ASSERT) ? 'y' : 'n');
196 printf("BOARD: %s\n", CONFIG_BOARD);
197 printf("NUM_CPUS: %u\n", NUM_CPUS);
198 printf("TEST_DELAY_US: %u\n", CONFIG_TEST_DELAY_US);
199 printf("TEST_DURATION_S: %u\n", CONFIG_TEST_DURATION_S);
200 printf("SMP: %c\n", IS_ENABLED(CONFIG_SMP) ? 'y' : 'n');
201
202 printf("API, Thread ID, time(s), threads, cores, rate (threads/s/core)\n");
203
204 if (IS_ENABLED(CONFIG_TEST_PTHREADS)) {
205 int __maybe_unused ret;
206 const struct sched_param param = {
207 .sched_priority = sched_get_priority_max(SCHED_FIFO),
208 };
209
210 /* setup pthread stacks */
211 for (int i = 0; i < NUM_CPUS; ++i) {
212 ret = pthread_attr_init(&pthread_attrs[i]);
213 __ASSERT(ret == 0, "pthread_attr_init[%d] failed: %d", i, ret);
214
215 ret = pthread_attr_setstack(&pthread_attrs[i], thread_stacks[i],
216 STACK_SIZE);
217 __ASSERT(ret == 0, "pthread_attr_setstack[%d] failed: %d", i, ret);
218
219 ret = pthread_attr_setschedpolicy(&pthread_attrs[i], SCHED_FIFO);
220 __ASSERT(ret == 0, "pthread_attr_setschedpolicy[%d] failed: %d", i, ret);
221
222 ret = pthread_attr_setschedparam(&pthread_attrs[i], ¶m);
223 __ASSERT(ret == 0, "pthread_attr_setschedparam[%d] failed: %d", i, ret);
224 }
225 }
226 }
227
before(void)228 static void before(void)
229 {
230 for (int i = 0; i < NUM_CPUS; ++i) {
231 counters[i] = 0;
232 }
233 }
234
main(void)235 int main(void)
236 {
237 setup();
238
239 create_join_kthread();
240 create_join_pthread();
241
242 printf("PROJECT EXECUTION SUCCESSFUL\n");
243 }
244