]> code.citadel.org Git - citadel.git/blob - citadel/stress.c
9c709e64387a95e1a293dc6274709ef8cd641326
[citadel.git] / citadel / stress.c
1 /* $Id$ */
2
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"
8 "\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"
11 "many threads!\n"
12 "\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"
16 "\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"
22 "\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"
25 "\n"
26 "Example:\n"
27 "stress -n 500 -w 25 myserver > stress.csv\n";
28
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.
37  *
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.
41  *
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.
47  *
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.
55  *
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.
59  *
60  * Once all threads have exited, the program exits.
61  */
62
63 #include <stdio.h>
64 #include <unistd.h>
65 #include <sys/types.h>
66 #include <string.h>
67 #include "sysdep.h"
68 #if TIME_WITH_SYS_TIME
69 # include <sys/time.h>
70 # include <time.h>
71 #else
72 # if HAVE_SYS_TIME_H
73 #  include <sys/time.h>
74 # else
75 #  include <time.h>
76 # endif
77 #endif
78 #include "citadel_ipc.h"
79
80 #ifndef HAVE_PTHREAD_H
81 #error This program requires threads
82 #endif
83
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
91 static char username[12];
92 static char password[12];
93
94 static char* hostname = NULL;
95 static char* portname = NULL;
96
97 /*
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()
101  */
102 static pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER;
103
104 /*
105  * Conditional.  All the threads wait for this signal to actually
106  * start bombarding the server.
107  */
108 static pthread_mutex_t start_mutex = PTHREAD_MUTEX_INITIALIZER;
109 static pthread_cond_t start_cond = PTHREAD_COND_INITIALIZER;
110
111 /*
112  * connection died; hang it up
113  */
114 #if 0
115 void connection_died(CtdlIPC* ipc, int using_ssl)
116 {
117         CtdlIPC_delete(ipc);
118         pthread_exit(NULL);
119 }
120 #endif
121
122
123 /*
124  * This is the worker thread.  It logs in and creates the 1,000 messages
125  * as described above.
126  */
127 void* worker(void* data)
128 {
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 */
136         int* argc_;
137         char*** argv_;
138
139         args = (void*)data;
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_);
144
145         /* Setup the message we will be posting */
146         msg.text = (char*)message;
147         msg.anonymous = 0;
148         msg.type = 1;
149         strcpy(msg.recipient, "");
150         strcpy(msg.subject, "Test message; ignore");
151         strcpy(msg.author, username);
152
153         fprintf(stderr, "Trying to connect to Citadel\n");
154         ipc = CtdlIPC_new(*argc_, *argv_, "", "");
155         if (!ipc)
156                 return NULL;    /* oops, something happened... */
157
158         fprintf(stderr, "Trying to login to Citadel\n");
159         CtdlIPC_chat_recv(ipc, aaa);
160         if (aaa[0] != '2') {
161                 fprintf(stderr, "Citadel refused me: %s\n", &aaa[4]);
162                 return NULL;    /* server ran out of connections maybe? */
163         }
164
165         CtdlIPCIdentifySoftware(ipc, 8, 8, REV_LEVEL, "Citadel stress tester",
166                 "localhost", aaa);      /* we're lying, the server knows */
167         
168         r = CtdlIPCQueryUsername(ipc, username, aaa);
169         if (r / 100 == 2) {
170                 /* testuser already exists (from previous run?) */
171                 r = CtdlIPCTryLogin(ipc, username, aaa);
172                 if (r / 100 != 3) {
173                         fprintf(stderr, "Citadel refused username: %s\n", aaa);
174                         CtdlIPC_delete_ptr(&ipc);
175                         return NULL;    /* Gawd only knows what went wrong */
176                 }
177                 r = CtdlIPCTryPassword(ipc, password, aaa);
178                 if (r / 100 != 2) {
179                         fprintf(stderr, "Citadel refused password: %s\n", aaa);
180                         CtdlIPC_delete_ptr(&ipc);
181                         return NULL;    /* Gawd only knows what went wrong */
182                 }
183         } else {
184                 /* testuser doesn't yet exist */
185                 r = CtdlIPCCreateUser(ipc, username, 1, aaa);
186                 if (r / 100 != 2) {
187                         fprintf(stderr, "Citadel refused create user: %s\n", aaa);
188                         CtdlIPC_delete_ptr(&ipc);
189                         return NULL;    /* Gawd only knows what went wrong */
190                 }
191                 r = CtdlIPCChangePassword(ipc, password, aaa);
192                 if (r / 100 != 2) {
193                         fprintf(stderr, "Citadel refused change password: %s\n", aaa);
194                         CtdlIPC_delete_ptr(&ipc);
195                         return NULL;    /* Gawd only knows what went wrong */
196                 }
197         }
198
199         /* Wait for the rest of the threads */
200         pthread_cond_wait(&start_cond, &start_mutex);
201
202         /* And now the fun begins!  Send out a whole shitload of messages */
203         start = time(NULL);
204         for (c = 0; c < m; c++) {
205                 int rm;
206                 char room[7];
207                 struct ctdlipcroom *rret;
208
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);
214
215                 /* Goto the selected room */
216                 sprintf(room, "test%d", rm);
217                 r = CtdlIPCGotoRoom(ipc, room, "", &rret, aaa);
218                 if (r / 100 != 2) {
219                         fprintf(stderr, "Citadel refused room change: %s\n", aaa);
220                         CtdlIPC_delete_ptr(&ipc);
221                         return NULL;
222                 }
223
224                 /* Post the message */
225                 r = CtdlIPCPostMessage(ipc, 1, &msg, aaa);
226                 if (r / 100 != 4) {
227                         fprintf(stderr, "Citadel refused message entry: %s\n", aaa);
228                         CtdlIPC_delete_ptr(&ipc);
229                         return NULL;
230                 }
231
232                 if (w >= 3) {
233                         /* Do a status update */
234                         pthread_mutex_lock(&count_mutex);
235                         count++;
236                         pthread_mutex_unlock(&count_mutex);
237                         if (!(count % 20))
238                                 fprintf(stderr, "%d of %d - %d%%        \r",
239                                         count, total,
240                                         (int)(100 * count / total));
241                 }
242                 /* Wait for a while */
243                 sleep(w);
244         }
245         end = time(NULL);
246         printf("%ld\n", end - start);
247         return (void*)(end - start);
248 }
249
250
251 /*
252  * Shift argument list
253  */
254 int shift(int argc, char **argv, int start, int count)
255 {
256         int i;
257
258         for (i = start; i < argc - count; ++i)
259                 argv[i] = argv[i + count];
260         return argc - count;
261 }
262
263
264 /*
265  * Main loop.  Start a shitload of threads, all of which will attempt to
266  * kick a Citadel server square in the nuts.
267  */
268 int main(int argc, char** argv)
269 {
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 */
275
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);
281                 }
282                 if (!strcmp(argv[i], "-w")) {
283                         w = atoi(argv[i + 1]);
284                         argc = shift(argc, argv, i, 2);
285                 }
286                 if (!strcmp(argv[i], "-m")) {
287                         m = atoi(argv[i + 1]);
288                         argc = shift(argc, argv, i, 2);
289                 }
290                 if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
291                         fprintf(stderr, "Read stress.c for usage info\n");
292                         return 1;
293                 }
294         }
295
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);
301
302         /* This is how many total messages will be posted */
303         total = n * m;
304
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);
312
313         fprintf(stderr, "Allocating threads\n");
314         /* First, memory for our shitload of threads */
315         threads = calloc(n, sizeof(pthread_t*));
316         if (!threads) {
317                 perror("Not enough memory");
318                 return 1;
319         }
320
321         /* Then thread attributes (all defaults for now) */
322         pthread_attr_init(&attr);
323
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);
329         }
330
331         fprintf(stderr, "Starting in 10 seconds\r");
332         sleep(10);
333         fprintf(stderr, "                      \r");
334
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);
339
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 */
344         }
345         fprintf(stderr, "\r                                                                               \r");
346         return 0;
347 }