2 /* This message is exactly 1024 bytes */
4 "The point of this little file is to stress test a Citadel server.\n"
5 "It spawns n threads, where n is a command line parameter, each of\n"
6 "which writes 1000 messages total to the server.\n"
8 "-n is a command line parameter indicating how many users to simulate\n"
9 "(default 100). WARNING: Your system must be capable of creating this\n"
12 "-w is a command line parameter indicating how long to wait in seconds\n"
13 "between posting each message (default 10). The actual interval\n"
14 "will be randomized between w / 3 and w * 3.\n"
16 "A run is expected to take approximately three hours, given default\n"
17 "values, and assuming the server can keep up. If the run takes much\n"
18 "longer than this, there may be a performance problem with the server.\n"
19 "For best results, the test should be run from a different machine than\n"
20 "the server, but connected via a fast network link (e.g. 100Base-T).\n"
22 "To get baseline results, run the test with -n 1 (simulating 1 user)\n"
23 "on a machine with no other users logged in.\n"
26 "stress -n 500 -w 25 myserver > stress.csv\n";
28 /* The program tries to be as small and as fast as possible. Wherever
29 * possible, we avoid allocating memory on the heap. We do not pass data
30 * between threads. We do only a minimal amount of calculation. In
31 * particular, we only output raw timing data for the run; we do not
32 * collate it, average it, or do anything else with it. See below.
33 * The program does, however, use the same CtdlIPC functions as the
34 * standard Citadel text client, and incurs the same overhead as that
35 * program, using those functions.
37 * The program first creates a new user with a randomized username which
38 * begins with "testuser". It then creates 100 rooms named test0 through
39 * test99. If they already exist, this condition is ignored.
41 * The program then creates n threads, all of which wait on a conditional
42 * before they do anything. Once all of the threads have been created,
43 * they are signaled, and begin execution. Each thread logs in to the
44 * Citadel server separately, simulating a user login, then takes a
45 * timestamp from the operating system.
47 * Each thread selects a room from 0-99 randomly, then writes a small
48 * (1KB) test message to that room. 1K was chosen because it seems to
49 * represent an average message size for messages we expect to see.
50 * After writing the message, the thread sleeps for w seconds (sleep(w);)
51 * and repeats the process, until it has written 1,000 messages. The
52 * program provides a status display to standard error, unless w <= 2, in
53 * which case status display is disabled.
55 * After posting all messages, each thread takes a second timestamp, and
56 * subtracts the first timestamp. The resulting value (in seconds) is
57 * sent to standard output, followed by the minimum, average, and maximum
58 * amounts of time (in milliseconds) it took to post a message. The
61 * Once all threads have exited, the program exits.
67 #include <sys/types.h>
69 #include <libcitadel.h>
72 #include "citadel_ipc.h"
74 #ifndef HAVE_PTHREAD_H
75 #error This program requires threads
78 static int w = 10; /* see above */
79 static int n = 100; /* see above */
80 static int m = 1000; /* Number of messages to send; see above */
81 static volatile int count = 0; /* Total count of messages posted */
82 static volatile int total = 0; /* Total messages to be posted */
83 static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
84 static pthread_mutex_t arg_mutex = PTHREAD_MUTEX_INITIALIZER;
85 static pthread_mutex_t output_mutex = PTHREAD_MUTEX_INITIALIZER;
87 static char username[12];
88 static char password[12];
91 * Mutex for the random number generator
92 * We don't assume that rand_r() is present, so we have to
93 * provide our own locking for rand()
95 static pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER;
98 * Conditional. All the threads wait for this signal to actually
99 * start bombarding the server.
101 static pthread_mutex_t start_mutex = PTHREAD_MUTEX_INITIALIZER;
102 static pthread_cond_t start_cond = PTHREAD_COND_INITIALIZER;
106 * This is the worker thread. It logs in and creates the 1,000 messages
107 * as described above.
109 void* worker(void* data)
111 CtdlIPC* ipc; /* My connection to the server */
112 void** args; /* Args sent in */
113 int r; /* IPC return code */
114 char aaa[SIZ]; /* Generic buffer */
115 int c; /* Message count */
116 time_t start, end; /* Timestamps */
117 struct ctdlipcmessage msg; /* The message we will post */
120 long tmin = LONG_MAX, trun = 0, tmax = LONG_MIN;
123 argc_ = (int)args[0];
124 argv_ = (char**)args[1];
126 /* Setup the message we will be posting */
130 strcpy(msg.recipient, "");
131 strcpy(msg.subject, "Test message; ignore");
132 strcpy(msg.author, username);
134 pthread_mutex_lock(&arg_mutex);
135 ipc = CtdlIPC_new(argc_, argv_, NULL, NULL);
136 pthread_mutex_unlock(&arg_mutex);
138 return NULL; /* oops, something happened... */
140 CtdlIPC_chat_recv(ipc, aaa);
142 fprintf(stderr, "Citadel refused me: %s\n", &aaa[4]);
143 return NULL; /* server ran out of connections maybe? */
146 CtdlIPCIdentifySoftware(ipc, 8, 8, REV_LEVEL, "Citadel stress tester",
147 "localhost", aaa); /* we're lying, the server knows */
149 r = CtdlIPCQueryUsername(ipc, username, aaa);
151 /* testuser already exists (from previous run?) */
152 r = CtdlIPCTryLogin(ipc, username, aaa);
154 fprintf(stderr, "Citadel refused username: %s\n", aaa);
155 CtdlIPC_delete_ptr(&ipc);
156 return NULL; /* Gawd only knows what went wrong */
158 r = CtdlIPCTryPassword(ipc, password, aaa);
160 fprintf(stderr, "Citadel refused password: %s\n", aaa);
161 CtdlIPC_delete_ptr(&ipc);
162 return NULL; /* Gawd only knows what went wrong */
165 /* testuser doesn't yet exist */
166 r = CtdlIPCCreateUser(ipc, username, 1, aaa);
168 fprintf(stderr, "Citadel refused create user: %s\n", aaa);
169 CtdlIPC_delete_ptr(&ipc);
170 return NULL; /* Gawd only knows what went wrong */
172 r = CtdlIPCChangePassword(ipc, password, aaa);
174 fprintf(stderr, "Citadel refused change password: %s\n", aaa);
175 CtdlIPC_delete_ptr(&ipc);
176 return NULL; /* Gawd only knows what went wrong */
180 /* Wait for the rest of the threads */
181 pthread_mutex_lock(&start_mutex);
182 pthread_cond_wait(&start_cond, &start_mutex);
183 pthread_mutex_unlock(&start_mutex);
185 /* And now the fun begins! Send out a whole shitload of messages */
187 for (c = 0; c < m; c++) {
190 struct ctdlipcroom *rret;
195 /* Wait for a while */
196 pthread_mutex_lock(&rand_mutex);
197 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
198 /* Randomize between w/3 to w*3 (yes, it's complicated) */
199 wait = (int)((1.0+2.7*(float)w)*rand()/(RAND_MAX+(float)w/3.0)); /* range 0-99 */
200 pthread_mutex_unlock(&rand_mutex);
203 /* Select the room to goto */
204 pthread_mutex_lock(&rand_mutex);
205 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
206 rm = (int)(100.0*rand()/(RAND_MAX+1.0)); /* range 0-99 */
207 pthread_mutex_unlock(&rand_mutex);
209 /* Goto the selected room */
210 sprintf(room, "test%d", rm);
211 /* Create the room if not existing. Ignore the return */
212 r = CtdlIPCCreateRoom(ipc, 1, room, 0, NULL, 0, aaa);
213 if (r / 100 != 2 && r != 574) { /* Already exists */
214 fprintf(stderr, "Citadel refused room create: %s\n", aaa);
215 pthread_mutex_lock(&count_mutex);
217 pthread_mutex_unlock(&count_mutex);
218 CtdlIPC_delete_ptr(&ipc);
221 gettimeofday(&tv, NULL);
222 tstart = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
223 r = CtdlIPCGotoRoom(ipc, room, "", &rret, aaa);
225 fprintf(stderr, "Citadel refused room change: %s\n", aaa);
226 pthread_mutex_lock(&count_mutex);
228 pthread_mutex_unlock(&count_mutex);
229 CtdlIPC_delete_ptr(&ipc);
233 /* Post the message */
234 r = CtdlIPCPostMessage(ipc, 1, NULL, &msg, aaa);
236 fprintf(stderr, "Citadel refused message entry: %s\n", aaa);
237 pthread_mutex_lock(&count_mutex);
239 pthread_mutex_unlock(&count_mutex);
240 CtdlIPC_delete_ptr(&ipc);
244 /* Do a status update */
245 pthread_mutex_lock(&count_mutex);
247 pthread_mutex_unlock(&count_mutex);
248 fprintf(stderr, " %d/%d=%d%% \r",
250 (int)(100 * count / total));
251 gettimeofday(&tv, NULL);
252 tend = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
254 if (tend < tmin) tmin = tend;
255 if (tend > tmax) tmax = tend;
259 pthread_mutex_lock(&output_mutex);
260 fprintf(stderr, " \r");
261 printf("%ld %ld %ld %ld\n", end - start, tmin, trun / c, tmax);
262 pthread_mutex_unlock(&output_mutex);
263 return (void*)(end - start);
268 * Shift argument list
270 int shift(int argc, char **argv, int start, int count)
274 for (i = start; i < argc - count; ++i)
275 argv[i] = argv[i + count];
281 * Main loop. Start a shitload of threads, all of which will attempt to
282 * kick a Citadel server square in the nuts.
284 int main(int argc, char** argv)
286 void* data[2]; /* pass args to worker thread */
287 pthread_t* threads; /* A shitload of threads */
288 pthread_attr_t attr; /* Thread attributes (we use defaults) */
289 int i; /* Counters */
290 long runtime; /* Run time for each thread */
292 /* Read argument list */
293 for (i = 0; i < argc; i++) {
294 if (!strcmp(argv[i], "-n")) {
295 n = atoi(argv[i + 1]);
296 argc = shift(argc, argv, i, 2);
298 if (!strcmp(argv[i], "-w")) {
299 w = atoi(argv[i + 1]);
300 argc = shift(argc, argv, i, 2);
302 if (!strcmp(argv[i], "-m")) {
303 m = atoi(argv[i + 1]);
304 argc = shift(argc, argv, i, 2);
306 if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
307 fprintf(stderr, "Read stress.c for usage info\n");
312 data[0] = (void*)argc; /* pass args to worker thread */
313 data[1] = (void*)argv; /* pass args to worker thread */
315 /* This is how many total messages will be posted */
318 /* Pick a randomized username */
319 pthread_mutex_lock(&rand_mutex);
320 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
321 i = (int)(100.0*rand()/(RAND_MAX+1.0)); /* range 0-99 */
322 pthread_mutex_unlock(&rand_mutex);
323 sprintf(username, "testuser%d", i);
324 strcpy(password, username);
326 /* First, memory for our shitload of threads */
327 threads = calloc(n, sizeof(pthread_t));
329 perror("Not enough memory");
333 /* Then thread attributes (all defaults for now) */
334 pthread_attr_init(&attr);
335 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
337 /* Then, create some threads */
338 fprintf(stderr, "Creating threads \r");
339 for (i = 0; i < n; ++i) {
340 pthread_create(&threads[i], &attr, worker, (void*)data);
342 /* Give thread #0 time to create the user account */
343 if (i == 0) sleep(3);
346 //fprintf(stderr, "Starting in %d seconds\r", n);
348 fprintf(stderr, " \r");
350 /* Then, signal the conditional they all are waiting on */
351 pthread_mutex_lock(&start_mutex);
352 pthread_cond_broadcast(&start_cond);
353 pthread_mutex_unlock(&start_mutex);
355 /* Then wait for them to exit */
356 for (i = 0; i < n; i++) {
357 pthread_join(threads[i], (void*)&runtime);
358 /* We're ignoring this value for now... TODO */
360 fprintf(stderr, "\r \r");