4fd04619156e61561a06493d2cfdba7186780e5e
[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 static pthread_mutex_t arg_mutex = PTHREAD_MUTEX_INITIALIZER;
91
92 static char username[12];
93 static char password[12];
94
95 static char* hostname = NULL;
96 static char* portname = NULL;
97
98 /*
99  * Mutex for the random number generator
100  * We don't assume that rand_r() is present, so we have to
101  * provide our own locking for rand()
102  */
103 static pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER;
104
105 /*
106  * Conditional.  All the threads wait for this signal to actually
107  * start bombarding the server.
108  */
109 static pthread_mutex_t start_mutex = PTHREAD_MUTEX_INITIALIZER;
110 static pthread_cond_t start_cond = PTHREAD_COND_INITIALIZER;
111
112 /*
113  * connection died; hang it up
114  */
115 #if 0
116 void connection_died(CtdlIPC* ipc, int using_ssl)
117 {
118         CtdlIPC_delete(ipc);
119         pthread_exit(NULL);
120 }
121 #endif
122
123
124 /*
125  * This is the worker thread.  It logs in and creates the 1,000 messages
126  * as described above.
127  */
128 void* worker(void* data)
129 {
130         CtdlIPC* ipc;   /* My connection to the server */
131         void** args;    /* Args sent in */
132         int r;          /* IPC return code */
133         char aaa[SIZ];  /* Generic buffer */
134         int c;          /* Message count */
135         time_t start, end;      /* Timestamps */
136         struct ctdlipcmessage msg;      /* The message we will post */
137         int* argc_;
138         char*** argv_;
139
140         args = (void*)data;
141         argc_ = (int*)args[0];
142         argv_ = (char***)args[1];
143         fprintf(stderr, "Set argc to %x\n", argc_);
144         fprintf(stderr, "Set argv to %x\n", argv_);
145
146         /* Setup the message we will be posting */
147         msg.text = (char*)message;
148         msg.anonymous = 0;
149         msg.type = 1;
150         strcpy(msg.recipient, "");
151         strcpy(msg.subject, "Test message; ignore");
152         strcpy(msg.author, username);
153
154         fprintf(stderr, "Trying to connect to Citadel\n");
155         pthread_mutex_lock(&arg_mutex);
156         ipc = CtdlIPC_new(*argc_, *argv_, "", "");
157         pthread_mutex_unlock(&arg_mutex);
158         if (!ipc)
159                 return NULL;    /* oops, something happened... */
160
161         fprintf(stderr, "Trying to login to Citadel\n");
162         CtdlIPC_chat_recv(ipc, aaa);
163         if (aaa[0] != '2') {
164                 fprintf(stderr, "Citadel refused me: %s\n", &aaa[4]);
165                 return NULL;    /* server ran out of connections maybe? */
166         }
167
168         CtdlIPCIdentifySoftware(ipc, 8, 8, REV_LEVEL, "Citadel stress tester",
169                 "localhost", aaa);      /* we're lying, the server knows */
170         
171         r = CtdlIPCQueryUsername(ipc, username, aaa);
172         if (r / 100 == 2) {
173                 /* testuser already exists (from previous run?) */
174                 r = CtdlIPCTryLogin(ipc, username, aaa);
175                 if (r / 100 != 3) {
176                         fprintf(stderr, "Citadel refused username: %s\n", aaa);
177                         CtdlIPC_delete_ptr(&ipc);
178                         return NULL;    /* Gawd only knows what went wrong */
179                 }
180                 r = CtdlIPCTryPassword(ipc, password, aaa);
181                 if (r / 100 != 2) {
182                         fprintf(stderr, "Citadel refused password: %s\n", aaa);
183                         CtdlIPC_delete_ptr(&ipc);
184                         return NULL;    /* Gawd only knows what went wrong */
185                 }
186         } else {
187                 /* testuser doesn't yet exist */
188                 r = CtdlIPCCreateUser(ipc, username, 1, aaa);
189                 if (r / 100 != 2) {
190                         fprintf(stderr, "Citadel refused create user: %s\n", aaa);
191                         CtdlIPC_delete_ptr(&ipc);
192                         return NULL;    /* Gawd only knows what went wrong */
193                 }
194                 r = CtdlIPCChangePassword(ipc, password, aaa);
195                 if (r / 100 != 2) {
196                         fprintf(stderr, "Citadel refused change password: %s\n", aaa);
197                         CtdlIPC_delete_ptr(&ipc);
198                         return NULL;    /* Gawd only knows what went wrong */
199                 }
200         }
201
202         /* Wait for the rest of the threads */
203         pthread_cond_wait(&start_cond, &start_mutex);
204
205         /* And now the fun begins!  Send out a whole shitload of messages */
206         start = time(NULL);
207         for (c = 0; c < m; c++) {
208                 int rm;
209                 char room[7];
210                 struct ctdlipcroom *rret;
211
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)(99.0*rand()/(RAND_MAX+1.0));
216                 pthread_mutex_unlock(&rand_mutex);
217
218                 /* Goto the selected room */
219                 sprintf(room, "test%d", rm);
220                 r = CtdlIPCGotoRoom(ipc, room, "", &rret, aaa);
221                 if (r / 100 != 2) {
222                         fprintf(stderr, "Citadel refused room change: %s\n", aaa);
223                         CtdlIPC_delete_ptr(&ipc);
224                         return NULL;
225                 }
226
227                 /* Post the message */
228                 r = CtdlIPCPostMessage(ipc, 1, &msg, aaa);
229                 if (r / 100 != 4) {
230                         fprintf(stderr, "Citadel refused message entry: %s\n", aaa);
231                         CtdlIPC_delete_ptr(&ipc);
232                         return NULL;
233                 }
234
235                 if (w >= 3) {
236                         /* Do a status update */
237                         pthread_mutex_lock(&count_mutex);
238                         count++;
239                         pthread_mutex_unlock(&count_mutex);
240                         if (!(count % 20))
241                                 fprintf(stderr, "%d of %d - %d%%        \r",
242                                         count, total,
243                                         (int)(100 * count / total));
244                 }
245                 /* Wait for a while */
246                 sleep(w);
247         }
248         end = time(NULL);
249         printf("%ld\n", end - start);
250         return (void*)(end - start);
251 }
252
253
254 /*
255  * Shift argument list
256  */
257 int shift(int argc, char **argv, int start, int count)
258 {
259         int i;
260
261         for (i = start; i < argc - count; ++i)
262                 argv[i] = argv[i + count];
263         return argc - count;
264 }
265
266
267 /*
268  * Main loop.  Start a shitload of threads, all of which will attempt to
269  * kick a Citadel server square in the nuts.
270  */
271 int main(int argc, char** argv)
272 {
273         void* data[2];          /* pass args to worker thread */
274         pthread_t** threads;    /* A shitload of threads */
275         pthread_attr_t attr;    /* Thread attributes (we use defaults) */
276         int i;                  /* Counters */
277         long runtime;           /* Run time for each thread */
278
279         setvbuf(stderr, NULL, _IONBF, 0);
280
281         /* Read argument list */
282         for (i = 0; i < argc; i++) {
283                 if (!strcmp(argv[i], "-n")) {
284                         n = atoi(argv[i + 1]);
285                         argc = shift(argc, argv, i, 2);
286                 }
287                 if (!strcmp(argv[i], "-w")) {
288                         w = atoi(argv[i + 1]);
289                         argc = shift(argc, argv, i, 2);
290                 }
291                 if (!strcmp(argv[i], "-m")) {
292                         m = atoi(argv[i + 1]);
293                         argc = shift(argc, argv, i, 2);
294                 }
295                 if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
296                         fprintf(stderr, "Read stress.c for usage info\n");
297                         return 1;
298                 }
299         }
300
301         data[0] = (void*)argc;  /* pass args to worker thread */
302         data[1] = (void*)argv;  /* pass args to worker thread */
303         fprintf(stderr, "Set data[0] to %x\n", data[0]);
304         fprintf(stderr, "Set data[1] to %x\n", data[1]);
305         fprintf(stderr, "Data is at %x\n", data);
306
307         /* This is how many total messages will be posted */
308         total = n * m;
309
310         /* Pick a randomized username */
311         pthread_mutex_lock(&rand_mutex);
312         /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
313         i = (int)(99.0*rand()/(RAND_MAX+1.0));
314         pthread_mutex_unlock(&rand_mutex);
315         sprintf(username, "testuser%d", i);
316         strcpy(password, username);
317
318         fprintf(stderr, "Allocating threads\n");
319         /* First, memory for our shitload of threads */
320         threads = calloc(n, sizeof(pthread_t*));
321         if (!threads) {
322                 perror("Not enough memory");
323                 return 1;
324         }
325
326         /* Then thread attributes (all defaults for now) */
327         pthread_attr_init(&attr);
328         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
329
330         /* Then, create some threads */
331         for (i = 0; i < n; ++i) {
332                 fprintf(stderr, "\rCreating thread %d", i);
333                 pthread_create(threads[i], &attr, worker, (void*)data);
334         }
335
336         fprintf(stderr, "Starting in 10 seconds\r");
337         sleep(10);
338         fprintf(stderr, "                      \r");
339
340         /* Then, signal the conditional they all are waiting on */
341         pthread_mutex_lock(&start_mutex);
342         pthread_cond_broadcast(&start_cond);
343         pthread_mutex_unlock(&start_mutex);
344
345         /* Then wait for them to exit */
346         for (i = 0; i < n; i++) {
347                 pthread_join(*(threads[i]), (void*)&runtime);
348                 /* We're ignoring this value for now... TODO */
349         }
350         fprintf(stderr, "\r                                                                               \r");
351         return 0;
352 }