xref: /haiku/src/tests/system/kernel/spinlock_contention.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
1 /*
2  * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 #include <errno.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <sys/wait.h>
11 #include <unistd.h>
12 
13 #include <SupportDefs.h>
14 
15 #include <syscalls.h>
16 #include <spinlock_contention.h>
17 
18 
19 #define panic printf
20 
21 
22 struct dummy_spinlock {
23 	vint32	lock;
24 	vint32	count_low;
25 	vint32	count_high;
26 };
27 
28 
29 struct dummy_smp_msg {
30 	dummy_smp_msg*	next;
31 };
32 
33 
34 static int				sNumCPUs = 2;
35 static bool				sICIEnabled = true;
36 static dummy_spinlock	cpu_msg_spinlock[SMP_MAX_CPUS];
37 static dummy_smp_msg*	smp_msgs[SMP_MAX_CPUS];
38 static dummy_spinlock	broadcast_msg_spinlock;
39 static dummy_smp_msg*	smp_broadcast_msgs;
40 
41 
42 bool
43 dummy_are_interrupts_enabled()
44 {
45 	return false;
46 }
47 
48 
49 void
50 dummy_acquire_spinlock_nocheck(dummy_spinlock* lock)
51 {
52 	if (sNumCPUs > 1) {
53 		if (dummy_are_interrupts_enabled())
54 			panic("acquire_spinlock_nocheck: attempt to acquire lock %p with interrupts enabled\n", lock);
55 
56 		while (atomic_add(&lock->lock, 1) != 0) {
57 		}
58 	} else {
59 		if (dummy_are_interrupts_enabled())
60 			panic("acquire_spinlock_nocheck: attempt to acquire lock %p with interrupts enabled\n", lock);
61 		if (atomic_set((int32 *)lock, 1) != 0)
62 			panic("acquire_spinlock_nocheck: attempt to acquire lock %p twice on non-SMP system\n", lock);
63 	}
64 }
65 
66 
67 void
68 dummy_release_spinlock(dummy_spinlock* lock)
69 {
70 	if (sNumCPUs > 1) {
71 		if (dummy_are_interrupts_enabled())
72 			panic("release_spinlock: attempt to release lock %p with interrupts enabled\n", lock);
73 
74 		{
75 			int32 count = atomic_set(&lock->lock, 0) - 1;
76 			if (count < 0) {
77 				panic("release_spinlock: lock %p was already released\n", lock);
78 			} else {
79 				// add to the total count -- deal with carry manually
80 				if ((uint32)atomic_add(&lock->count_low, count) + count
81 						< (uint32)count) {
82 					atomic_add(&lock->count_high, 1);
83 				}
84 			}
85 		}
86 	} else {
87 		if (dummy_are_interrupts_enabled())
88 			panic("release_spinlock: attempt to release lock %p with interrupts enabled\n", lock);
89 		if (atomic_set((int32 *)lock, 0) != 1)
90 			panic("release_spinlock: lock %p was already released\n", lock);
91 	}
92 }
93 
94 
95 struct dummy_smp_msg *
96 dummy_check_for_message(int currentCPU, int *source_mailbox)
97 {
98 	struct dummy_smp_msg *msg;
99 
100 	if (!sICIEnabled)
101 		return NULL;
102 
103 	dummy_acquire_spinlock_nocheck(&cpu_msg_spinlock[currentCPU]);
104 	msg = smp_msgs[currentCPU];
105 	if (msg != NULL) {
106 		printf("yeah\n");
107 		dummy_release_spinlock(&cpu_msg_spinlock[currentCPU]);
108 		*source_mailbox = 1;
109 	} else {
110 		// try getting one from the broadcast mailbox
111 
112 		dummy_release_spinlock(&cpu_msg_spinlock[currentCPU]);
113 		dummy_acquire_spinlock_nocheck(&broadcast_msg_spinlock);
114 
115 		msg = smp_broadcast_msgs;
116 		while (msg != NULL) {
117 			printf("yeah\n");
118 			msg = msg->next;
119 		}
120 		dummy_release_spinlock(&broadcast_msg_spinlock);
121 	}
122 	return msg;
123 }
124 
125 
126 int32
127 dummy_process_pending_ici(int32 currentCPU)
128 {
129 	int retval = 42;
130 	int sourceMailbox = 0;
131 
132 	dummy_smp_msg* msg = dummy_check_for_message(currentCPU, &sourceMailbox);
133 	if (msg == NULL)
134 		return retval;
135 
136 	switch ((addr_t)msg) {
137 		case 0:
138 			printf("foo\n");
139 			break;
140 		case 1:
141 			printf("foo\n");
142 			break;
143 		case 2:
144 			printf("foo\n");
145 			break;
146 	}
147 
148 	return 9;
149 }
150 
151 
152 static void
153 test_spinlock(dummy_spinlock* lock)
154 {
155 	while (atomic_add(&lock->lock, -1) != 0)
156 		dummy_process_pending_ici(0);
157 }
158 
159 
160 static double
161 estimate_spinlock_tick_time()
162 {
163 	// time the spinlock
164 	int32 count = (INT_MAX >> 16) + 1;
165 	while (true) {
166 		bigtime_t startTime = system_time();
167 
168 		dummy_spinlock lock;
169 		lock.lock = count;
170 		test_spinlock(&lock);
171 
172 		bigtime_t totalTime = system_time() - startTime;
173 		double tickTime = (double)totalTime / count;
174 
175 		if (totalTime > 1000000 || INT_MAX >> 2 < count)
176 			return tickTime;
177 
178 		count <<= 1;
179 	}
180 }
181 
182 
183 static const char*
184 time_string(double timeInUsecs, char* buffer)
185 {
186 	static const char* const kUnits[] = { "us", "ms", "s ", NULL };
187 
188 	double time = timeInUsecs;
189 
190 	int32 i = 0;
191 	while (time > 1000 && kUnits[i + 1] != NULL) {
192 		time /= 1000;
193 		i++;
194 	}
195 
196 	sprintf(buffer, "%.3f %s", time, kUnits[i]);
197 
198 	return buffer;
199 }
200 
201 
202 int
203 main(int argc, char** argv)
204 {
205 	// get the initial contention info
206 	spinlock_contention_info startInfo;
207 	status_t error = _kern_generic_syscall(SPINLOCK_CONTENTION,
208 		GET_SPINLOCK_CONTENTION_INFO, &startInfo, sizeof(startInfo));
209 	if (error != B_OK) {
210 		fprintf(stderr, "Error: Failed to get spinlock contention info: %s\n",
211 			strerror(error));
212 		exit(1);
213 	}
214 	bigtime_t startTime = system_time();
215 
216 	pid_t child = fork();
217 	if (child < 0) {
218 		fprintf(stderr, "Error: fork() failed: %s\n", strerror(errno));
219 		exit(1);
220 	}
221 
222 	if (child == 0) {
223 		execvp(argv[1], argv + 1);
224 		fprintf(stderr, "Error: exec() failed: %s\n", strerror(errno));
225 		exit(1);
226 	} else {
227 		int status;
228 		wait(&status);
229 	}
230 
231 	// get the final contention info
232 	spinlock_contention_info endInfo;
233 	error = _kern_generic_syscall(SPINLOCK_CONTENTION,
234 		GET_SPINLOCK_CONTENTION_INFO, &endInfo, sizeof(endInfo));
235 	if (error != B_OK) {
236 		fprintf(stderr, "Error: Failed to get spinlock contention info: %s\n",
237 			strerror(error));
238 		exit(1);
239 	}
240 	bigtime_t totalTime = system_time() - startTime;
241 
242 	char buffer[128];
243 	printf("\ntotal run time: %s\n", time_string(totalTime, buffer));
244 
245 	// estimate spinlock tick time
246 	printf("estimating time per spinlock tick...\n");
247 	double tickTime = estimate_spinlock_tick_time();
248 	printf("time per spinlock tick: %s\n",
249 		time_string(tickTime, buffer));
250 
251 	// print results
252 	static const char* const kLockNames[] = { "thread creation", NULL };
253 	bigtime_t lockCounts[] = {
254 		endInfo.thread_creation_spinlock - startInfo.thread_creation_spinlock,
255 	};
256 
257 	printf("\nlock             counter            time   wasted %% CPU\n");
258 	printf("-------------------------------------------------------\n");
259 	for (int32 i = 0; kLockNames[i] != NULL; i++) {
260 		double wastedUsecs = lockCounts[i] * tickTime;
261 		printf("%-10s  %12llu  %14s   %12.4f\n", kLockNames[i], lockCounts[i],
262 			time_string(wastedUsecs, buffer), wastedUsecs / totalTime * 100);
263 	}
264 
265 	return 0;
266 }
267