]> code.citadel.org Git - citadel.git/blob - citadel/stress.c
* stress.c: Fixed. Now properly spawns threads and stresses out your
[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                 gettimeofday(&tv, NULL);
201                 tstart = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
202                 /* Select the room to goto */
203                 pthread_mutex_lock(&rand_mutex);
204                 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
205                 rm = (int)(100.0*rand()/(RAND_MAX+1.0)); /* range 0-99 */
206                 pthread_mutex_unlock(&rand_mutex);
207
208                 /* Goto the selected room */
209                 sprintf(room, "test%d", rm);
210                 /* Create the room if not existing. Ignore the return */
211                 r = CtdlIPCCreateRoom(ipc, 1, room, 0, NULL, 0, aaa);
212                 if (r / 100 != 2 && r != 574) { /* Already exists */
213                         fprintf(stderr, "Citadel refused room create: %s\n", aaa);
214                         pthread_mutex_lock(&count_mutex);
215                         total -= m - c;
216                         pthread_mutex_unlock(&count_mutex);
217                         CtdlIPC_delete_ptr(&ipc);
218                         return NULL;
219                 }
220                 r = CtdlIPCGotoRoom(ipc, room, "", &rret, aaa);
221                 if (r / 100 != 2) {
222                         fprintf(stderr, "Citadel refused room change: %s\n", aaa);
223                         pthread_mutex_lock(&count_mutex);
224                         total -= m - c;
225                         pthread_mutex_unlock(&count_mutex);
226                         CtdlIPC_delete_ptr(&ipc);
227                         return NULL;
228                 }
229
230                 /* Post the message */
231                 r = CtdlIPCPostMessage(ipc, 1, &msg, aaa);
232                 if (r / 100 != 4) {
233                         fprintf(stderr, "Citadel refused message entry: %s\n", aaa);
234                         pthread_mutex_lock(&count_mutex);
235                         total -= m - c;
236                         pthread_mutex_unlock(&count_mutex);
237                         CtdlIPC_delete_ptr(&ipc);
238                         return NULL;
239                 }
240
241                 if (w >= 3) {
242                         /* Do a status update */
243                         pthread_mutex_lock(&count_mutex);
244                         count++;
245                         pthread_mutex_unlock(&count_mutex);
246                         fprintf(stderr, "%d of %d - %d%%        \r",
247                                 count, total,
248                                 (int)(100 * count / total));
249                 }
250                 gettimeofday(&tv, NULL);
251                 tend = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
252                 tend -= tstart;
253                 if (tend < tmin) tmin = tend;
254                 if (tend > tmax) tmax = tend;
255                 trun += tend;
256
257                 /* Wait for a while */
258                 sleep(w);
259         }
260         end = time(NULL);
261         pthread_mutex_lock(&output_mutex);
262         printf("%ld %ld %ld %ld\n", end - start, tmin, trun / c, tmax);
263         pthread_mutex_unlock(&output_mutex);
264         return (void*)(end - start);
265 }
266
267
268 /*
269  * Shift argument list
270  */
271 int shift(int argc, char **argv, int start, int count)
272 {
273         int i;
274
275         for (i = start; i < argc - count; ++i)
276                 argv[i] = argv[i + count];
277         return argc - count;
278 }
279
280
281 /*
282  * Main loop.  Start a shitload of threads, all of which will attempt to
283  * kick a Citadel server square in the nuts.
284  */
285 int main(int argc, char** argv)
286 {
287         void* data[2];          /* pass args to worker thread */
288         pthread_t* threads;     /* A shitload of threads */
289         pthread_attr_t attr;    /* Thread attributes (we use defaults) */
290         int i;                  /* Counters */
291         long runtime;           /* Run time for each thread */
292
293         /* Read argument list */
294         for (i = 0; i < argc; i++) {
295                 if (!strcmp(argv[i], "-n")) {
296                         n = atoi(argv[i + 1]);
297                         argc = shift(argc, argv, i, 2);
298                 }
299                 if (!strcmp(argv[i], "-w")) {
300                         w = atoi(argv[i + 1]);
301                         argc = shift(argc, argv, i, 2);
302                 }
303                 if (!strcmp(argv[i], "-m")) {
304                         m = atoi(argv[i + 1]);
305                         argc = shift(argc, argv, i, 2);
306                 }
307                 if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
308                         fprintf(stderr, "Read stress.c for usage info\n");
309                         return 1;
310                 }
311         }
312
313         data[0] = (void*)argc;  /* pass args to worker thread */
314         data[1] = (void*)argv;  /* pass args to worker thread */
315
316         /* This is how many total messages will be posted */
317         total = n * m;
318
319         /* Pick a randomized username */
320         pthread_mutex_lock(&rand_mutex);
321         /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
322         i = (int)(100.0*rand()/(RAND_MAX+1.0)); /* range 0-99 */
323         pthread_mutex_unlock(&rand_mutex);
324         sprintf(username, "testuser%d", i);
325         strcpy(password, username);
326
327         /* First, memory for our shitload of threads */
328         threads = calloc(n, sizeof(pthread_t));
329         if (!threads) {
330                 perror("Not enough memory");
331                 return 1;
332         }
333
334         /* Then thread attributes (all defaults for now) */
335         pthread_attr_init(&attr);
336         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
337
338         /* Then, create some threads */
339         for (i = 0; i < n; ++i) {
340                 pthread_create(&threads[i], &attr, worker, (void*)data);
341         }
342
343         fprintf(stderr, "Starting in 10 seconds\r");
344         sleep(10);
345         fprintf(stderr, "                      \r");
346
347         /* Then, signal the conditional they all are waiting on */
348         pthread_mutex_lock(&start_mutex);
349         pthread_cond_broadcast(&start_cond);
350         pthread_mutex_unlock(&start_mutex);
351
352         /* Then wait for them to exit */
353         for (i = 0; i < n; i++) {
354                 pthread_join(threads[i], (void*)&runtime);
355                 /* We're ignoring this value for now... TODO */
356         }
357         fprintf(stderr, "\r                                                                               \r");
358         return 0;
359 }