* Updated the wording of the output of a DOWN command ued to restart the server.
[citadel.git] / citadel / stress.c
blobd62ea3cb8dfcd11276857b8f42db9cddb9c4bbab
1 /* $Id$ */
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";
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, followed by the minimum, average, and maximum
59 * amounts of time (in milliseconds) it took to post a message. The
60 * thread then exits.
62 * Once all threads have exited, the program exits.
65 #include <stdlib.h>
66 #include <unistd.h>
67 #include <stdio.h>
68 #include <sys/types.h>
69 #include <string.h>
70 #include <libcitadel.h>
71 #include "sysdep.h"
72 #if TIME_WITH_SYS_TIME
73 # include <sys/time.h>
74 # include <time.h>
75 #else
76 # if HAVE_SYS_TIME_H
77 # include <sys/time.h>
78 # else
79 # include <time.h>
80 # endif
81 #endif
82 #include "citadel_ipc.h"
84 #ifndef HAVE_PTHREAD_H
85 #error This program requires threads
86 #endif
88 static int w = 10; /* see above */
89 static int n = 100; /* see above */
90 static int m = 1000; /* Number of messages to send; see above */
91 static volatile int count = 0; /* Total count of messages posted */
92 static volatile int total = 0; /* Total messages to be posted */
93 static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
94 static pthread_mutex_t arg_mutex = PTHREAD_MUTEX_INITIALIZER;
95 static pthread_mutex_t output_mutex = PTHREAD_MUTEX_INITIALIZER;
97 static char username[12];
98 static char password[12];
101 * Mutex for the random number generator
102 * We don't assume that rand_r() is present, so we have to
103 * provide our own locking for rand()
105 static pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER;
108 * Conditional. All the threads wait for this signal to actually
109 * start bombarding the server.
111 static pthread_mutex_t start_mutex = PTHREAD_MUTEX_INITIALIZER;
112 static pthread_cond_t start_cond = PTHREAD_COND_INITIALIZER;
116 * This is the worker thread. It logs in and creates the 1,000 messages
117 * as described above.
119 void* worker(void* data)
121 CtdlIPC* ipc; /* My connection to the server */
122 void** args; /* Args sent in */
123 int r; /* IPC return code */
124 char aaa[SIZ]; /* Generic buffer */
125 int c; /* Message count */
126 time_t start, end; /* Timestamps */
127 struct ctdlipcmessage msg; /* The message we will post */
128 int argc_;
129 char** argv_;
130 long tmin = LONG_MAX, trun = 0, tmax = LONG_MIN;
132 args = (void*)data;
133 argc_ = (int)args[0];
134 argv_ = (char**)args[1];
136 /* Setup the message we will be posting */
137 msg.text = message;
138 msg.anonymous = 0;
139 msg.type = 1;
140 strcpy(msg.recipient, "");
141 strcpy(msg.subject, "Test message; ignore");
142 strcpy(msg.author, username);
144 pthread_mutex_lock(&arg_mutex);
145 ipc = CtdlIPC_new(argc_, argv_, NULL, NULL);
146 pthread_mutex_unlock(&arg_mutex);
147 if (!ipc)
148 return NULL; /* oops, something happened... */
150 CtdlIPC_chat_recv(ipc, aaa);
151 if (aaa[0] != '2') {
152 fprintf(stderr, "Citadel refused me: %s\n", &aaa[4]);
153 return NULL; /* server ran out of connections maybe? */
156 CtdlIPCIdentifySoftware(ipc, 8, 8, REV_LEVEL, "Citadel stress tester",
157 "localhost", aaa); /* we're lying, the server knows */
159 r = CtdlIPCQueryUsername(ipc, username, aaa);
160 if (r / 100 == 2) {
161 /* testuser already exists (from previous run?) */
162 r = CtdlIPCTryLogin(ipc, username, aaa);
163 if (r / 100 != 3) {
164 fprintf(stderr, "Citadel refused username: %s\n", aaa);
165 CtdlIPC_delete_ptr(&ipc);
166 return NULL; /* Gawd only knows what went wrong */
168 r = CtdlIPCTryPassword(ipc, password, aaa);
169 if (r / 100 != 2) {
170 fprintf(stderr, "Citadel refused password: %s\n", aaa);
171 CtdlIPC_delete_ptr(&ipc);
172 return NULL; /* Gawd only knows what went wrong */
174 } else {
175 /* testuser doesn't yet exist */
176 r = CtdlIPCCreateUser(ipc, username, 1, aaa);
177 if (r / 100 != 2) {
178 fprintf(stderr, "Citadel refused create user: %s\n", aaa);
179 CtdlIPC_delete_ptr(&ipc);
180 return NULL; /* Gawd only knows what went wrong */
182 r = CtdlIPCChangePassword(ipc, password, aaa);
183 if (r / 100 != 2) {
184 fprintf(stderr, "Citadel refused change password: %s\n", aaa);
185 CtdlIPC_delete_ptr(&ipc);
186 return NULL; /* Gawd only knows what went wrong */
190 /* Wait for the rest of the threads */
191 pthread_mutex_lock(&start_mutex);
192 pthread_cond_wait(&start_cond, &start_mutex);
193 pthread_mutex_unlock(&start_mutex);
195 /* And now the fun begins! Send out a whole shitload of messages */
196 start = time(NULL);
197 for (c = 0; c < m; c++) {
198 int rm;
199 char room[7];
200 struct ctdlipcroom *rret;
201 struct timeval tv;
202 long tstart, tend;
203 int wait;
205 /* Wait for a while */
206 pthread_mutex_lock(&rand_mutex);
207 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
208 /* Randomize between w/3 to w*3 (yes, it's complicated) */
209 wait = (int)((1.0+2.7*(float)w)*rand()/(RAND_MAX+(float)w/3.0)); /* range 0-99 */
210 pthread_mutex_unlock(&rand_mutex);
211 sleep(wait);
213 /* Select the room to goto */
214 pthread_mutex_lock(&rand_mutex);
215 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
216 rm = (int)(100.0*rand()/(RAND_MAX+1.0)); /* range 0-99 */
217 pthread_mutex_unlock(&rand_mutex);
219 /* Goto the selected room */
220 sprintf(room, "test%d", rm);
221 /* Create the room if not existing. Ignore the return */
222 r = CtdlIPCCreateRoom(ipc, 1, room, 0, NULL, 0, aaa);
223 if (r / 100 != 2 && r != 574) { /* Already exists */
224 fprintf(stderr, "Citadel refused room create: %s\n", aaa);
225 pthread_mutex_lock(&count_mutex);
226 total -= m - c;
227 pthread_mutex_unlock(&count_mutex);
228 CtdlIPC_delete_ptr(&ipc);
229 return NULL;
231 gettimeofday(&tv, NULL);
232 tstart = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
233 r = CtdlIPCGotoRoom(ipc, room, "", &rret, aaa);
234 if (r / 100 != 2) {
235 fprintf(stderr, "Citadel refused room change: %s\n", aaa);
236 pthread_mutex_lock(&count_mutex);
237 total -= m - c;
238 pthread_mutex_unlock(&count_mutex);
239 CtdlIPC_delete_ptr(&ipc);
240 return NULL;
243 /* Post the message */
244 r = CtdlIPCPostMessage(ipc, 1, NULL, &msg, aaa);
245 if (r / 100 != 4) {
246 fprintf(stderr, "Citadel refused message entry: %s\n", aaa);
247 pthread_mutex_lock(&count_mutex);
248 total -= m - c;
249 pthread_mutex_unlock(&count_mutex);
250 CtdlIPC_delete_ptr(&ipc);
251 return NULL;
254 /* Do a status update */
255 pthread_mutex_lock(&count_mutex);
256 count++;
257 pthread_mutex_unlock(&count_mutex);
258 fprintf(stderr, " %d/%d=%d%% \r",
259 count, total,
260 (int)(100 * count / total));
261 gettimeofday(&tv, NULL);
262 tend = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
263 tend -= tstart;
264 if (tend < tmin) tmin = tend;
265 if (tend > tmax) tmax = tend;
266 trun += tend;
268 end = time(NULL);
269 pthread_mutex_lock(&output_mutex);
270 fprintf(stderr, " \r");
271 printf("%ld %ld %ld %ld\n", end - start, tmin, trun / c, tmax);
272 pthread_mutex_unlock(&output_mutex);
273 return (void*)(end - start);
278 * Shift argument list
280 int shift(int argc, char **argv, int start, int count)
282 int i;
284 for (i = start; i < argc - count; ++i)
285 argv[i] = argv[i + count];
286 return argc - count;
291 * Main loop. Start a shitload of threads, all of which will attempt to
292 * kick a Citadel server square in the nuts.
294 int main(int argc, char** argv)
296 void* data[2]; /* pass args to worker thread */
297 pthread_t* threads; /* A shitload of threads */
298 pthread_attr_t attr; /* Thread attributes (we use defaults) */
299 int i; /* Counters */
300 long runtime; /* Run time for each thread */
302 /* Read argument list */
303 for (i = 0; i < argc; i++) {
304 if (!strcmp(argv[i], "-n")) {
305 n = atoi(argv[i + 1]);
306 argc = shift(argc, argv, i, 2);
308 if (!strcmp(argv[i], "-w")) {
309 w = atoi(argv[i + 1]);
310 argc = shift(argc, argv, i, 2);
312 if (!strcmp(argv[i], "-m")) {
313 m = atoi(argv[i + 1]);
314 argc = shift(argc, argv, i, 2);
316 if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
317 fprintf(stderr, "Read stress.c for usage info\n");
318 return 1;
322 data[0] = (void*)argc; /* pass args to worker thread */
323 data[1] = (void*)argv; /* pass args to worker thread */
325 /* This is how many total messages will be posted */
326 total = n * m;
328 /* Pick a randomized username */
329 pthread_mutex_lock(&rand_mutex);
330 /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
331 i = (int)(100.0*rand()/(RAND_MAX+1.0)); /* range 0-99 */
332 pthread_mutex_unlock(&rand_mutex);
333 sprintf(username, "testuser%d", i);
334 strcpy(password, username);
336 /* First, memory for our shitload of threads */
337 threads = calloc(n, sizeof(pthread_t));
338 if (!threads) {
339 perror("Not enough memory");
340 return 1;
343 /* Then thread attributes (all defaults for now) */
344 pthread_attr_init(&attr);
345 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
347 /* Then, create some threads */
348 fprintf(stderr, "Creating threads \r");
349 for (i = 0; i < n; ++i) {
350 pthread_create(&threads[i], &attr, worker, (void*)data);
352 /* Give thread #0 time to create the user account */
353 if (i == 0) sleep(3);
356 //fprintf(stderr, "Starting in %d seconds\r", n);
357 //sleep(n);
358 fprintf(stderr, " \r");
360 /* Then, signal the conditional they all are waiting on */
361 pthread_mutex_lock(&start_mutex);
362 pthread_cond_broadcast(&start_cond);
363 pthread_mutex_unlock(&start_mutex);
365 /* Then wait for them to exit */
366 for (i = 0; i < n; i++) {
367 pthread_join(threads[i], (void*)&runtime);
368 /* We're ignoring this value for now... TODO */
370 fprintf(stderr, "\r \r");
371 return 0;