File: stress.c

package info (click to toggle)
citadel 902-4
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 3,904 kB
  • ctags: 4,359
  • sloc: ansic: 54,083; sh: 4,226; yacc: 651; makefile: 413; xml: 40
file content (362 lines) | stat: -rw-r--r-- 11,902 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362

/* This message is exactly 1024 bytes */
char* const message =
"The point of this little file is to stress test a Citadel server.\n"
"It spawns n threads, where n is a command line parameter, each of\n"
"which writes 1000 messages total to the server.\n"
"\n"
"-n is a command line parameter indicating how many users to simulate\n"
"(default 100).  WARNING: Your system must be capable of creating this\n"
"many threads!\n"
"\n"
"-w is a command line parameter indicating how long to wait in seconds\n"
"between posting each message (default 10).  The actual interval\n"
"will be randomized between w / 3 and w * 3.\n"
"\n"
"A run is expected to take approximately three hours, given default\n"
"values, and assuming the server can keep up.  If the run takes much\n"
"longer than this, there may be a performance problem with the server.\n"
"For best results, the test should be run from a different machine than\n"
"the server, but connected via a fast network link (e.g. 100Base-T).\n"
"\n"
"To get baseline results, run the test with -n 1 (simulating 1 user)\n"
"on a machine with no other users logged in.\n"
"\n"
"Example:\n"
"stress -n 500 -w 25 myserver > stress.csv\n";

/* The program tries to be as small and as fast as possible.  Wherever
 * possible, we avoid allocating memory on the heap.  We do not pass data
 * between threads.  We do only a minimal amount of calculation.  In
 * particular, we only output raw timing data for the run; we do not
 * collate it, average it, or do anything else with it.  See below.
 * The program does, however, use the same CtdlIPC functions as the
 * standard Citadel text client, and incurs the same overhead as that
 * program, using those functions.
 *
 * The program first creates a new user with a randomized username which
 * begins with "testuser".  It then creates 100 rooms named test0 through
 * test99.  If they already exist, this condition is ignored.
 *
 * The program then creates n threads, all of which wait on a conditional
 * before they do anything.  Once all of the threads have been created,
 * they are signaled, and begin execution.  Each thread logs in to the
 * Citadel server separately, simulating a user login, then takes a
 * timestamp from the operating system.
 *
 * Each thread selects a room from 0-99 randomly, then writes a small
 * (1KB) test message to that room.  1K was chosen because it seems to
 * represent an average message size for messages we expect to see.
 * After writing the message, the thread sleeps for w seconds (sleep(w);)
 * and repeats the process, until it has written 1,000 messages.  The
 * program provides a status display to standard error, unless w <= 2, in
 * which case status display is disabled.
 *
 * After posting all messages, each thread takes a second timestamp, and
 * subtracts the first timestamp.  The resulting value (in seconds) is
 * sent to standard output, followed by the minimum, average, and maximum
 * amounts of time (in milliseconds) it took to post a message.  The
 * thread then exits.
 *
 * Once all threads have exited, the program exits.
 */

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <libcitadel.h>
#include "sysdep.h"
#include <time.h>
#include "citadel_ipc.h"

#ifndef HAVE_PTHREAD_H
#error This program requires threads
#endif

static int w = 10;		/* see above */
static int n = 100;		/* see above */
static int m = 1000;		/* Number of messages to send; see above */
static volatile int count = 0;	/* Total count of messages posted */
static volatile int total = 0;	/* Total messages to be posted */
static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t arg_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t output_mutex = PTHREAD_MUTEX_INITIALIZER;

static char username[12];
static char password[12];

/*
 * Mutex for the random number generator
 * We don't assume that rand_r() is present, so we have to
 * provide our own locking for rand()
 */
static pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER;

/*
 * Conditional.  All the threads wait for this signal to actually
 * start bombarding the server.
 */
static pthread_mutex_t start_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t start_cond = PTHREAD_COND_INITIALIZER;


/*
 * This is the worker thread.  It logs in and creates the 1,000 messages
 * as described above.
 */
