]> code.citadel.org Git - citadel.git/blob - citadel/stress.c
* stress.c: silence warning
[citadel.git] / citadel / stress.c
1 /* $Id$ */
2
3 /* This message is exactly 1024 bytes */
4 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 <stdlib.h>
64 #include <unistd.h>
65 #include <stdio.h>
66 #include <sys/types.h>
67 #include <string.h>
68 #include "sysdep.h"
69 #if TIME_WITH_SYS_TIME
70 # include <sys/time.h>
71 # include <time.h>
72 #else
73 # if HAVE_SYS_TIME_H
74 #  include <sys/time.h>
75 # else
76 #  include <time.h>
77 # endif
78 #endif
79 #include "citadel_ipc.h"
80
81 #ifndef HAVE_PTHREAD_H
82 #error This program requires threads
83 #endif
84
85 static int w = 10;              /* see above */
86 static int n = 100;             /* see above */
87 static int m = 1000;            /* Number of messages to send; see above */
88 static volatile int count = 0;  /* Total count of messages posted */
89 static volatile int total = 0;  /* Total messages to be posted */
90 static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
91 static pthread_mutex_t arg_mutex = PTHREAD_MUTEX_INITIALIZER;
92 static pthread_mutex_t output_mutex = PTHREAD_MUTEX_INITIALIZER;
93
94 static char username[12];
95 static char password[12];
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 /*
113  * This is the worker thread.  It logs in and creates the 1,000 messages
114  * as described above.
115  */
116 void* worker(void* data)
117 {
118         CtdlIPC* ipc;   /* My connection to the server */
119         void** args;    /* Args sent in */
120         int r;          /* IPC return code */
121         char aaa[SIZ];  /* Generic buffer */
122         int c;          /* Message count */
123         time_t start, end;      /* Timestamps */
124         struct ctdlipcmessage msg;      /* The message we will post */
125         int argc_;
126         char** argv_;
127         long tmin = LONG_MAX, trun = 0, tmax = LONG_MIN;
128
129         args = (void*)data;
130         argc_ = (int)args[0];
131         argv_ = (char**)args[1];
132
133         /* Setup the message we will be posting */
134         msg.text = message;
135         msg.anonymous = 0;
136         msg.type = 1;
137         strcpy(msg.recipient, "");
138         strcpy(msg.subject, "Test message; ignore");
139         strcpy(msg.author, username);
140
141         pthread_mutex_lock(&arg_mutex);
142         ipc = CtdlIPC_new(argc_, argv_, NULL, NULL);
143         pthread_mutex_unlock(&arg_mutex);
144         if (!ipc)
145                 return NULL;    /* oops, something happened... */
146
147         CtdlIPC_chat_recv(ipc, aaa);
148         if (aaa[0] != '2') {
149                 fprintf(stderr, "Citadel refused me: %s\n", &aaa[4]);
150                 return NULL;    /* server ran out of connections maybe? */
151         }
152
153         CtdlIPCIdentifySoftware(ipc, 8, 8, REV_LEVEL, "Citadel stress tester",
154                 "localhost", aaa);      /* we're lying, the server knows */
155         
156         r = CtdlIPCQueryUsername(ipc, username, aaa);
157         if (r / 100 == 2) {
158                 /* testuser already exists (from previous run?) */
159                 r = CtdlIPCTryLogin(ipc, username, aaa);
160                 if (r / 100 != 3) {
161                         fprintf(stderr, "Citadel refused username: %s\n", aaa);
162                         CtdlIPC_delete_ptr(&ipc);
163                         return NULL;    /* Gawd only knows what went wrong */
164                 }
165                 r = CtdlIPCTryPassword(ipc, password, aaa);
166                 if (r / 100 != 2) {
167                         fprintf(stderr, "Citadel refused password: %s\n", aaa);
168                         CtdlIPC_delete_ptr(&ipc);
169                         return NULL;    /* Gawd only knows what went wrong */
170                 }
171         } else {
172                 /* testuser doesn't yet exist */
173                 r = CtdlIPCCreateUser(ipc, username, 1, aaa);
174                 if (r / 100 != 2) {
175                         fprintf(stderr, "Citadel refused create user: %s\n", aaa);
176                         CtdlIPC_delete_ptr(&ipc);
177                         return NULL;    /* Gawd only knows what went wrong */
178                 }
179                 r = CtdlIPCChangePassword(ipc, password, aaa);
180                 if (r / 100 != 2) {
181                         fprintf(stderr, "Citadel refused change password: %s\n", aaa);
182                         CtdlIPC_delete_ptr(&ipc);
183                         return NULL;    /* Gawd only knows what went wrong */
184                 }
185         }
186
187         /* Wait for the rest of the threads */
188         pthread_mutex_lock(&start_mutex);
189         pthread_cond_wait(&start_cond, &start_mutex);
190         pthread_mutex_unlock(&start_mutex);
191
192         /* And now the fun begins!  Send out a whole shitload of messages */
193         start = time(NULL);
194         for (c = 0; c < m; c++) {
195                 int rm;
196                 char room[7];
197                 struct ctdlipcroom *rret;
198                 struct timeval tv;
199                 long tstart, tend;
200                 int wait;
201
202                 /* Wait for a while */
203                 pthread_mutex_lock(&rand_mutex);
204                 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
205                 /* Randomize between w/3 to w*3 (yes, it's complicated) */
206                 wait = (int)((1.0+2.7*(float)w)*rand()/(RAND_MAX+(float)w/3.0)); /* range 0-99 */
207                 pthread_mutex_unlock(&rand_mutex);
208                 sleep(wait);
209
210                 gettimeofday(&tv, NULL);
211                 tstart = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
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);
217
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);
225                         total -= m - c;
226                         pthread_mutex_unlock(&count_mutex);
227                         CtdlIPC_delete_ptr(&ipc);
228                         return NULL;
229                 }
230                 r = CtdlIPCGotoRoom(ipc, room, "", &rret, aaa);
231                 if (r / 100 != 2) {
232                         fprintf(stderr, "Citadel refused room change: %s\n", aaa);
233                         pthread_mutex_lock(&count_mutex);
234                         total -= m - c;
235                         pthread_mutex_unlock(&count_mutex);
236                         CtdlIPC_delete_ptr(&ipc);
237                         return NULL;
238                 }
239
240                 /* Post the message */
241                 r = CtdlIPCPostMessage(ipc, 1, &msg, aaa);
242                 if (r / 100 != 4) {
243                         fprintf(stderr, "Citadel refused message entry: %s\n", aaa);
244                         pthread_mutex_lock(&count_mutex);
245                         total -= m - c;
246                         pthread_mutex_unlock(&count_mutex);
247                         CtdlIPC_delete_ptr(&ipc);
248                         return NULL;
249                 }
250
251                 /* Do a status update */
252                 pthread_mutex_lock(&count_mutex);
253                 count++;
254                 pthread_mutex_unlock(&count_mutex);
255                 fprintf(stderr, "%d of %d - %d%%        \r",
256                         count, total,
257                         (int)(100 * count / total));
258                 gettimeofday(&tv, NULL);
259                 tend = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
260                 tend -= tstart;
261                 if (tend < tmin) tmin = tend;
262                 if (tend > tmax) tmax = tend;
263                 trun += tend;
264         }
265         end = time(NULL);
266         pthread_mutex_lock(&output_mutex);
267         printf("%ld %ld %ld %ld\n", end - start, tmin, trun / c, tmax);
268         pthread_mutex_unlock(&output_mutex);
269         return (void*)(end - start);
270 }
271
272
273 /*
274  * Shift argument list
275  */
276 int shift(int argc, char **argv, int start, int count)
277 {
278         int i;
279
280         for (i = start; i < argc - count; ++i)
281                 argv[i] = argv[i + count];
282         return argc - count;
283 }
284
285
286 /*
287  * Main loop.  Start a shitload of threads, all of which will attempt to
288  * kick a Citadel server square in the nuts.
289  */
290 int main(int argc, char** argv)
291 {
292         void* data[2];          /* pass args to worker thread */
293         pthread_t* threads;     /* A shitload of threads */
294         pthread_attr_t attr;    /* Thread attributes (we use defaults) */
295         int i;                  /* Counters */
296         long runtime;           /* Run time for each thread */
297
298         /* Read argument list */
299         for (i = 0; i < argc; i++) {
300                 if (!strcmp(argv[i], "-n")) {
301                         n = atoi(argv[i + 1]);
302                         argc = shift(argc, argv, i, 2);
303                 }
304                 if (!strcmp(argv[i], "-w")) {
305                         w = atoi(argv[i + 1]);
306                         argc = shift(argc, argv, i, 2);
307                 }
308                 if (!strcmp(argv[i], "-m")) {
309                         m = atoi(argv[i + 1]);
310                         argc = shift(argc, argv, i, 2);
311                 }
312                 if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
313                         fprintf(stderr, "Read stress.c for usage info\n");
314                         return 1;
315                 }
316         }
317
318         data[0] = (void*)argc;  /* pass args to worker thread */
319         data[1] = (void*)argv;  /* pass args to worker thread */
320
321         /* This is how many total messages will be posted */
322         total = n * m;
323
324         /* Pick a randomized username */
325         pthread_mutex_lock(&rand_mutex);
326         /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
327         i = (int)(100.0*rand()/(RAND_MAX+1.0)); /* range 0-99 */
328         pthread_mutex_unlock(&rand_mutex);
329         sprintf(username, "testuser%d", i);
330         strcpy(password, username);
331
332         /* First, memory for our shitload of threads */
333         threads = calloc(n, sizeof(pthread_t));
334         if (!threads) {
335                 perror("Not enough memory");
336                 return 1;
337         }
338
339         /* Then thread attributes (all defaults for now) */
340         pthread_attr_init(&attr);
341         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
342
343         /* Then, create some threads */
344         fprintf(stderr, "Creating threads      \r");
345         for (i = 0; i < n; ++i) {
346                 pthread_create(&threads[i], &attr, worker, (void*)data);
347                 
348                 /* Give thread #0 time to create the user account */
349                 if (i == 0) sleep(3);
350         }
351
352         fprintf(stderr, "Starting in 10 seconds\r");
353         sleep(10);
354         fprintf(stderr, "                      \r");
355
356         /* Then, signal the conditional they all are waiting on */
357         pthread_mutex_lock(&start_mutex);
358         pthread_cond_broadcast(&start_cond);
359         pthread_mutex_unlock(&start_mutex);
360
361         /* Then wait for them to exit */
362         for (i = 0; i < n; i++) {
363                 pthread_join(threads[i], (void*)&runtime);
364                 /* We're ignoring this value for now... TODO */
365         }
366         fprintf(stderr, "\r                                                                               \r");
367         return 0;
368 }