3 /* This message is exactly 1024 bytes */
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.
66 #include <sys/types.h>
69 #if TIME_WITH_SYS_TIME
70 # include <sys/time.h>
74 # include <sys/time.h>
79 #include "citadel_ipc.h"
81 #ifndef HAVE_PTHREAD_H
82 #error This program requires threads
85 static int w = 10; /* see above */
86 static int n = 100; /* see above */
87 static int m = 1000; /* Number of messages to send; see above */
88 static volatile int count = 0; /* Total count of messages posted */
89 static volatile int total = 0; /* Total messages to be posted */
90 static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
91 static pthread_mutex_t arg_mutex = PTHREAD_MUTEX_INITIALIZER;
92 static pthread_mutex_t output_mutex = PTHREAD_MUTEX_INITIALIZER;
94 static char username[12];
95 static char password[12];
98 * Mutex for the random number generator
99 * We don't assume that rand_r() is present, so we have to
100 * provide our own locking for rand()
102 static pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER;
105 * Conditional. All the threads wait for this signal to actually
106 * start bombarding the server.
108 static pthread_mutex_t start_mutex = PTHREAD_MUTEX_INITIALIZER;
109 static pthread_cond_t start_cond = PTHREAD_COND_INITIALIZER;
113 * This is the worker thread. It logs in and creates the 1,000 messages
114 * as described above.
116 void* worker(void* data)
118 CtdlIPC* ipc; /* My connection to the server */
119 void** args; /* Args sent in */
120 int r; /* IPC return code */
121 char aaa[SIZ]; /* Generic buffer */
122 int c; /* Message count */
123 time_t start, end; /* Timestamps */
124 struct ctdlipcmessage msg; /* The message we will post */
127 long tmin = LONG_MAX, trun = 0, tmax = LONG_MIN;
130 argc_ = (int)args[0];
131 argv_ = (char**)args[1];
133 /* Setup the message we will be posting */
137 strcpy(msg.recipient, "");
138 strcpy(msg.subject, "Test message; ignore");
139 strcpy(msg.author, username);
141 pthread_mutex_lock(&arg_mutex);
142 ipc = CtdlIPC_new(argc_, argv_, NULL, NULL);
143 pthread_mutex_unlock(&arg_mutex);
145 return NULL; /* oops, something happened... */
147 CtdlIPC_chat_recv(ipc, aaa);
149 fprintf(stderr, "Citadel refused me: %s\n", &aaa[4]);
150 return NULL; /* server ran out of connections maybe? */
153 CtdlIPCIdentifySoftware(ipc, 8, 8, REV_LEVEL, "Citadel stress tester",
154 "localhost", aaa); /* we're lying, the server knows */
156 r = CtdlIPCQueryUsername(ipc, username, aaa);
158 /* testuser already exists (from previous run?) */
159 r = CtdlIPCTryLogin(ipc, username, aaa);
161 fprintf(stderr, "Citadel refused username: %s\n", aaa);
162 CtdlIPC_delete_ptr(&ipc);
163 return NULL; /* Gawd only knows what went wrong */
165 r = CtdlIPCTryPassword(ipc, password, aaa);
167 fprintf(stderr, "Citadel refused password: %s\n", aaa);
168 CtdlIPC_delete_ptr(&ipc);
169 return NULL; /* Gawd only knows what went wrong */
172 /* testuser doesn't yet exist */
173 r = CtdlIPCCreateUser(ipc, username, 1, aaa);
175 fprintf(stderr, "Citadel refused create user: %s\n", aaa);
176 CtdlIPC_delete_ptr(&ipc);
177 return NULL; /* Gawd only knows what went wrong */
179 r = CtdlIPCChangePassword(ipc, password, aaa);
181 fprintf(stderr, "Citadel refused change password: %s\n", aaa);
182 CtdlIPC_delete_ptr(&ipc);
183 return NULL; /* Gawd only knows what went wrong */
187 /* Wait for the rest of the threads */
188 pthread_mutex_lock(&start_mutex);
189 pthread_cond_wait(&start_cond, &start_mutex);
190 pthread_mutex_unlock(&start_mutex);
192 /* And now the fun begins! Send out a whole shitload of messages */
194 for (c = 0; c < m; c++) {
197 struct ctdlipcroom *rret;
202 /* Wait for a while */
203 pthread_mutex_lock(&rand_mutex);
204 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
205 /* Randomize between w/3 to w*3 (yes, it's complicated) */
206 wait = (int)((1.0+2.7*(float)w)*rand()/(RAND_MAX+(float)w/3.0)); /* range 0-99 */
207 pthread_mutex_unlock(&rand_mutex);
210 gettimeofday(&tv, NULL);
211 tstart = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
212 /* Select the room to goto */
213 pthread_mutex_lock(&rand_mutex);
214 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
215 rm = (int)(100.0*rand()/(RAND_MAX+1.0)); /* range 0-99 */
216 pthread_mutex_unlock(&rand_mutex);
218 /* Goto the selected room */
219 sprintf(room, "test%d", rm);
220 /* Create the room if not existing. Ignore the return */
221 r = CtdlIPCCreateRoom(ipc, 1, room, 0, NULL, 0, aaa);
222 if (r / 100 != 2 && r != 574) { /* Already exists */
223 fprintf(stderr, "Citadel refused room create: %s\n", aaa);
224 pthread_mutex_lock(&count_mutex);
226 pthread_mutex_unlock(&count_mutex);
227 CtdlIPC_delete_ptr(&ipc);
230 r = CtdlIPCGotoRoom(ipc, room, "", &rret, aaa);
232 fprintf(stderr, "Citadel refused room change: %s\n", aaa);
233 pthread_mutex_lock(&count_mutex);
235 pthread_mutex_unlock(&count_mutex);
236 CtdlIPC_delete_ptr(&ipc);
240 /* Post the message */
241 r = CtdlIPCPostMessage(ipc, 1, &msg, aaa);
243 fprintf(stderr, "Citadel refused message entry: %s\n", aaa);
244 pthread_mutex_lock(&count_mutex);
246 pthread_mutex_unlock(&count_mutex);
247 CtdlIPC_delete_ptr(&ipc);
251 /* Do a status update */
252 pthread_mutex_lock(&count_mutex);
254 pthread_mutex_unlock(&count_mutex);
255 fprintf(stderr, "%d of %d - %d%% \r",
257 (int)(100 * count / total));
258 gettimeofday(&tv, NULL);
259 tend = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
261 if (tend < tmin) tmin = tend;
262 if (tend > tmax) tmax = tend;
266 pthread_mutex_lock(&output_mutex);
267 printf("%ld %ld %ld %ld\n", end - start, tmin, trun / c, tmax);
268 pthread_mutex_unlock(&output_mutex);
269 return (void*)(end - start);
274 * Shift argument list
276 int shift(int argc, char **argv, int start, int count)
280 for (i = start; i < argc - count; ++i)
281 argv[i] = argv[i + count];
287 * Main loop. Start a shitload of threads, all of which will attempt to
288 * kick a Citadel server square in the nuts.
290 int main(int argc, char** argv)
292 void* data[2]; /* pass args to worker thread */
293 pthread_t* threads; /* A shitload of threads */
294 pthread_attr_t attr; /* Thread attributes (we use defaults) */
295 int i; /* Counters */
296 long runtime; /* Run time for each thread */
298 /* Read argument list */
299 for (i = 0; i < argc; i++) {
300 if (!strcmp(argv[i], "-n")) {
301 n = atoi(argv[i + 1]);
302 argc = shift(argc, argv, i, 2);
304 if (!strcmp(argv[i], "-w")) {
305 w = atoi(argv[i + 1]);
306 argc = shift(argc, argv, i, 2);
308 if (!strcmp(argv[i], "-m")) {
309 m = atoi(argv[i + 1]);
310 argc = shift(argc, argv, i, 2);
312 if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
313 fprintf(stderr, "Read stress.c for usage info\n");
318 data[0] = (void*)argc; /* pass args to worker thread */
319 data[1] = (void*)argv; /* pass args to worker thread */
321 /* This is how many total messages will be posted */
324 /* Pick a randomized username */
325 pthread_mutex_lock(&rand_mutex);
326 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
327 i = (int)(100.0*rand()/(RAND_MAX+1.0)); /* range 0-99 */
328 pthread_mutex_unlock(&rand_mutex);
329 sprintf(username, "testuser%d", i);
330 strcpy(password, username);
332 /* First, memory for our shitload of threads */
333 threads = calloc(n, sizeof(pthread_t));
335 perror("Not enough memory");
339 /* Then thread attributes (all defaults for now) */
340 pthread_attr_init(&attr);
341 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
343 /* Then, create some threads */
344 fprintf(stderr, "Creating threads \r");
345 for (i = 0; i < n; ++i) {
346 pthread_create(&threads[i], &attr, worker, (void*)data);
348 /* Give thread #0 time to create the user account */
349 if (i == 0) sleep(3);
352 fprintf(stderr, "Starting in 10 seconds\r");
354 fprintf(stderr, " \r");
356 /* Then, signal the conditional they all are waiting on */
357 pthread_mutex_lock(&start_mutex);
358 pthread_cond_broadcast(&start_cond);
359 pthread_mutex_unlock(&start_mutex);
361 /* Then wait for them to exit */
362 for (i = 0; i < n; i++) {
363 pthread_join(threads[i], (void*)&runtime);
364 /* We're ignoring this value for now... TODO */
366 fprintf(stderr, "\r \r");