void* worker(void* data)
{
	CtdlIPC* ipc;	/* My connection to the server */
	void** args;	/* Args sent in */
	int r;		/* IPC return code */
	char aaa[SIZ];	/* Generic buffer */
	int c;		/* Message count */
	time_t start, end;	/* Timestamps */
	struct ctdlipcmessage msg;	/* The message we will post */
	int argc_;
	char** argv_;
	long tmin = LONG_MAX, trun = 0, tmax = LONG_MIN;

	args = (void*)data;
	argc_ = (int)args[0];
	argv_ = (char**)args[1];

	/* Setup the message we will be posting */
	msg.text = message;
	msg.anonymous = 0;
	msg.type = 1;
	strcpy(msg.recipient, "");
	strcpy(msg.subject, "Test message; ignore");
	strcpy(msg.author, username);

	pthread_mutex_lock(&arg_mutex);
	ipc = CtdlIPC_new(argc_, argv_, NULL, NULL);
	pthread_mutex_unlock(&arg_mutex);
	if (!ipc)
		return NULL;	/* oops, something happened... */

	CtdlIPC_chat_recv(ipc, aaa);
	if (aaa[0] != '2') {
		fprintf(stderr, "Citadel refused me: %s\n", &aaa[4]);
		return NULL;	/* server ran out of connections maybe? */
	}

	CtdlIPCIdentifySoftware(ipc, 8, 8, REV_LEVEL, "Citadel stress tester",
		"localhost", aaa);	/* we're lying, the server knows */
	
	r = CtdlIPCQueryUsername(ipc, username, aaa);
	if (r / 100 == 2) {
		/* testuser already exists (from previous run?) */
		r = CtdlIPCTryLogin(ipc, username, aaa);
		if (r / 100 != 3) {
			fprintf(stderr, "Citadel refused username: %s\n", aaa);
			CtdlIPC_delete_ptr(&ipc);
			return NULL;	/* Gawd only knows what went wrong */
		}
		r = CtdlIPCTryPassword(ipc, password, aaa);
		if (r / 100 != 2) {
			fprintf(stderr, "Citadel refused password: %s\n", aaa);
			CtdlIPC_delete_ptr(&ipc);
			return NULL;	/* Gawd only knows what went wrong */
		}
	} else {
		/* testuser doesn't yet exist */
		r = CtdlIPCCreateUser(ipc, username, 1, aaa);
		if (r / 100 != 2) {
			fprintf(stderr, "Citadel refused create user: %s\n", aaa);
			CtdlIPC_delete_ptr(&ipc);
			return NULL;	/* Gawd only knows what went wrong */
		}
		r = CtdlIPCChangePassword(ipc, password, aaa);
		if (r / 100 != 2) {
			fprintf(stderr, "Citadel refused change password: %s\n", aaa);
			CtdlIPC_delete_ptr(&ipc);
			return NULL;	/* Gawd only knows what went wrong */
		}
	}

	/* Wait for the rest of the threads */
	pthread_mutex_lock(&start_mutex);
	pthread_cond_wait(&start_cond, &start_mutex);
	pthread_mutex_unlock(&start_mutex);

	/* And now the fun begins!  Send out a whole shitload of messages */
	start = time(NULL);
	for (c = 0; c < m; c++) {
		int rm;
		char room[7];
		struct ctdlipcroom *rret;
		struct timeval tv;
		long tstart, tend;
		int wait;

		/* Wait for a while */
		pthread_mutex_lock(&rand_mutex);
		/* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
		/* Randomize between w/3 to w*3 (yes, it's complicated) */
		wait = (int)((1.0+2.7*(float)w)*rand()/(RAND_MAX+(float)w/3.0)); /* range 0-99 */
		pthread_mutex_unlock(&rand_mutex);
		sleep(wait);

		/* Select the room to goto */
		pthread_mutex_lock(&rand_mutex);
		/* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
		rm = (int)(100.0*rand()/(RAND_MAX+1.0)); /* range 0-99 */
		pthread_mutex_unlock(&rand_mutex);

		/* Goto the selected room */
		sprintf(room, "test%d", rm);
		/* Create the room if not existing. Ignore the return */
		r = CtdlIPCCreateRoom(ipc, 1, room, 0, NULL, 0, aaa);
		if (r / 100 != 2 && r != 574) {	/* Already exists */
			fprintf(stderr, "Citadel refused room create: %s\n", aaa);
			pthread_mutex_lock(&count_mutex);
			total -= m - c;
			pthread_mutex_unlock(&count_mutex);
			CtdlIPC_delete_ptr(&ipc);
			return NULL;
		}
		gettimeofday(&tv, NULL);
		tstart = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
		r = CtdlIPCGotoRoom(ipc, room, "", &rret, aaa);
		if (r / 100 != 2) {
			fprintf(stderr, "Citadel refused room change: %s\n", aaa);
			pthread_mutex_lock(&count_mutex);
			total -= m - c;
			pthread_mutex_unlock(&count_mutex);
			CtdlIPC_delete_ptr(&ipc);
			return NULL;
		}

		/* Post the message */
		r = CtdlIPCPostMessage(ipc, 1, NULL, &msg, aaa);
		if (r / 100 != 4) {
			fprintf(stderr, "Citadel refused message entry: %s\n", aaa);
			pthread_mutex_lock(&count_mutex);
			total -= m - c;
			pthread_mutex_unlock(&count_mutex);
			CtdlIPC_delete_ptr(&ipc);
			return NULL;
		}

		/* Do a status update */
		pthread_mutex_lock(&count_mutex);
		count++;
		pthread_mutex_unlock(&count_mutex);
		fprintf(stderr, " %d/%d=%d%%             \r",
			count, total,
			(int)(100 * count / total));
		gettimeofday(&tv, NULL);
		tend = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
		tend -= tstart;
		if (tend < tmin) tmin = tend;
		if (tend > tmax) tmax = tend;
		trun += tend;
	}
	end = time(NULL);
	pthread_mutex_lock(&output_mutex);
	fprintf(stderr, "               \r");
	printf("%ld %ld %ld %ld\n", end - start, tmin, trun / c, tmax);
	pthread_mutex_unlock(&output_mutex);
	return (void*)(end - start);
}


