3 /* This message is exactly 1024 bytes */
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 100). 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, followed by the minimum, average, and maximum
59 * amounts of time (in milliseconds) it took to post a message. The
62 * Once all threads have exited, the program exits.
68 #include <sys/types.h>
70 #include <libcitadel.h>
72 #if TIME_WITH_SYS_TIME
73 # include <sys/time.h>
77 # include <sys/time.h>
82 #include "citadel_ipc.h"
84 #ifndef HAVE_PTHREAD_H
85 #error This program requires threads
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 */
130 long tmin
= LONG_MAX
, trun
= 0, tmax
= LONG_MIN
;
133 argc_
= (int)args
[0];
134 argv_
= (char**)args
[1];
136 /* Setup the message we will be posting */
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
);
148 return NULL
; /* oops, something happened... */
150 CtdlIPC_chat_recv(ipc
, aaa
);
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
);
161 /* testuser already exists (from previous run?) */
162 r
= CtdlIPCTryLogin(ipc
, username
, aaa
);
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
);
170 fprintf(stderr
, "Citadel refused password: %s\n", aaa
);
171 CtdlIPC_delete_ptr(&ipc
);
172 return NULL
; /* Gawd only knows what went wrong */
175 /* testuser doesn't yet exist */
176 r
= CtdlIPCCreateUser(ipc
, username
, 1, aaa
);
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
);
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 */
197 for (c
= 0; c
< m
; c
++) {
200 struct ctdlipcroom
*rret
;
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
);
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
);
227 pthread_mutex_unlock(&count_mutex
);
228 CtdlIPC_delete_ptr(&ipc
);
231 gettimeofday(&tv
, NULL
);
232 tstart
= tv
.tv_sec
* 1000 + tv
.tv_usec
/ 1000; /* cvt to msec */
233 r
= CtdlIPCGotoRoom(ipc
, room
, "", &rret
, aaa
);
235 fprintf(stderr
, "Citadel refused room change: %s\n", aaa
);
236 pthread_mutex_lock(&count_mutex
);
238 pthread_mutex_unlock(&count_mutex
);
239 CtdlIPC_delete_ptr(&ipc
);
243 /* Post the message */
244 r
= CtdlIPCPostMessage(ipc
, 1, NULL
, &msg
, aaa
);
246 fprintf(stderr
, "Citadel refused message entry: %s\n", aaa
);
247 pthread_mutex_lock(&count_mutex
);
249 pthread_mutex_unlock(&count_mutex
);
250 CtdlIPC_delete_ptr(&ipc
);
254 /* Do a status update */
255 pthread_mutex_lock(&count_mutex
);
257 pthread_mutex_unlock(&count_mutex
);
258 fprintf(stderr
, " %d/%d=%d%% \r",
260 (int)(100 * count
/ total
));
261 gettimeofday(&tv
, NULL
);
262 tend
= tv
.tv_sec
* 1000 + tv
.tv_usec
/ 1000; /* cvt to msec */
264 if (tend
< tmin
) tmin
= tend
;
265 if (tend
> tmax
) tmax
= tend
;
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
)
284 for (i
= start
; i
< argc
- count
; ++i
)
285 argv
[i
] = argv
[i
+ 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");
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 */
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
));
339 perror("Not enough memory");
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);
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");