Back to index

citadel  8.12
stress.c
Go to the documentation of this file.
00001 
00002 /* This message is exactly 1024 bytes */
00003 char* const message =
00004 "The point of this little file is to stress test a Citadel server.\n"
00005 "It spawns n threads, where n is a command line parameter, each of\n"
00006 "which writes 1000 messages total to the server.\n"
00007 "\n"
00008 "-n is a command line parameter indicating how many users to simulate\n"
00009 "(default 100).  WARNING: Your system must be capable of creating this\n"
00010 "many threads!\n"
00011 "\n"
00012 "-w is a command line parameter indicating how long to wait in seconds\n"
00013 "between posting each message (default 10).  The actual interval\n"
00014 "will be randomized between w / 3 and w * 3.\n"
00015 "\n"
00016 "A run is expected to take approximately three hours, given default\n"
00017 "values, and assuming the server can keep up.  If the run takes much\n"
00018 "longer than this, there may be a performance problem with the server.\n"
00019 "For best results, the test should be run from a different machine than\n"
00020 "the server, but connected via a fast network link (e.g. 100Base-T).\n"
00021 "\n"
00022 "To get baseline results, run the test with -n 1 (simulating 1 user)\n"
00023 "on a machine with no other users logged in.\n"
00024 "\n"
00025 "Example:\n"
00026 "stress -n 500 -w 25 myserver > stress.csv\n";
00027 
00028 /* The program tries to be as small and as fast as possible.  Wherever
00029  * possible, we avoid allocating memory on the heap.  We do not pass data
00030  * between threads.  We do only a minimal amount of calculation.  In
00031  * particular, we only output raw timing data for the run; we do not
00032  * collate it, average it, or do anything else with it.  See below.
00033  * The program does, however, use the same CtdlIPC functions as the
00034  * standard Citadel text client, and incurs the same overhead as that
00035  * program, using those functions.
00036  *
00037  * The program first creates a new user with a randomized username which
00038  * begins with "testuser".  It then creates 100 rooms named test0 through
00039  * test99.  If they already exist, this condition is ignored.
00040  *
00041  * The program then creates n threads, all of which wait on a conditional
00042  * before they do anything.  Once all of the threads have been created,
00043  * they are signaled, and begin execution.  Each thread logs in to the
00044  * Citadel server separately, simulating a user login, then takes a
00045  * timestamp from the operating system.
00046  *
00047  * Each thread selects a room from 0-99 randomly, then writes a small
00048  * (1KB) test message to that room.  1K was chosen because it seems to
00049  * represent an average message size for messages we expect to see.
00050  * After writing the message, the thread sleeps for w seconds (sleep(w);)
00051  * and repeats the process, until it has written 1,000 messages.  The
00052  * program provides a status display to standard error, unless w <= 2, in
00053  * which case status display is disabled.
00054  *
00055  * After posting all messages, each thread takes a second timestamp, and
00056  * subtracts the first timestamp.  The resulting value (in seconds) is
00057  * sent to standard output, followed by the minimum, average, and maximum
00058  * amounts of time (in milliseconds) it took to post a message.  The
00059  * thread then exits.
00060  *
00061  * Once all threads have exited, the program exits.
00062  */
00063 
00064 #include <stdlib.h>
00065 #include <unistd.h>
00066 #include <stdio.h>
00067 #include <sys/types.h>
00068 #include <string.h>
00069 #include <libcitadel.h>
00070 #include "sysdep.h"
00071 #if TIME_WITH_SYS_TIME
00072 # include <sys/time.h>
00073 # include <time.h>
00074 #else
00075 # if HAVE_SYS_TIME_H
00076 #  include <sys/time.h>
00077 # else
00078 #  include <time.h>
00079 # endif
00080 #endif
00081 #include "citadel_ipc.h"
00082 
00083 #ifndef HAVE_PTHREAD_H
00084 #error This program requires threads
00085 #endif
00086 
00087 static int w = 10;          /* see above */
00088 static int n = 100;         /* see above */
00089 static int m = 1000;        /* Number of messages to send; see above */
00090 static volatile int count = 0;     /* Total count of messages posted */
00091 static volatile int total = 0;     /* Total messages to be posted */
00092 static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
00093 static pthread_mutex_t arg_mutex = PTHREAD_MUTEX_INITIALIZER;
00094 static pthread_mutex_t output_mutex = PTHREAD_MUTEX_INITIALIZER;
00095 
00096 static char username[12];
00097 static char password[12];
00098 
00099 /*
00100  * Mutex for the random number generator
00101  * We don't assume that rand_r() is present, so we have to
00102  * provide our own locking for rand()
00103  */
00104 static pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER;
00105 
00106 /*
00107  * Conditional.  All the threads wait for this signal to actually
00108  * start bombarding the server.
00109  */
00110 static pthread_mutex_t start_mutex = PTHREAD_MUTEX_INITIALIZER;
00111 static pthread_cond_t start_cond = PTHREAD_COND_INITIALIZER;
00112 
00113 
00114 /*
00115  * This is the worker thread.  It logs in and creates the 1,000 messages
00116  * as described above.
00117  */
00118 void* worker(void* data)
00119 {
00120        CtdlIPC* ipc; /* My connection to the server */
00121        void** args;  /* Args sent in */
00122        int r;        /* IPC return code */
00123        char aaa[SIZ];       /* Generic buffer */
00124        int c;        /* Message count */
00125        time_t start, end;   /* Timestamps */
00126        struct ctdlipcmessage msg;  /* The message we will post */
00127        int argc_;
00128        char** argv_;
00129        long tmin = LONG_MAX, trun = 0, tmax = LONG_MIN;
00130 
00131        args = (void*)data;
00132        argc_ = (int)args[0];
00133        argv_ = (char**)args[1];
00134 
00135        /* Setup the message we will be posting */
00136        msg.text = message;
00137        msg.anonymous = 0;
00138        msg.type = 1;
00139        strcpy(msg.recipient, "");
00140        strcpy(msg.subject, "Test message; ignore");
00141        strcpy(msg.author, username);
00142 
00143        pthread_mutex_lock(&arg_mutex);
00144        ipc = CtdlIPC_new(argc_, argv_, NULL, NULL);
00145        pthread_mutex_unlock(&arg_mutex);
00146        if (!ipc)
00147               return NULL;  /* oops, something happened... */
00148 
00149        CtdlIPC_chat_recv(ipc, aaa);
00150        if (aaa[0] != '2') {
00151               fprintf(stderr, "Citadel refused me: %s\n", &aaa[4]);
00152               return NULL;  /* server ran out of connections maybe? */
00153        }
00154 
00155        CtdlIPCIdentifySoftware(ipc, 8, 8, REV_LEVEL, "Citadel stress tester",
00156               "localhost", aaa);   /* we're lying, the server knows */
00157        
00158        r = CtdlIPCQueryUsername(ipc, username, aaa);
00159        if (r / 100 == 2) {
00160               /* testuser already exists (from previous run?) */
00161               r = CtdlIPCTryLogin(ipc, username, aaa);
00162               if (r / 100 != 3) {
00163                      fprintf(stderr, "Citadel refused username: %s\n", aaa);
00164                      CtdlIPC_delete_ptr(&ipc);
00165                      return NULL;  /* Gawd only knows what went wrong */
00166               }
00167               r = CtdlIPCTryPassword(ipc, password, aaa);
00168               if (r / 100 != 2) {
00169                      fprintf(stderr, "Citadel refused password: %s\n", aaa);
00170                      CtdlIPC_delete_ptr(&ipc);
00171                      return NULL;  /* Gawd only knows what went wrong */
00172               }
00173        } else {
00174               /* testuser doesn't yet exist */
00175               r = CtdlIPCCreateUser(ipc, username, 1, aaa);
00176               if (r / 100 != 2) {
00177                      fprintf(stderr, "Citadel refused create user: %s\n", aaa);
00178                      CtdlIPC_delete_ptr(&ipc);
00179                      return NULL;  /* Gawd only knows what went wrong */
00180               }
00181               r = CtdlIPCChangePassword(ipc, password, aaa);
00182               if (r / 100 != 2) {
00183                      fprintf(stderr, "Citadel refused change password: %s\n", aaa);
00184                      CtdlIPC_delete_ptr(&ipc);
00185                      return NULL;  /* Gawd only knows what went wrong */
00186               }
00187        }
00188 
00189        /* Wait for the rest of the threads */
00190        pthread_mutex_lock(&start_mutex);
00191        pthread_cond_wait(&start_cond, &start_mutex);
00192        pthread_mutex_unlock(&start_mutex);
00193 
00194        /* And now the fun begins!  Send out a whole shitload of messages */
00195        start = time(NULL);
00196        for (c = 0; c < m; c++) {
00197               int rm;
00198               char room[7];
00199               struct ctdlipcroom *rret;
00200               struct timeval tv;
00201               long tstart, tend;
00202               int wait;
00203 
00204               /* Wait for a while */
00205               pthread_mutex_lock(&rand_mutex);
00206               /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
00207               /* Randomize between w/3 to w*3 (yes, it's complicated) */
00208               wait = (int)((1.0+2.7*(float)w)*rand()/(RAND_MAX+(float)w/3.0)); /* range 0-99 */
00209               pthread_mutex_unlock(&rand_mutex);
00210               sleep(wait);
00211 
00212               /* Select the room to goto */
00213               pthread_mutex_lock(&rand_mutex);
00214               /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
00215               rm = (int)(100.0*rand()/(RAND_MAX+1.0)); /* range 0-99 */
00216               pthread_mutex_unlock(&rand_mutex);
00217 
00218               /* Goto the selected room */
00219               sprintf(room, "test%d", rm);
00220               /* Create the room if not existing. Ignore the return */
00221               r = CtdlIPCCreateRoom(ipc, 1, room, 0, NULL, 0, aaa);
00222               if (r / 100 != 2 && r != 574) {    /* Already exists */
00223                      fprintf(stderr, "Citadel refused room create: %s\n", aaa);
00224                      pthread_mutex_lock(&count_mutex);
00225                      total -= m - c;
00226                      pthread_mutex_unlock(&count_mutex);
00227                      CtdlIPC_delete_ptr(&ipc);
00228                      return NULL;
00229               }
00230               gettimeofday(&tv, NULL);
00231               tstart = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
00232               r = CtdlIPCGotoRoom(ipc, room, "", &rret, aaa);
00233               if (r / 100 != 2) {
00234                      fprintf(stderr, "Citadel refused room change: %s\n", aaa);
00235                      pthread_mutex_lock(&count_mutex);
00236                      total -= m - c;
00237                      pthread_mutex_unlock(&count_mutex);
00238                      CtdlIPC_delete_ptr(&ipc);
00239                      return NULL;
00240               }
00241 
00242               /* Post the message */
00243               r = CtdlIPCPostMessage(ipc, 1, NULL, &msg, aaa);
00244               if (r / 100 != 4) {
00245                      fprintf(stderr, "Citadel refused message entry: %s\n", aaa);
00246                      pthread_mutex_lock(&count_mutex);
00247                      total -= m - c;
00248                      pthread_mutex_unlock(&count_mutex);
00249                      CtdlIPC_delete_ptr(&ipc);
00250                      return NULL;
00251               }
00252 
00253               /* Do a status update */
00254               pthread_mutex_lock(&count_mutex);
00255               count++;
00256               pthread_mutex_unlock(&count_mutex);
00257               fprintf(stderr, " %d/%d=%d%%             \r",
00258                      count, total,
00259                      (int)(100 * count / total));
00260               gettimeofday(&tv, NULL);
00261               tend = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */
00262               tend -= tstart;
00263               if (tend < tmin) tmin = tend;
00264               if (tend > tmax) tmax = tend;
00265               trun += tend;
00266        }
00267        end = time(NULL);
00268        pthread_mutex_lock(&output_mutex);
00269        fprintf(stderr, "               \r");
00270        printf("%ld %ld %ld %ld\n", end - start, tmin, trun / c, tmax);
00271        pthread_mutex_unlock(&output_mutex);
00272        return (void*)(end - start);
00273 }
00274 
00275 
00276 /*
00277  * Shift argument list
00278  */
00279 int shift(int argc, char **argv, int start, int count)
00280 {
00281        int i;
00282 
00283        for (i = start; i < argc - count; ++i)
00284               argv[i] = argv[i + count];
00285        return argc - count;
00286 }
00287 
00288 
00289 /*
00290  * Main loop.  Start a shitload of threads, all of which will attempt to
00291  * kick a Citadel server square in the nuts.
00292  */
00293 int main(int argc, char** argv)
00294 {
00295        void* data[2];              /* pass args to worker thread */
00296        pthread_t* threads;  /* A shitload of threads */
00297        pthread_attr_t attr; /* Thread attributes (we use defaults) */
00298        int i;               /* Counters */
00299        long runtime;        /* Run time for each thread */
00300 
00301        /* Read argument list */
00302        for (i = 0; i < argc; i++) {
00303               if (!strcmp(argv[i], "-n")) {
00304                      n = atoi(argv[i + 1]);
00305                      argc = shift(argc, argv, i, 2);
00306               }
00307               if (!strcmp(argv[i], "-w")) {
00308                      w = atoi(argv[i + 1]);
00309                      argc = shift(argc, argv, i, 2);
00310               }
00311               if (!strcmp(argv[i], "-m")) {
00312                      m = atoi(argv[i + 1]);
00313                      argc = shift(argc, argv, i, 2);
00314               }
00315               if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
00316                      fprintf(stderr, "Read stress.c for usage info\n");
00317                      return 1;
00318               }
00319        }
00320 
00321        data[0] = (void*)argc;      /* pass args to worker thread */
00322        data[1] = (void*)argv;      /* pass args to worker thread */
00323 
00324        /* This is how many total messages will be posted */
00325        total = n * m;
00326 
00327        /* Pick a randomized username */
00328        pthread_mutex_lock(&rand_mutex);
00329        /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */
00330        i = (int)(100.0*rand()/(RAND_MAX+1.0));   /* range 0-99 */
00331        pthread_mutex_unlock(&rand_mutex);
00332        sprintf(username, "testuser%d", i);
00333        strcpy(password, username);
00334 
00335        /* First, memory for our shitload of threads */
00336        threads = calloc(n, sizeof(pthread_t));
00337        if (!threads) {
00338               perror("Not enough memory");
00339               return 1;
00340        }
00341 
00342        /* Then thread attributes (all defaults for now) */
00343        pthread_attr_init(&attr);
00344        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
00345 
00346        /* Then, create some threads */
00347        fprintf(stderr, "Creating threads      \r");
00348        for (i = 0; i < n; ++i) {
00349               pthread_create(&threads[i], &attr, worker, (void*)data);
00350               
00351               /* Give thread #0 time to create the user account */
00352               if (i == 0) sleep(3);
00353        }
00354 
00355        //fprintf(stderr, "Starting in %d seconds\r", n);
00356        //sleep(n);
00357        fprintf(stderr, "                      \r");
00358 
00359        /* Then, signal the conditional they all are waiting on */
00360        pthread_mutex_lock(&start_mutex);
00361        pthread_cond_broadcast(&start_cond);
00362        pthread_mutex_unlock(&start_mutex);
00363 
00364        /* Then wait for them to exit */
00365        for (i = 0; i < n; i++) {
00366               pthread_join(threads[i], (void*)&runtime);
00367               /* We're ignoring this value for now... TODO */
00368        }
00369        fprintf(stderr, "\r                                                                               \r");
00370        return 0;
00371 }