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"
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"
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"
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"
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"
27 "stress -n 500 -w 25 myserver > stress.csv\n";
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.
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.
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.
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.
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.
60 * Once all threads have exited, the program exits.
65 #include <sys/types.h>
67 #if TIME_WITH_SYS_TIME
68 # include <sys/time.h>
72 # include <sys/time.h>
77 #include "citadel_ipc.h"
79 #ifndef HAVE_PTHREAD_H
80 #error This program requires threads
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 */
87 static char* username = NULL;
88 static char* password = NULL;
90 static char* hostname = NULL;
91 static char* portname = NULL;
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()
98 static pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER;
101 * Conditional. All the threads wait for this signal to actually
102 * start bombarding the server.
104 static pthread_mutex_t start_mutex = PTHREAD_MUTEX_INITIALIZER;
105 static pthread_cond_t start_cond = PTHREAD_COND_INITIALIZER;
108 * connection died; hang it up
111 void connection_died(CtdlIPC* ipc, int using_ssl)
120 * This is the worker thread. It logs in and creates the 1,000 messages
121 * as described above.
123 void* worker(void* data)
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 */
135 argv_ = (char***)(data + 1);
137 /* Setup the message we will be posting */
138 msg.text = (char*)message;
141 strcpy(msg.recipient, "");
142 strcpy(msg.subject, "Test message; ignore");
143 strcpy(msg.author, username);
145 ipc = CtdlIPC_new(*argc_, *argv_, hostname, portname);
147 return NULL; /* oops, something happened... */
149 CtdlIPC_chat_recv(ipc, aaa);
151 fprintf(stderr, "Citadel refused me: %s\n", &aaa[4]);
152 return NULL; /* server ran out of connections maybe? */
155 CtdlIPCIdentifySoftware(ipc, 8, 8, REV_LEVEL, "Citadel stress tester",
156 "localhost", aaa); /* we're lying, the server knows */
158 r = CtdlIPCTryLogin(ipc, username, aaa);
160 fprintf(stderr, "Citadel refused username: %s\n", aaa);
161 CtdlIPC_delete_ptr(&ipc);
162 return NULL; /* Gawd only knows what went wrong */
165 r = CtdlIPCTryPassword(ipc, password, aaa);
167 fprintf(stderr, "Citadel refused password: %s\n", aaa);
168 CtdlIPC_delete_ptr(&ipc);
169 return NULL; /* Gawd only knows what went wrong */
172 /* Wait for the rest of the threads */
173 pthread_cond_wait(&start_cond, &start_mutex);
175 /* And now the fun begins! Send out a whole shitload of messages */
177 for (c = 0; c < m; c++) {
180 struct ctdlipcroom *rret;
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);
188 /* Goto the selected room */
189 sprintf(room, "test%d", rm);
190 r = CtdlIPCGotoRoom(ipc, room, "", &rret, aaa);
192 fprintf(stderr, "Citadel refused room change: %s\n", aaa);
193 CtdlIPC_delete_ptr(&ipc);
197 /* Post the message */
198 r = CtdlIPCPostMessage(ipc, 1, &msg, aaa);
200 fprintf(stderr, "Citadel refused message entry: %s\n", aaa);
201 CtdlIPC_delete_ptr(&ipc);
205 /* Wait for a while */
209 printf("%ld\n", end - start);
210 return (void*)(end - start);
215 * Main loop. Start a shitload of threads, all of which will attempt to
216 * kick a Citadel server square in the nuts.
218 int main(int argc, char** argv)
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 */
225 long runtime; /* Run time for each thread */
227 data[0] = (void*)argc; /* pass args to worker thread */
228 data[1] = (void*)argv; /* pass args to worker thread */
230 /* First, memory for our shitload of threads */
231 threads = calloc(n, sizeof(pthread_t*));
233 perror("Not enough memory");
237 /* Then thread attributes (all defaults for now */
238 pthread_attr_init(&attr);
240 /* Then, create some threads */
241 for (i = 0; i < n; ++i) {
242 pthread_create(threads[i], &attr, worker, data);
245 fprintf(stderr, "Starting in 10 seconds\r");
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);
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 */
258 fprintf(stderr, "\r \r");