]> code.citadel.org Git - citadel.git/blob - citadel/stress.c
* First draft of the stress tester. Isn't quite ready (it needs to be
[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 2000).  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 "sysdep.h"
67 #if TIME_WITH_SYS_TIME
68 # include <sys/time.h>
69 # include <time.h>
70 #else
71 # if HAVE_SYS_TIME_H
72 #  include <sys/time.h>
73 # else
74 #  include <time.h>
75 # endif
76 #endif
77 #include "citadel_ipc.h"
78
79 #ifndef HAVE_PTHREAD_H
80 #error This program requires threads
81 #endif
82
83 static int w = 10;              /* see above */
84 static int n = 2000;            /* see above */
85 static int m = 1000;            /* Number of messages to send; see above */
86
87 static char* username = NULL;
88 static char* password = NULL;
89
90 static char* hostname = NULL;
91 static char* portname = NULL;
92
93 /*
94  * Mutex for the random number generator
95  * We don't assume that rand_r() is present, so we have to
96  * provide our own locking for rand()
97  */
98 static pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER;
99
100 /*
101  * Conditional.  All the threads wait for this signal to actually
102  * start bombarding the server.
103  */
104 static pthread_mutex_t start_mutex = PTHREAD_MUTEX_INITIALIZER;
105 static pthread_cond_t start_cond = PTHREAD_COND_INITIALIZER;
106
107 /*
108  * connection died; hang it up
109  */
110 #if 0
111 void connection_died(CtdlIPC* ipc, int using_ssl)
112 {
113         CtdlIPC_delete(ipc);
114         pthread_exit(NULL);
115 }
116 #endif
117
118
119 /*
120  * This is the worker thread.  It logs in and creates the 1,000 messages
121  * as described above.
122  */
123 void* worker(void* data)
124 {
125         CtdlIPC* ipc;   /* My connection to the server */
126         int r;          /* IPC return code */
127         char aaa[SIZ];  /* Generic buffer */
128         int c;          /* Message count */
129         time_t start, end;      /* Timestamps */
130         struct ctdlipcmessage msg;      /* The message we will post */
131         int* argc_;
132         char*** argv_;
133
134         argc_ = (int*)data;
135         argv_ = (char***)(data + 1);
136
137         /* Setup the message we will be posting */
138         msg.text = (char*)message;
139         msg.anonymous = 0;
140         msg.type = 1;
141         strcpy(msg.recipient, "");
142         strcpy(msg.subject, "Test message; ignore");
143         strcpy(msg.author, username);
144
145         ipc = CtdlIPC_new(*argc_, *argv_, hostname, portname);
146         if (!ipc)
147                 return NULL;    /* oops, something happened... */
148
149         CtdlIPC_chat_recv(ipc, aaa);
150         if (aaa[0] != '2') {
151                 fprintf(stderr, "Citadel refused me: %s\n", &aaa[4]);
152                 return NULL;    /* server ran out of connections maybe? */
153         }
154
155         CtdlIPCIdentifySoftware(ipc, 8, 8, REV_LEVEL, "Citadel stress tester",
156                 "localhost", aaa);      /* we're lying, the server knows */
157         
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
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
172         /* Wait for the rest of the threads */
173         pthread_cond_wait(&start_cond, &start_mutex);
174
175         /* And now the fun begins!  Send out a whole shitload of messages */
176         start = time(NULL);
177         for (c = 0; c < m; c++) {
178                 int rm;
179                 char room[7];
180                 struct ctdlipcroom *rret;
181
182                 /* Select the room to goto */
183                 pthread_mutex_lock(&rand_mutex);
184                 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
185                 rm = (int)(99.0*rand()/(RAND_MAX+1.0));
186                 pthread_mutex_unlock(&rand_mutex);
187
188                 /* Goto the selected room */
189                 sprintf(room, "test%d", rm);
190                 r = CtdlIPCGotoRoom(ipc, room, "", &rret, aaa);
191                 if (r / 100 != 2) {
192                         fprintf(stderr, "Citadel refused room change: %s\n", aaa);
193                         CtdlIPC_delete_ptr(&ipc);
194                         return NULL;
195                 }
196
197                 /* Post the message */
198                 r = CtdlIPCPostMessage(ipc, 1, &msg, aaa);
199                 if (r / 100 != 4) {
200                         fprintf(stderr, "Citadel refused message entry: %s\n", aaa);
201                         CtdlIPC_delete_ptr(&ipc);
202                         return NULL;
203                 }
204
205                 /* Wait for a while */
206                 sleep(w);
207         }
208         end = time(NULL);
209         printf("%ld\n", end - start);
210         return (void*)(end - start);
211 }
212
213
214 /*
215  * Main loop.  Start a shitload of threads, all of which will attempt to
216  * kick a Citadel server square in the nuts.
217  */
218 int main(int argc, char** argv)
219 {
220         void* data[2];          /* pass args to worker thread */
221         pthread_t** threads;    /* A shitload of threads */
222         pthread_attr_t attr;    /* Thread attributes (we use defaults) */
223         int i;                  /* Counters */
224         int t = 0;
225         long runtime;           /* Run time for each thread */
226
227         data[0] = (void*)argc;  /* pass args to worker thread */
228         data[1] = (void*)argv;  /* pass args to worker thread */
229
230         /* First, memory for our shitload of threads */
231         threads = calloc(n, sizeof(pthread_t*));
232         if (!threads) {
233                 perror("Not enough memory");
234                 return 1;
235         }
236
237         /* Then thread attributes (all defaults for now */
238         pthread_attr_init(&attr);
239
240         /* Then, create some threads */
241         for (i = 0; i < n; ++i) {
242                 pthread_create(threads[i], &attr, worker, data);
243         }
244
245         fprintf(stderr, "Starting in 10 seconds\r");
246         sleep(10);
247
248         /* Then, signal the conditional they all are waiting on */
249         pthread_mutex_lock(&start_mutex);
250         pthread_cond_broadcast(&start_cond);
251         pthread_mutex_unlock(&start_mutex);
252
253         /* Then wait for them to exit */
254         for (i = 0; i < n; i++) {
255                 pthread_join(*(threads[i]), (void*)&runtime);
256                 /* We're ignoring this value for now... TODO */
257         }
258         fprintf(stderr, "\r                                                                               \r");
259         return 0;
260 }