3 /* This message is exactly 1024 bytes */
4 const char* const message =
5 "The point of this little file is to stress test a Citadel server.\n"
6 "It spawns n threads, where n is a command line parameter, each of\n"
7 "which writes 1000 messages total to the server.\n"
9 "-n is a command line parameter indicating how many users to simulate\n"
10 "(default 100). WARNING: Your system must be capable of creating this\n"
13 "-w is a command line parameter indicating how long to wait in seconds\n"
14 "between posting each message (default 10). The actual interval\n"
15 "will be randomized between w / 3 and w * 3.\n"
17 "A run is expected to take approximately three hours, given default\n"
18 "values, and assuming the server can keep up. If the run takes much\n"
19 "longer than this, there may be a performance problem with the server.\n"
20 "For best results, the test should be run from a different machine than\n"
21 "the server, but connected via a fast network link (e.g. 100Base-T).\n"
23 "To get baseline results, run the test with -n 1 (simulating 1 user)\n"
24 "on a machine with no other users logged in.\n"
27 "stress -n 500 -w 25 myserver > stress.csv\n";
29 /* The program tries to be as small and as fast as possible. Wherever
30 * possible, we avoid allocating memory on the heap. We do not pass data
31 * between threads. We do only a minimal amount of calculation. In
32 * particular, we only output raw timing data for the run; we do not
33 * collate it, average it, or do anything else with it. See below.
34 * The program does, however, use the same CtdlIPC functions as the
35 * standard Citadel text client, and incurs the same overhead as that
36 * program, using those functions.
38 * The program first creates a new user with a randomized username which
39 * begins with "testuser". It then creates 100 rooms named test0 through
40 * test99. If they already exist, this condition is ignored.
42 * The program then creates n threads, all of which wait on a conditional
43 * before they do anything. Once all of the threads have been created,
44 * they are signaled, and begin execution. Each thread logs in to the
45 * Citadel server separately, simulating a user login, then takes a
46 * timestamp from the operating system.
48 * Each thread selects a room from 0-99 randomly, then writes a small
49 * (1KB) test message to that room. 1K was chosen because it seems to
50 * represent an average message size for messages we expect to see.
51 * After writing the message, the thread sleeps for w seconds (sleep(w);)
52 * and repeats the process, until it has written 1,000 messages. The
53 * program provides a status display to standard error, unless w <= 2, in
54 * which case status display is disabled.
56 * After posting all messages, each thread takes a second timestamp, and
57 * subtracts the first timestamp. The resulting value (in seconds) is
58 * sent to standard output. The thread then exits.
60 * Once all threads have exited, the program exits.
65 #include <sys/types.h>
68 #if TIME_WITH_SYS_TIME
69 # include <sys/time.h>
73 # include <sys/time.h>
78 #include "citadel_ipc.h"
80 #ifndef HAVE_PTHREAD_H
81 #error This program requires threads
84 static int w = 10; /* see above */
85 static int n = 100; /* see above */
86 static int m = 1000; /* Number of messages to send; see above */
87 static volatile int count = 0; /* Total count of messages posted */
88 static volatile int total = 0; /* Total messages to be posted */
89 static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
90 static pthread_mutex_t arg_mutex = PTHREAD_MUTEX_INITIALIZER;
91 static pthread_mutex_t output_mutex = PTHREAD_MUTEX_INITIALIZER;
93 static char username[12];
94 static char password[12];
97 * Mutex for the random number generator
98 * We don't assume that rand_r() is present, so we have to
99 * provide our own locking for rand()
101 static pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER;
104 * Conditional. All the threads wait for this signal to actually
105 * start bombarding the server.
107 static pthread_mutex_t start_mutex = PTHREAD_MUTEX_INITIALIZER;
108 static pthread_cond_t start_cond = PTHREAD_COND_INITIALIZER;
112 * This is the worker thread. It logs in and creates the 1,000 messages
113 * as described above.
115 void* worker(void* data)
117 CtdlIPC* ipc; /* My connection to the server */
118 void** args; /* Args sent in */
119 int r; /* IPC return code */
120 char aaa[SIZ]; /* Generic buffer */
121 int c; /* Message count */
122 time_t start, end; /* Timestamps */
123 struct ctdlipcmessage msg; /* The message we will post */
126 long tmin = LONG_MAX, trun = 0, tmax = LONG_MIN;
129 argc_ = (int)args[0];
130 argv_ = (char**)args[1];
132 /* Setup the message we will be posting */
136 strcpy(msg.recipient, "");
137 strcpy(msg.subject, "Test message; ignore");
138 strcpy(msg.author, username);
140 pthread_mutex_lock(&arg_mutex);
141 ipc = CtdlIPC_new(argc_, argv_, NULL, NULL);
142 pthread_mutex_unlock(&arg_mutex);
144 return NULL; /* oops, something happened... */
146 CtdlIPC_chat_recv(ipc, aaa);
148 fprintf(stderr, "Citadel refused me: %s\n", &aaa[4]);
149 return NULL; /* server ran out of connections maybe? */
152 CtdlIPCIdentifySoftware(ipc, 8, 8, REV_LEVEL, "Citadel stress tester",
153 "localhost", aaa); /* we're lying, the server knows */
155 r = CtdlIPCQueryUsername(ipc, username, aaa);
157 /* testuser already exists (from previous run?) */
158 r = CtdlIPCTryLogin(ipc, username, aaa);
160 fprintf(stderr, "Citadel refused username: %s\n", aaa);
161 CtdlIPC_delete_ptr(&ipc);
162 return NULL; /* Gawd only knows what went wrong */
164 r = CtdlIPCTryPassword(ipc, password, aaa);
166 fprintf(stderr, "Citadel refused password: %s\n", aaa);
167 CtdlIPC_delete_ptr(&ipc);
168 return NULL; /* Gawd only knows what went wrong */
171 /* testuser doesn't yet exist */
172 r = CtdlIPCCreateUser(ipc, username, 1, aaa);
174 fprintf(stderr, "Citadel refused create user: %s\n", aaa);
175 CtdlIPC_delete_ptr(&ipc);
176 return NULL; /* Gawd only knows what went wrong */
178 r = CtdlIPCChangePassword(ipc, password, aaa);
180 fprintf(stderr, "Citadel refused change password: %s\n", aaa);
181 CtdlIPC_delete_ptr(&ipc);
182 return NULL; /* Gawd only knows what went wrong */
186 /* Wait for the rest of the threads */
187 pthread_mutex_lock(&start_mutex);
188 pthread_cond_wait(&start_cond, &start_mutex);
189 pthread_mutex_unlock(&start_mutex);
191 /* And now the fun begins! Send out a whole shitload of messages */
193 for (c = 0; c < m; c++) {
196 struct ctdlipcroom *rret;
201 /* Wait for a while */
202 pthread_mutex_lock(&rand_mutex);
203 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
204 /* Randomize between w/3 to w*3 (yes, it's complicated) */
205 wait = (int)((1.0+2.7*(float)w)*rand()/(RAND_MAX+(float)w/3.0)); /* range 0-99 */
206 pthread_mutex_unlock(&rand_mutex);
209 gettimeofday(&tv, NULL);
210 tstart = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
211 /* Select the room to goto */
212 pthread_mutex_lock(&rand_mutex);
213 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
214 rm = (int)(100.0*rand()/(RAND_MAX+1.0)); /* range 0-99 */
215 pthread_mutex_unlock(&rand_mutex);
217 /* Goto the selected room */
218 sprintf(room, "test%d", rm);
219 /* Create the room if not existing. Ignore the return */
220 r = CtdlIPCCreateRoom(ipc, 1, room, 0, NULL, 0, aaa);
221 if (r / 100 != 2 && r != 574) { /* Already exists */
222 fprintf(stderr, "Citadel refused room create: %s\n", aaa);
223 pthread_mutex_lock(&count_mutex);
225 pthread_mutex_unlock(&count_mutex);
226 CtdlIPC_delete_ptr(&ipc);
229 r = CtdlIPCGotoRoom(ipc, room, "", &rret, aaa);
231 fprintf(stderr, "Citadel refused room change: %s\n", aaa);
232 pthread_mutex_lock(&count_mutex);
234 pthread_mutex_unlock(&count_mutex);
235 CtdlIPC_delete_ptr(&ipc);
239 /* Post the message */
240 r = CtdlIPCPostMessage(ipc, 1, &msg, aaa);
242 fprintf(stderr, "Citadel refused message entry: %s\n", aaa);
243 pthread_mutex_lock(&count_mutex);
245 pthread_mutex_unlock(&count_mutex);
246 CtdlIPC_delete_ptr(&ipc);
250 /* Do a status update */
251 pthread_mutex_lock(&count_mutex);
253 pthread_mutex_unlock(&count_mutex);
254 fprintf(stderr, "%d of %d - %d%% \r",
256 (int)(100 * count / total));
257 gettimeofday(&tv, NULL);
258 tend = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
260 if (tend < tmin) tmin = tend;
261 if (tend > tmax) tmax = tend;
265 pthread_mutex_lock(&output_mutex);
266 printf("%ld %ld %ld %ld\n", end - start, tmin, trun / c, tmax);
267 pthread_mutex_unlock(&output_mutex);
268 return (void*)(end - start);
273 * Shift argument list
275 int shift(int argc, char **argv, int start, int count)
279 for (i = start; i < argc - count; ++i)
280 argv[i] = argv[i + count];
286 * Main loop. Start a shitload of threads, all of which will attempt to
287 * kick a Citadel server square in the nuts.
289 int main(int argc, char** argv)
291 void* data[2]; /* pass args to worker thread */
292 pthread_t* threads; /* A shitload of threads */
293 pthread_attr_t attr; /* Thread attributes (we use defaults) */
294 int i; /* Counters */
295 long runtime; /* Run time for each thread */
297 /* Read argument list */
298 for (i = 0; i < argc; i++) {
299 if (!strcmp(argv[i], "-n")) {
300 n = atoi(argv[i + 1]);
301 argc = shift(argc, argv, i, 2);
303 if (!strcmp(argv[i], "-w")) {
304 w = atoi(argv[i + 1]);
305 argc = shift(argc, argv, i, 2);
307 if (!strcmp(argv[i], "-m")) {
308 m = atoi(argv[i + 1]);
309 argc = shift(argc, argv, i, 2);
311 if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
312 fprintf(stderr, "Read stress.c for usage info\n");
317 data[0] = (void*)argc; /* pass args to worker thread */
318 data[1] = (void*)argv; /* pass args to worker thread */
320 /* This is how many total messages will be posted */
323 /* Pick a randomized username */
324 pthread_mutex_lock(&rand_mutex);
325 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
326 i = (int)(100.0*rand()/(RAND_MAX+1.0)); /* range 0-99 */
327 pthread_mutex_unlock(&rand_mutex);
328 sprintf(username, "testuser%d", i);
329 strcpy(password, username);
331 /* First, memory for our shitload of threads */
332 threads = calloc(n, sizeof(pthread_t));
334 perror("Not enough memory");
338 /* Then thread attributes (all defaults for now) */
339 pthread_attr_init(&attr);
340 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
342 /* Then, create some threads */
343 for (i = 0; i < n; ++i) {
344 pthread_create(&threads[i], &attr, worker, (void*)data);
347 fprintf(stderr, "Starting in 10 seconds\r");
349 fprintf(stderr, " \r");
351 /* Then, signal the conditional they all are waiting on */
352 pthread_mutex_lock(&start_mutex);
353 pthread_cond_broadcast(&start_cond);
354 pthread_mutex_unlock(&start_mutex);
356 /* Then wait for them to exit */
357 for (i = 0; i < n; i++) {
358 pthread_join(threads[i], (void*)&runtime);
359 /* We're ignoring this value for now... TODO */
361 fprintf(stderr, "\r \r");