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