master
c 435 lines 14.4 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "benchmark.h"
4
5 #define MAX_THREADS 64
6 #define TEST_DURATION_SEC 1
7 #define STOP_SIGNAL UINT64_MAX
8 #define NUM_LOCK_TYPES 5
9
10 // Structure to store summary stats
11 typedef struct {
12 double locks_per_sec[NUM_LOCK_TYPES][7]; // [lock_type][thread_count_index]
13 } summary_stats_t;
14
15 typedef struct {
16 uint64_t locks;
17 usec_t test_time;
18 volatile int ready;
19 } thread_stats_t;
20
21 typedef struct {
22 netdata_cond_t cond; // Individual condition for each thread
23 netdata_mutex_t cond_mutex; // Individual mutex for each thread
24 uint64_t run_flag; // Individual run flag for each thread
25 } thread_control_t;
26
27 typedef struct {
28 uint64_t protected_counter;
29 thread_stats_t stats[MAX_THREADS];
30 thread_control_t thread_controls[MAX_THREADS]; // Array of per-thread controls
31 } lock_control_t;
32
33 typedef enum {
34 LOCK_MUTEX,
35 LOCK_RWLOCK,
36 LOCK_SPINLOCK,
37 LOCK_RW_SPINLOCK,
38 LOCK_WAITQ
39 } lock_type_t;
40
41 typedef struct {
42 int thread_id;
43 lock_type_t type;
44 WAITQ_PRIORITY priority; // For waitq only
45 lock_control_t *control;
46 void *lock; // Points to the actual lock
47 ND_THREAD *thread;
48 } thread_context_t;
49
50 static const char *lock_names[] = {
51 "Mutex",
52 "RWLock",
53 "Spinlock",
54 "RW Spinlock",
55 "WaitQ"
56 };
57
58 static const char *priority_to_string(WAITQ_PRIORITY p) {
59 switch(p) {
60 case WAITQ_PRIO_URGENT: return "URGENT";
61 case WAITQ_PRIO_HIGH: return "HIGH";
62 case WAITQ_PRIO_NORMAL: return "NORMAL";
63 case WAITQ_PRIO_LOW: return "LOW";
64 default: return "UNKNOWN";
65 }
66 }
67
68 static void print_summary(const summary_stats_t *summary) {
69 fprintf(stderr, "\n=== Performance Summary (Million locks/sec) ===\n\n");
70 fprintf(stderr, "%-12s %8s %8s %8s %8s %8s %8s %8s\n",
71 "Lock Type", "1", "2", "4", "8", "16", "32", "64");
72 fprintf(stderr, "------------------------------------------------------------------------------\n");
73
74 for(int type = 0; type < NUM_LOCK_TYPES; type++) {
75 fprintf(stderr, "%-12s", lock_names[type]);
76 for(int i = 0; i < 7; i++) { // 6 different thread counts
77 fprintf(stderr, " %8.2f", summary->locks_per_sec[type][i] / 1000000.0);
78 }
79 fprintf(stderr, "\n");
80 }
81 fprintf(stderr, "\n");
82 }
83
84 static void wait_for_signal(netdata_cond_t *cond, netdata_mutex_t *mutex, uint64_t *flag) {
85 netdata_mutex_lock(mutex);
86 while (*flag == 0)
87 netdata_cond_wait(cond, mutex);
88 netdata_mutex_unlock(mutex);
89 }
90
91 static void benchmark_thread(void *arg) {
92 thread_context_t *ctx = (thread_context_t *)arg;
93 thread_stats_t *stats = &ctx->control->stats[ctx->thread_id];
94 thread_control_t *thread_control = &ctx->control->thread_controls[ctx->thread_id];
95
96 while(1) {
97 wait_for_signal(&thread_control->cond, &thread_control->cond_mutex, &thread_control->run_flag);
98
99 if (thread_control->run_flag == STOP_SIGNAL)
100 break;
101
102 usec_t start = now_monotonic_high_precision_usec();
103 uint64_t local_counter = 0;
104
105 switch(ctx->type) {
106 case LOCK_MUTEX: {
107 netdata_mutex_t *mutex = ctx->lock;
108 while (thread_control->run_flag) {
109 netdata_mutex_lock(mutex);
110 ctx->control->protected_counter++;
111 netdata_mutex_unlock(mutex);
112 local_counter++;
113 }
114 break;
115 }
116
117 case LOCK_RWLOCK: {
118 netdata_rwlock_t *rwlock = ctx->lock;
119 while (thread_control->run_flag) {
120 netdata_rwlock_wrlock(rwlock);
121 ctx->control->protected_counter++;
122 netdata_rwlock_wrunlock(rwlock);
123 local_counter++;
124 }
125 break;
126 }
127
128 case LOCK_SPINLOCK: {
129 SPINLOCK *spinlock = ctx->lock;
130 while (thread_control->run_flag) {
131 spinlock_lock(spinlock);
132 ctx->control->protected_counter++;
133 spinlock_unlock(spinlock);
134 local_counter++;
135 }
136 break;
137 }
138
139 case LOCK_RW_SPINLOCK: {
140 RW_SPINLOCK *rw_spinlock = ctx->lock;
141 while (thread_control->run_flag) {
142 rw_spinlock_write_lock(rw_spinlock);
143 ctx->control->protected_counter++;
144 rw_spinlock_write_unlock(rw_spinlock);
145 local_counter++;
146 }
147 break;
148 }
149
150 case LOCK_WAITQ: {
151 WAITQ *waitq = ctx->lock;
152 WAITQ_PRIORITY priority = ctx->priority;
153 while (thread_control->run_flag) {
154 waitq_acquire(waitq, priority);
155 ctx->control->protected_counter++;
156 waitq_release(waitq);
157 local_counter++;
158 }
159 break;
160 }
161 }
162
163 // Store results atomically
164 usec_t test_time = now_monotonic_high_precision_usec() - start;
165 __atomic_store_n(&stats->test_time, test_time, __ATOMIC_RELEASE);
166 __atomic_store_n(&stats->locks, local_counter, __ATOMIC_RELEASE);
167 __atomic_store_n(&stats->ready, 1, __ATOMIC_RELEASE);
168 }
169 }
170
171 static void print_thread_stats(const char *test_name, int threads, thread_context_t *contexts,
172 thread_stats_t *stats, uint64_t protected_counter,
173 summary_stats_t *summary, int thread_count_idx) {
174 fprintf(stderr, "\n%-20s (threads: %d)\n", test_name, threads);
175 if (strcmp(test_name, "WaitQ") == 0) {
176 fprintf(stderr, "%4s %8s %12s %12s %12s\n",
177 "THR", "PRIO", "LOCKS", "LOCKS/SEC", "TIME (ms)");
178 }
179 else {
180 fprintf(stderr, "%4s %12s %12s %12s\n",
181 "THR", "LOCKS", "LOCKS/SEC", "TIME (ms)");
182 }
183
184 uint64_t total_locks = 0;
185 double total_locks_per_sec = 0;
186
187 for(int i = 0; i < threads; i++) {
188 uint64_t locks = __atomic_load_n(&stats[i].locks, __ATOMIC_ACQUIRE);
189 usec_t time = __atomic_load_n(&stats[i].test_time, __ATOMIC_ACQUIRE);
190 double locks_per_sec = (double)locks * USEC_PER_SEC / time;
191 total_locks_per_sec += locks_per_sec;
192
193 if (strcmp(test_name, "WaitQ") == 0) {
194 fprintf(stderr, "%4d %8s %12"PRIu64" %12.0f %12.2f\n",
195 i,
196 priority_to_string(contexts[i].priority),
197 locks,
198 locks_per_sec,
199 (double)time / 1000.0);
200 }
201 else {
202 fprintf(stderr, "%4d %12"PRIu64" %12.0f %12.2f\n",
203 i, locks, locks_per_sec,
204 (double)time / 1000.0);
205 }
206
207 total_locks += locks;
208 }
209
210 if(total_locks != protected_counter) {
211 fprintf(stderr, "\nERROR: Counter mismatch!\n");
212 fprintf(stderr, "Sum of thread counters: %"PRIu64"\n", total_locks);
213 fprintf(stderr, "Protected counter: %"PRIu64"\n", protected_counter);
214 fprintf(stderr, "Difference: %"PRIu64"\n",
215 total_locks > protected_counter ?
216 total_locks - protected_counter :
217 protected_counter - total_locks);
218
219 fflush(stderr);
220 _exit(1);
221 }
222
223 fprintf(stderr, "%4s %12"PRIu64"\n", "TOT", total_locks);
224
225 // Store in summary for the final table
226 summary->locks_per_sec[contexts[0].type][thread_count_idx] = total_locks_per_sec;
227 }
228
229 static void run_test(const char *name, int threads, thread_context_t *contexts,
230 lock_control_t *control, summary_stats_t *summary) {
231 fprintf(stderr, "\nRunning test: %s with %d threads...\n", name, threads);
232
233 // Reset stats and counter
234 for(int i = 0; i < threads; i++) {
235 __atomic_store_n(&control->stats[i].locks, 0, __ATOMIC_RELEASE);
236 __atomic_store_n(&control->stats[i].test_time, 0, __ATOMIC_RELEASE);
237 __atomic_store_n(&control->stats[i].ready, 0, __ATOMIC_RELEASE);
238 }
239 control->protected_counter = 0;
240
241 // Signal only the threads we need for this test
242 for(int i = 0; i < threads; i++) {
243 thread_control_t *thread_control = &control->thread_controls[i];
244 netdata_mutex_lock(&thread_control->cond_mutex);
245 thread_control->run_flag = 1;
246 netdata_cond_signal(&thread_control->cond);
247 netdata_mutex_unlock(&thread_control->cond_mutex);
248 }
249
250 // Wait for test duration
251 sleep_usec(TEST_DURATION_SEC * USEC_PER_SEC);
252
253 // Signal threads to stop
254 for(int i = 0; i < threads; i++) {
255 thread_control_t *thread_control = &control->thread_controls[i];
256 __atomic_store_n(&thread_control->run_flag, 0, __ATOMIC_RELEASE);
257 }
258
259 // Wait for threads to report results
260 for(int i = 0; i < threads; i++) {
261 while(!__atomic_load_n(&control->stats[i].ready, __ATOMIC_ACQUIRE))
262 sleep_usec(10);
263 }
264
265 // Get thread count index for summary
266 int thread_count_idx;
267 switch(threads) {
268 case 1: thread_count_idx = 0; break;
269 case 2: thread_count_idx = 1; break;
270 case 4: thread_count_idx = 2; break;
271 case 8: thread_count_idx = 3; break;
272 case 16: thread_count_idx = 4; break;
273 case 32: thread_count_idx = 5; break;
274 case 64: thread_count_idx = 6; break;
275 default: thread_count_idx = 0; break;
276 }
277
278 print_thread_stats(name, threads, contexts, control->stats, control->protected_counter,
279 summary, thread_count_idx);
280 }
281
282 static void set_waitq_priorities(int thread_count, thread_context_t *contexts) {
283 switch(thread_count) {
284 case 1:
285 contexts[0].priority = WAITQ_PRIO_URGENT;
286 break;
287
288 case 2:
289 contexts[0].priority = WAITQ_PRIO_URGENT;
290 contexts[1].priority = WAITQ_PRIO_HIGH;
291 break;
292
293 case 4:
294 contexts[0].priority = WAITQ_PRIO_URGENT;
295 contexts[1].priority = WAITQ_PRIO_HIGH;
296 contexts[2].priority = WAITQ_PRIO_NORMAL;
297 contexts[3].priority = WAITQ_PRIO_LOW;
298 break;
299
300 default: { // 8, 16, 32
301 int threads_per_priority = thread_count / 4;
302 int remainder = thread_count % 4;
303 int thread_idx = 0;
304
305 for (int prio = WAITQ_PRIO_URGENT; prio <= WAITQ_PRIO_LOW; prio++) {
306 int count = threads_per_priority + (remainder > 0 ? 1 : 0);
307 remainder--;
308
309 for (int i = 0; i < count && thread_idx < thread_count; i++) {
310 contexts[thread_idx++].priority = prio;
311 }
312 }
313 break;
314 }
315 }
316 }
317
318 int locks_stress_test(void) {
319 summary_stats_t summary = {0};
320
321 // Initialize actual locks
322 netdata_mutex_t mutex;
323 netdata_rwlock_t rwlock;
324 netdata_mutex_init(&mutex);
325 netdata_rwlock_init(&rwlock);
326
327 SPINLOCK spinlock = SPINLOCK_INITIALIZER;
328 RW_SPINLOCK rw_spinlock = RW_SPINLOCK_INITIALIZER;
329 WAITQ waitq = WAITQ_INITIALIZER;
330
331 void *locks[] = {
332 &mutex,
333 &rwlock,
334 &spinlock,
335 &rw_spinlock,
336 &waitq
337 };
338
339 // Initialize control structures
340 lock_control_t controls[NUM_LOCK_TYPES] = { 0 };
341 for(int i = 0; i < NUM_LOCK_TYPES; i++) {
342 // Initialize per-thread condition variables and mutexes
343 for(int j = 0; j < MAX_THREADS; j++) {
344 netdata_cond_init(&controls[i].thread_controls[j].cond);
345 netdata_mutex_init(&controls[i].thread_controls[j].cond_mutex);
346 controls[i].thread_controls[j].run_flag = 0;
347 }
348 }
349
350 // Initialize thread arrays
351 thread_context_t *threads[NUM_LOCK_TYPES];
352 for(int i = 0; i < NUM_LOCK_TYPES; i++) {
353 threads[i] = calloc(MAX_THREADS, sizeof(thread_context_t));
354 if(!threads[i]) {
355 fprintf(stderr, "Failed to allocate memory for threads\n");
356 return 1;
357 }
358
359 // Initialize thread contexts
360 for(int j = 0; j < MAX_THREADS; j++) {
361 threads[i][j] = (thread_context_t){
362 .thread_id = j,
363 .type = i,
364 .control = &controls[i],
365 .lock = locks[i]
366 };
367 }
368 }
369
370 // Create all threads
371 fprintf(stderr, "Creating threads...\n");
372 for(int type = 0; type < NUM_LOCK_TYPES; type++) {
373 for(int i = 0; i < MAX_THREADS; i++) {
374 char thr_name[32];
375 snprintf(thr_name, sizeof(thr_name), "%s%d", lock_names[type], i);
376 threads[type][i].thread = nd_thread_create(
377 thr_name,
378 NETDATA_THREAD_OPTION_DONT_LOG,
379 benchmark_thread,
380 &threads[type][i]);
381 }
382 }
383
384 // Run tests with different thread counts
385 int thread_counts[] = {1, 2, 4, 8, 16, 32, 64};
386
387 // Warm up the CPU
388 sleep_usec(100000);
389
390 for(size_t i = 0; i < sizeof(thread_counts)/sizeof(thread_counts[0]); i++) {
391 int count = thread_counts[i];
392
393 // Set waitq priorities for this test
394 set_waitq_priorities(count, threads[LOCK_WAITQ]);
395
396 // Run test for each lock type
397 for(int type = 0; type < NUM_LOCK_TYPES; type++) {
398 run_test(lock_names[type], count, threads[type], &controls[type], &summary);
399 }
400 }
401
402 // Print the summary table
403 print_summary(&summary);
404
405 // Signal all threads to exit
406 fprintf(stderr, "\nStopping threads...\n");
407 for(int type = 0; type < NUM_LOCK_TYPES; type++) {
408 for(int i = 0; i < MAX_THREADS; i++) {
409 thread_control_t *thread_control = &controls[type].thread_controls[i];
410 netdata_mutex_lock(&thread_control->cond_mutex);
411 thread_control->run_flag = STOP_SIGNAL;
412 netdata_cond_signal(&thread_control->cond);
413 netdata_mutex_unlock(&thread_control->cond_mutex);
414 }
415 }
416
417 // Join all threads
418 fprintf(stderr, "\nWaiting for threads to exit...\n");
419 for(int type = 0; type < NUM_LOCK_TYPES; type++) {
420 for(int i = 0; i < MAX_THREADS; i++) {
421 nd_thread_join(threads[type][i].thread);
422 }
423 }
424
425 // Cleanup condition variables and mutexes
426 for(int type = 0; type < NUM_LOCK_TYPES; type++) {
427 for(int i = 0; i < MAX_THREADS; i++) {
428 netdata_cond_destroy(&controls[type].thread_controls[i].cond);
429 netdata_mutex_destroy(&controls[type].thread_controls[i].cond_mutex);
430 }
431 free(threads[type]);
432 }
433
434 return 0;
435 }