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, followed by the minimum, average, and maximum
59 * amounts of time (in milliseconds) it took to post a message. The
62 * Once all threads have exited, the program exits.
68 #include <sys/types.h>
71 #if TIME_WITH_SYS_TIME
72 # include <sys/time.h>
76 # include <sys/time.h>
81 #include "citadel_ipc.h"
83 #ifndef HAVE_PTHREAD_H
84 #error This program requires threads
87 static int w = 10; /* see above */
88 static int n = 100; /* see above */
89 static int m = 1000; /* Number of messages to send; see above */
90 static volatile int count = 0; /* Total count of messages posted */
91 static volatile int total = 0; /* Total messages to be posted */
92 static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
93 static pthread_mutex_t arg_mutex = PTHREAD_MUTEX_INITIALIZER;
94 static pthread_mutex_t output_mutex = PTHREAD_MUTEX_INITIALIZER;
96 static char username[12];
97 static char password[12];
100 * Mutex for the random number generator
101 * We don't assume that rand_r() is present, so we have to
102 * provide our own locking for rand()
104 static pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER;
107 * Conditional. All the threads wait for this signal to actually
108 * start bombarding the server.
110 static pthread_mutex_t start_mutex = PTHREAD_MUTEX_INITIALIZER;
111 static pthread_cond_t start_cond = PTHREAD_COND_INITIALIZER;
115 * This is the worker thread. It logs in and creates the 1,000 messages
116 * as described above.
118 void* worker(void* data)
120 CtdlIPC* ipc; /* My connection to the server */
121 void** args; /* Args sent in */
122 int r; /* IPC return code */
123 char aaa[SIZ]; /* Generic buffer */
124 int c; /* Message count */
125 time_t start, end; /* Timestamps */
126 struct ctdlipcmessage msg; /* The message we will post */
129 long tmin = LONG_MAX, trun = 0, tmax = LONG_MIN;
132 argc_ = (int)args[0];
133 argv_ = (char**)args[1];
135 /* Setup the message we will be posting */
139 strcpy(msg.recipient, "");
140 strcpy(msg.subject, "Test message; ignore");
141 strcpy(msg.author, username);
143 pthread_mutex_lock(&arg_mutex);
144 ipc = CtdlIPC_new(argc_, argv_, NULL, NULL);
145 pthread_mutex_unlock(&arg_mutex);
147 return NULL; /* oops, something happened... */
149 CtdlIPC_chat_recv(ipc, aaa);
151 fprintf(stderr, "Citadel refused me: %s\n", &aaa[4]);
152 return NULL; /* server ran out of connections maybe? */
155 CtdlIPCIdentifySoftware(ipc, 8, 8, REV_LEVEL, "Citadel stress tester",
156 "localhost", aaa); /* we're lying, the server knows */
158 r = CtdlIPCQueryUsername(ipc, username, aaa);
160 /* testuser already exists (from previous run?) */
161 r = CtdlIPCTryLogin(ipc, username, aaa);
163 fprintf(stderr, "Citadel refused username: %s\n", aaa);
164 CtdlIPC_delete_ptr(&ipc);
165 return NULL; /* Gawd only knows what went wrong */
167 r = CtdlIPCTryPassword(ipc, password, aaa);
169 fprintf(stderr, "Citadel refused password: %s\n", aaa);
170 CtdlIPC_delete_ptr(&ipc);
171 return NULL; /* Gawd only knows what went wrong */
174 /* testuser doesn't yet exist */
175 r = CtdlIPCCreateUser(ipc, username, 1, aaa);
177 fprintf(stderr, "Citadel refused create user: %s\n", aaa);
178 CtdlIPC_delete_ptr(&ipc);
179 return NULL; /* Gawd only knows what went wrong */
181 r = CtdlIPCChangePassword(ipc, password, aaa);
183 fprintf(stderr, "Citadel refused change password: %s\n", aaa);
184 CtdlIPC_delete_ptr(&ipc);
185 return NULL; /* Gawd only knows what went wrong */
189 /* Wait for the rest of the threads */
190 pthread_mutex_lock(&start_mutex);
191 pthread_cond_wait(&start_cond, &start_mutex);
192 pthread_mutex_unlock(&start_mutex);
194 /* And now the fun begins! Send out a whole shitload of messages */
196 for (c = 0; c < m; c++) {
199 struct ctdlipcroom *rret;
204 /* Wait for a while */
205 pthread_mutex_lock(&rand_mutex);
206 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
207 /* Randomize between w/3 to w*3 (yes, it's complicated) */
208 wait = (int)((1.0+2.7*(float)w)*rand()/(RAND_MAX+(float)w/3.0)); /* range 0-99 */
209 pthread_mutex_unlock(&rand_mutex);
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 gettimeofday(&tv, NULL);
231 tstart = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
232 r = CtdlIPCGotoRoom(ipc, room, "", &rret, aaa);
234 fprintf(stderr, "Citadel refused room change: %s\n", aaa);
235 pthread_mutex_lock(&count_mutex);
237 pthread_mutex_unlock(&count_mutex);
238 CtdlIPC_delete_ptr(&ipc);
242 /* Post the message */
243 r = CtdlIPCPostMessage(ipc, 1, NULL, &msg, aaa);
245 fprintf(stderr, "Citadel refused message entry: %s\n", aaa);
246 pthread_mutex_lock(&count_mutex);
248 pthread_mutex_unlock(&count_mutex);
249 CtdlIPC_delete_ptr(&ipc);
253 /* Do a status update */
254 pthread_mutex_lock(&count_mutex);
256 pthread_mutex_unlock(&count_mutex);
257 fprintf(stderr, " %d/%d=%d%% \r",
259 (int)(100 * count / total));
260 gettimeofday(&tv, NULL);
261 tend = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
263 if (tend < tmin) tmin = tend;
264 if (tend > tmax) tmax = tend;
268 pthread_mutex_lock(&output_mutex);
269 fprintf(stderr, " \r");
270 printf("%ld %ld %ld %ld\n", end - start, tmin, trun / c, tmax);
271 pthread_mutex_unlock(&output_mutex);
272 return (void*)(end - start);
277 * Shift argument list
279 int shift(int argc, char **argv, int start, int count)
283 for (i = start; i < argc - count; ++i)
284 argv[i] = argv[i + count];
290 * Main loop. Start a shitload of threads, all of which will attempt to
291 * kick a Citadel server square in the nuts.
293 int main(int argc, char** argv)
295 void* data[2]; /* pass args to worker thread */
296 pthread_t* threads; /* A shitload of threads */
297 pthread_attr_t attr; /* Thread attributes (we use defaults) */
298 int i; /* Counters */
299 long runtime; /* Run time for each thread */
301 CtdlInitBase64Table();
303 /* Read argument list */
304 for (i = 0; i < argc; i++) {
305 if (!strcmp(argv[i], "-n")) {
306 n = atoi(argv[i + 1]);
307 argc = shift(argc, argv, i, 2);
309 if (!strcmp(argv[i], "-w")) {
310 w = atoi(argv[i + 1]);
311 argc = shift(argc, argv, i, 2);
313 if (!strcmp(argv[i], "-m")) {
314 m = atoi(argv[i + 1]);
315 argc = shift(argc, argv, i, 2);
317 if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
318 fprintf(stderr, "Read stress.c for usage info\n");
323 data[0] = (void*)argc; /* pass args to worker thread */
324 data[1] = (void*)argv; /* pass args to worker thread */
326 /* This is how many total messages will be posted */
329 /* Pick a randomized username */
330 pthread_mutex_lock(&rand_mutex);
331 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
332 i = (int)(100.0*rand()/(RAND_MAX+1.0)); /* range 0-99 */
333 pthread_mutex_unlock(&rand_mutex);
334 sprintf(username, "testuser%d", i);
335 strcpy(password, username);
337 /* First, memory for our shitload of threads */
338 threads = calloc(n, sizeof(pthread_t));
340 perror("Not enough memory");
344 /* Then thread attributes (all defaults for now) */
345 pthread_attr_init(&attr);
346 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
348 /* Then, create some threads */
349 fprintf(stderr, "Creating threads \r");
350 for (i = 0; i < n; ++i) {
351 pthread_create(&threads[i], &attr, worker, (void*)data);
353 /* Give thread #0 time to create the user account */
354 if (i == 0) sleep(3);
357 //fprintf(stderr, "Starting in %d seconds\r", n);
359 fprintf(stderr, " \r");
361 /* Then, signal the conditional they all are waiting on */
362 pthread_mutex_lock(&start_mutex);
363 pthread_cond_broadcast(&start_cond);
364 pthread_mutex_unlock(&start_mutex);
366 /* Then wait for them to exit */
367 for (i = 0; i < n; i++) {
368 pthread_join(threads[i], (void*)&runtime);
369 /* We're ignoring this value for now... TODO */
371 fprintf(stderr, "\r \r");