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