/*
 * Shift argument list
 */
int shift(int argc, char **argv, int start, int count)
{
	int i;

	for (i = start; i < argc - count; ++i)
		argv[i] = argv[i + count];
	return argc - count;
}


/*
 * Main loop.  Start a shitload of threads, all of which will attempt to
 * kick a Citadel server square in the nuts.
 */
int main(int argc, char** argv)
{
	void* data[2];		/* pass args to worker thread */
	pthread_t* threads;	/* A shitload of threads */
	pthread_attr_t attr;	/* Thread attributes (we use defaults) */
	int i;			/* Counters */
	long runtime;		/* Run time for each thread */

	/* Read argument list */
	for (i = 0; i < argc; i++) {
		if (!strcmp(argv[i], "-n")) {
			n = atoi(argv[i + 1]);
			argc = shift(argc, argv, i, 2);
		}
		if (!strcmp(argv[i], "-w")) {
			w = atoi(argv[i + 1]);
			argc = shift(argc, argv, i, 2);
		}
		if (!strcmp(argv[i], "-m")) {
			m = atoi(argv[i + 1]);
			argc = shift(argc, argv, i, 2);
		}
		if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
			fprintf(stderr, "Read stress.c for usage info\n");
			return 1;
		}
	}

	data[0] = (void*)argc;	/* pass args to worker thread */
	data[1] = (void*)argv;	/* pass args to worker thread */

	/* This is how many total messages will be posted */
	total = n * m;

	/* Pick a randomized username */
	pthread_mutex_lock(&rand_mutex);
	/* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
	i = (int)(100.0*rand()/(RAND_MAX+1.0));	/* range 0-99 */
	pthread_mutex_unlock(&rand_mutex);
	sprintf(username, "testuser%d", i);
	strcpy(password, username);

	/* First, memory for our shitload of threads */
	threads = calloc(n, sizeof(pthread_t));
	if (!threads) {
		perror("Not enough memory");
		return 1;
	}

	/* Then thread attributes (all defaults for now) */
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

	/* Then, create some threads */
	fprintf(stderr, "Creating threads      \r");
	for (i = 0; i < n; ++i) {
		pthread_create(&threads[i], &attr, worker, (void*)data);
		
		/* Give thread #0 time to create the user account */
		if (i == 0) sleep(3);
	}

	//fprintf(stderr, "Starting in %d seconds\r", n);
	//sleep(n);
	fprintf(stderr, "                      \r");

	/* Then, signal the conditional they all are waiting on */
	pthread_mutex_lock(&start_mutex);
	pthread_cond_broadcast(&start_cond);
	pthread_mutex_unlock(&start_mutex);

	/* Then wait for them to exit */
	for (i = 0; i < n; i++) {
		pthread_join(threads[i], (void*)&runtime);
		/* We're ignoring this value for now... TODO */
	}
	fprintf(stderr, "\r                                                                               \r");
	return 0;
}