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;
91 static char username[12];
92 static char password[12];
94 static char* hostname = NULL;
95 static char* portname = NULL;
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;
112 * connection died; hang it up
115 void connection_died(CtdlIPC* ipc, int using_ssl)
124 * This is the worker thread. It logs in and creates the 1,000 messages
125 * as described above.
127 void* worker(void* data)
129 CtdlIPC* ipc; /* My connection to the server */
130 void** args; /* Args sent in */
131 int r; /* IPC return code */
132 char aaa[SIZ]; /* Generic buffer */
133 int c; /* Message count */
134 time_t start, end; /* Timestamps */
135 struct ctdlipcmessage msg; /* The message we will post */
140 argc_ = (int*)args[0];
141 argv_ = (char***)args[1];
142 fprintf(stderr, "Set argc to %x\n", argc_);
143 fprintf(stderr, "Set argv to %x\n", argv_);
145 /* Setup the message we will be posting */
146 msg.text = (char*)message;
149 strcpy(msg.recipient, "");
150 strcpy(msg.subject, "Test message; ignore");
151 strcpy(msg.author, username);
153 fprintf(stderr, "Trying to connect to Citadel\n");
154 ipc = CtdlIPC_new(*argc_, *argv_, "", "");
156 return NULL; /* oops, something happened... */
158 fprintf(stderr, "Trying to login to Citadel\n");
159 CtdlIPC_chat_recv(ipc, aaa);
161 fprintf(stderr, "Citadel refused me: %s\n", &aaa[4]);
162 return NULL; /* server ran out of connections maybe? */
165 CtdlIPCIdentifySoftware(ipc, 8, 8, REV_LEVEL, "Citadel stress tester",
166 "localhost", aaa); /* we're lying, the server knows */
168 r = CtdlIPCQueryUsername(ipc, username, aaa);
170 /* testuser already exists (from previous run?) */
171 r = CtdlIPCTryLogin(ipc, username, aaa);
173 fprintf(stderr, "Citadel refused username: %s\n", aaa);
174 CtdlIPC_delete_ptr(&ipc);
175 return NULL; /* Gawd only knows what went wrong */
177 r = CtdlIPCTryPassword(ipc, password, aaa);
179 fprintf(stderr, "Citadel refused password: %s\n", aaa);
180 CtdlIPC_delete_ptr(&ipc);
181 return NULL; /* Gawd only knows what went wrong */
184 /* testuser doesn't yet exist */
185 r = CtdlIPCCreateUser(ipc, username, 1, aaa);
187 fprintf(stderr, "Citadel refused create user: %s\n", aaa);
188 CtdlIPC_delete_ptr(&ipc);
189 return NULL; /* Gawd only knows what went wrong */
191 r = CtdlIPCChangePassword(ipc, password, aaa);
193 fprintf(stderr, "Citadel refused change password: %s\n", aaa);
194 CtdlIPC_delete_ptr(&ipc);
195 return NULL; /* Gawd only knows what went wrong */
199 /* Wait for the rest of the threads */
200 pthread_cond_wait(&start_cond, &start_mutex);
202 /* And now the fun begins! Send out a whole shitload of messages */
204 for (c = 0; c < m; c++) {
207 struct ctdlipcroom *rret;
209 /* Select the room to goto */
210 pthread_mutex_lock(&rand_mutex);
211 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
212 rm = (int)(99.0*rand()/(RAND_MAX+1.0));
213 pthread_mutex_unlock(&rand_mutex);
215 /* Goto the selected room */
216 sprintf(room, "test%d", rm);
217 r = CtdlIPCGotoRoom(ipc, room, "", &rret, aaa);
219 fprintf(stderr, "Citadel refused room change: %s\n", aaa);
220 CtdlIPC_delete_ptr(&ipc);
224 /* Post the message */
225 r = CtdlIPCPostMessage(ipc, 1, &msg, aaa);
227 fprintf(stderr, "Citadel refused message entry: %s\n", aaa);
228 CtdlIPC_delete_ptr(&ipc);
233 /* Do a status update */
234 pthread_mutex_lock(&count_mutex);
236 pthread_mutex_unlock(&count_mutex);
238 fprintf(stderr, "%d of %d - %d%% \r",
240 (int)(100 * count / total));
242 /* Wait for a while */
246 printf("%ld\n", end - start);
247 return (void*)(end - start);
252 * Shift argument list
254 int shift(int argc, char **argv, int start, int count)
258 for (i = start; i < argc - count; ++i)
259 argv[i] = argv[i + count];
265 * Main loop. Start a shitload of threads, all of which will attempt to
266 * kick a Citadel server square in the nuts.
268 int main(int argc, char** argv)
270 void* data[2]; /* pass args to worker thread */
271 pthread_t** threads; /* A shitload of threads */
272 pthread_attr_t attr; /* Thread attributes (we use defaults) */
273 int i; /* Counters */
274 long runtime; /* Run time for each thread */
276 /* Read argument list */
277 for (i = 0; i < argc; i++) {
278 if (!strcmp(argv[i], "-n")) {
279 n = atoi(argv[i + 1]);
280 argc = shift(argc, argv, i, 2);
282 if (!strcmp(argv[i], "-w")) {
283 w = atoi(argv[i + 1]);
284 argc = shift(argc, argv, i, 2);
286 if (!strcmp(argv[i], "-m")) {
287 m = atoi(argv[i + 1]);
288 argc = shift(argc, argv, i, 2);
290 if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
291 fprintf(stderr, "Read stress.c for usage info\n");
296 data[0] = (void*)argc; /* pass args to worker thread */
297 data[1] = (void*)argv; /* pass args to worker thread */
298 fprintf(stderr, "Set data[0] to %x\n", data[0]);
299 fprintf(stderr, "Set data[1] to %x\n", data[1]);
300 fprintf(stderr, "Data is at %x\n", data);
302 /* This is how many total messages will be posted */
305 /* Pick a randomized username */
306 pthread_mutex_lock(&rand_mutex);
307 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
308 i = (int)(99.0*rand()/(RAND_MAX+1.0));
309 pthread_mutex_unlock(&rand_mutex);
310 sprintf(username, "testuser%d", i);
311 strcpy(password, username);
313 fprintf(stderr, "Allocating threads\n");
314 /* First, memory for our shitload of threads */
315 threads = calloc(n, sizeof(pthread_t*));
317 perror("Not enough memory");
321 /* Then thread attributes (all defaults for now) */
322 pthread_attr_init(&attr);
324 fprintf(stderr, "Creating threads\n");
325 /* Then, create some threads */
326 for (i = 0; i < n; ++i) {
327 fprintf(stderr, "\rCreating thread %d", i);
328 pthread_create(threads[i], &attr, worker, (void*)data);
331 fprintf(stderr, "Starting in 10 seconds\r");
333 fprintf(stderr, " \r");
335 /* Then, signal the conditional they all are waiting on */
336 pthread_mutex_lock(&start_mutex);
337 pthread_cond_broadcast(&start_cond);
338 pthread_mutex_unlock(&start_mutex);
340 /* Then wait for them to exit */
341 for (i = 0; i < n; i++) {
342 pthread_join(*(threads[i]), (void*)&runtime);
343 /* We're ignoring this value for now... TODO */
345 fprintf(stderr, "\r \r");