1 /**
2  * Siege, http regression tester / benchmark utility
3  *
4  * Copyright (C) 2000-2015 by
5  * Jeffrey Fulmer - <jeff@joedog.org>, et al.
6  * This file is distributed as part of Siege
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with this program; if not, write to the Free Software Foundation, Inc.
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  *
22  */
23 #define  INTERN  1
24 
25 #ifdef  HAVE_CONFIG_H
26 # include <config.h>
27 #endif/*HAVE_CONFIG_H*/
28 
29 #ifdef  HAVE_PTHREAD_H
30 # include <pthread.h>
31 #endif/*HAVE_PTHREAD_H*/
32 
33 /*LOCAL HEADERS*/
34 #include <setup.h>
35 #include <array.h>
36 #include <handler.h>
37 #include <timer.h>
38 #include <browser.h>
39 #include <util.h>
40 #include <log.h>
41 #include <init.h>
42 #include <cfg.h>
43 #include <url.h>
44 #include <ssl.h>
45 #include <cookies.h>
46 #include <crew.h>
47 #include <data.h>
48 #include <version.h>
49 #include <memory.h>
50 #include <notify.h>
51 #include <sys/resource.h>
52 #ifdef __CYGWIN__
53 # include <getopt.h>
54 #else
55 # include <joedog/getopt.h>
56 #endif
57 
58 /**
59  * long options, std options struct
60  */
61 static struct option long_options[] =
62 {
63   { "version",      no_argument,       NULL, 'V' },
64   { "help",         no_argument,       NULL, 'h' },
65   { "verbose",      no_argument,       NULL, 'v' },
66   { "quiet",        no_argument,       NULL, 'q' },
67   { "config",       no_argument,       NULL, 'C' },
68   { "debug",        no_argument,       NULL, 'D' },
69   { "get",          no_argument,       NULL, 'g' },
70   { "print",        no_argument,       NULL, 'p' },
71   { "concurrent",   required_argument, NULL, 'c' },
72   { "no-parser",    no_argument,       NULL, 'N' },
73   { "no-follow",    no_argument,       NULL, 'F' },
74   { "internet",     no_argument,       NULL, 'i' },
75   { "benchmark",    no_argument,       NULL, 'b' },
76   { "reps",         required_argument, NULL, 'r' },
77   { "time",         required_argument, NULL, 't' },
78   { "delay",        required_argument, NULL, 'd' },
79   { "log",          optional_argument, NULL, 'l' },
80   { "file",         required_argument, NULL, 'f' },
81   { "rc",           required_argument, NULL, 'R' },
82   { "mark",         required_argument, NULL, 'm' },
83   { "header",       required_argument, NULL, 'H' },
84   { "user-agent",   required_argument, NULL, 'A' },
85   { "content-type", required_argument, NULL, 'T' },
86   { "json-output",  no_argument,       NULL, 'j' },
87   {0, 0, 0, 0}
88 };
89 
90 /**
91  * display_version
92  * displays the version number and exits on boolean false.
93  * continue running? TRUE=yes, FALSE=no
94  * return void
95  */
96 
97 void
display_version(BOOLEAN b)98 display_version(BOOLEAN b)
99 {
100   /**
101    * version_string is defined in version.c
102    * adding it to a separate file allows us
103    * to parse it in configure.
104    */
105   char name[128];
106 
107   memset(name, 0, sizeof name);
108   strncpy(name, program_name, strlen(program_name));
109 
110   if(my.debug){
111     fprintf(stderr,"%s %s: debugging enabled\n\n%s\n", uppercase(name, strlen(name)), version_string, copyright);
112   } else {
113     if(b == TRUE){
114       fprintf(stderr,"%s %s\n\n%s\n", uppercase(name, strlen(name)), version_string, copyright);
115       exit(EXIT_SUCCESS);
116     } else {
117       fprintf(stderr,"%s %s\n", uppercase(name, strlen(name)), version_string);
118     }
119   }
120 }  /* end of display version */
121 
122 /**
123  * display_help
124  * displays the help section to STDOUT and exits
125  */
126 void
display_help()127 display_help()
128 {
129   /**
130    * call display_version, but do not exit
131    */
132   display_version(FALSE);
133   printf("Usage: %s [options]\n", program_name);
134   printf("       %s [options] URL\n", program_name);
135   printf("       %s -g URL\n", program_name);
136   printf("Options:\n"                    );
137   puts("  -V, --version             VERSION, prints the version number.");
138   puts("  -h, --help                HELP, prints this section.");
139   puts("  -C, --config              CONFIGURATION, show the current config.");
140   puts("  -v, --verbose             VERBOSE, prints notification to screen.");
141   puts("  -q, --quiet               QUIET turns verbose off and suppresses output.");
142   puts("  -g, --get                 GET, pull down HTTP headers and display the");
143   puts("                            transaction. Great for application debugging.");
144   puts("  -p, --print               PRINT, like GET only it prints the entire page.");
145   puts("  -c, --concurrent=NUM      CONCURRENT users, default is 10");
146   puts("  -r, --reps=NUM            REPS, number of times to run the test." );
147   puts("  -t, --time=NUMm           TIMED testing where \"m\" is modifier S, M, or H");
148   puts("                            ex: --time=1H, one hour test." );
149   puts("  -d, --delay=NUM           Time DELAY, random delay before each request");
150   puts("  -b, --benchmark           BENCHMARK: no delays between requests." );
151   puts("  -i, --internet            INTERNET user simulation, hits URLs randomly.");
152   puts("  -f, --file=FILE           FILE, select a specific URLS FILE." );
153   printf("  -R, --rc=FILE             RC, specify an %src file\n",program_name);
154   puts("  -l, --log[=FILE]          LOG to FILE. If FILE is not specified, the");
155   printf("                            default is used: PREFIX/var/%s.log\n", program_name);
156   puts("  -m, --mark=\"text\"         MARK, mark the log file with a string." );
157   puts("                            between .001 and NUM. (NOT COUNTED IN STATS)");
158   puts("  -H, --header=\"text\"       Add a header to request (can be many)" );
159   puts("  -A, --user-agent=\"text\"   Sets User-Agent in request" );
160   puts("  -T, --content-type=\"text\" Sets Content-Type in request" );
161   puts("  -j, --json-output         JSON OUTPUT, print final stats to stdout as JSON");
162   puts("      --no-parser           NO PARSER, turn off the HTML page parser");
163   puts("      --no-follow           NO FOLLOW, do not follow HTTP redirects");
164   puts("");
165   puts(copyright);
166   /**
167    * our work is done, exit nicely
168    */
169   exit( EXIT_SUCCESS );
170 }
171 
172 /* Check the command line for the presence of the -R or --RC switch.  We
173  * need to do this seperately from the other command line switches because
174  * the options are initialized from the .siegerc file before the command line
175  * switches are parsed. The argument index is reset before leaving the
176  * function. */
177 void
parse_rc_cmdline(int argc,char * argv[])178 parse_rc_cmdline(int argc, char *argv[])
179 {
180   int a = 0;
181   strcpy(my.rc, "");
182 
183   while( a > -1 ){
184     a = getopt_long(argc, argv, "VhvqCDNFpgl::ibr:t:f:d:c:m:H:R:A:T:j", long_options, (int*)0);
185     if(a == 'R'){
186       strcpy(my.rc, optarg);
187       a = -1;
188     }
189   }
190   optind = 0;
191 }
192 
193 /**
194  * parses command line arguments and assigns
195  * values to run time variables. relies on GNU
196  * getopts included with this distribution.
197  */
198 void
parse_cmdline(int argc,char * argv[])199 parse_cmdline(int argc, char *argv[])
200 {
201   int c = 0;
202   int nargs;
203   while ((c = getopt_long(argc, argv, "VhvqCDNFpgl::ibr:t:f:d:c:m:H:R:A:T:j", long_options, (int *)0)) != EOF) {
204   switch (c) {
205       case 'V':
206         display_version(TRUE);
207         break;
208       case 'h':
209         display_help();
210         exit(EXIT_SUCCESS);
211       case 'D':
212         my.debug = TRUE;
213         break;
214       case 'C':
215         my.config = TRUE;
216         my.get    = FALSE;
217         break;
218       case 'c':
219         my.cusers  = atoi(optarg);
220         break;
221       case 'i':
222         my.internet = TRUE;
223         break;
224       case 'b':
225         my.bench    = TRUE;
226         break;
227       case 'd':
228 	/* XXX range checking? use strtol? */
229         my.delay   = atof(optarg);
230 	if(my.delay < 0){
231 	  my.delay = 0;
232 	}
233         break;
234       case 'g':
235         my.get = TRUE;
236         break;
237       case 'p':
238         my.print  = TRUE;
239         my.cusers = 1;
240         my.reps   = 1;
241         break;
242       case 'l':
243         my.logging = TRUE;
244         if (optarg) {
245           my.logfile[strlen(optarg)] = '\0';
246           strncpy(my.logfile, optarg, strlen(optarg));
247         }
248         break;
249       case 'm':
250         my.mark    = TRUE;
251         my.markstr = optarg;
252         my.logging = TRUE;
253         break;
254       case 'q':
255         my.quiet   = TRUE;
256         break;
257       case 'v':
258         my.verbose = TRUE;
259         break;
260       case 'r':
261         if(strmatch(optarg, "once")){
262            my.reps = -1;
263         } else {
264           my.reps = atoi(optarg);
265         }
266         break;
267       case 't':
268         parse_time(optarg);
269         break;
270       case 'f':
271         memset(my.file, 0, sizeof(my.file));
272         if(optarg == NULL) break; /*paranoia*/
273         strncpy(my.file, optarg, strlen(optarg));
274         break;
275       case 'A':
276         strncpy(my.uagent, optarg, 255);
277         break;
278       case 'T':
279         strncpy(my.conttype, optarg, 255);
280         break;
281       case 'N':
282         my.parser = FALSE;
283         break;
284       case 'F':
285         my.follow = FALSE;
286         break;
287       case 'R':
288         /**
289          * processed above
290          */
291         break;
292       case 'H':
293         {
294           if(!strchr(optarg,':')) NOTIFY(FATAL, "no ':' in http-header");
295           if((strlen(optarg) + strlen(my.extra) + 3) > 2048)
296               NOTIFY(FATAL, "header is too large");
297           strcat(my.extra,optarg);
298           strcat(my.extra,"\015\012");
299         }
300         break;
301       case 'j':
302         my.json_output = TRUE;
303         break;
304 
305     } /* end of switch( c )           */
306   }   /* end of while c = getopt_long */
307   nargs = argc - optind;
308   if (nargs)
309     my.url = xstrdup(argv[argc-1]);
310   if (my.get && my.url==NULL) {
311     puts("ERROR: -g/--get requires a commandline URL");
312     exit(1);
313   }
314   return;
315 } /* end of parse_cmdline */
316 
317 private void
__signal_setup()318 __signal_setup()
319 {
320   sigset_t sigs;
321 
322   sigemptyset(&sigs);
323   sigaddset(&sigs, SIGHUP);
324   sigaddset(&sigs, SIGINT);
325   sigaddset(&sigs, SIGALRM);
326   sigaddset(&sigs, SIGTERM);
327   sigaddset(&sigs, SIGPIPE);
328   sigprocmask(SIG_BLOCK, &sigs, NULL);
329 }
330 
331 private void
__config_setup(int argc,char * argv[])332 __config_setup(int argc, char *argv[])
333 {
334 
335   memset(&my, '\0', sizeof(struct CONFIG));
336 
337   parse_rc_cmdline(argc, argv);
338   if (init_config() < 0) {
339     exit(EXIT_FAILURE);
340   }
341   parse_cmdline(argc, argv);
342   ds_module_check();
343 
344   if (my.config) {
345     show_config(TRUE);
346   }
347 
348   /**
349    * Let's tap the brakes and make sure the user knows what they're doing...
350    */
351   if (my.cusers > my.limit) {
352     printf("\n");
353     printf("================================================================\n");
354     printf("WARNING: The number of users is capped at %d.%sTo increase this\n", my.limit, (my.limit>999)?" ":"  ");
355     printf("         limit, search your .siegerc file for 'limit' and change\n");
356     printf("         its value. Make sure you read the instructions there...\n");
357     printf("================================================================\n");
358     sleep(10);
359     my.cusers = my.limit;
360   }
361 }
362 
363 private LINES *
__urls_setup()364 __urls_setup()
365 {
366   LINES * lines;
367 
368   lines          = xcalloc(1, sizeof(LINES));
369   lines->index   = 0;
370   lines->line    = NULL;
371 
372   if (my.url != NULL) {
373     my.length = 1;
374   } else {
375     my.length = read_cfg_file(lines, my.file);
376   }
377 
378   if (my.length == 0) {
379     display_help();
380   }
381 
382   return lines;
383 }
384 
385 int
main(int argc,char * argv[])386 main(int argc, char *argv[])
387 {
388   int       i, j;
389   int       result   = 0;
390   void  *   status   = NULL;
391   LINES *   lines    = NULL;
392   CREW      crew     = NULL;
393   DATA      data     = NULL;
394   HASH      cookies  = NULL;
395   ARRAY     urls     = new_array();
396   ARRAY     browsers = new_array();
397   pthread_t cease;
398   pthread_t timer;
399   pthread_attr_t scope_attr;
400 
401   __signal_setup();
402   __config_setup(argc, argv);
403   lines = __urls_setup();
404 
405   pthread_attr_init(&scope_attr);
406   pthread_attr_setscope(&scope_attr, PTHREAD_SCOPE_SYSTEM);
407 #if defined(_AIX)
408   /**
409    * AIX, for whatever reason, defies the pthreads standard and
410    * creates threads detached by default. (see pthread.h on AIX)
411    */
412   pthread_attr_setdetachstate(&scope_attr, PTHREAD_CREATE_JOINABLE);
413 #endif
414 
415 #ifdef HAVE_SSL
416   SSL_thread_setup();
417 #endif
418 
419   if (my.url != NULL) {
420     URL tmp = new_url(my.url);
421     url_set_ID(tmp, 0);
422     if (my.get && url_get_method(tmp) != POST && url_get_method(tmp) != PUT) {
423       url_set_method(tmp, my.method);
424     }
425     array_npush(urls, tmp, URLSIZE); // from cmd line
426   } else {
427     for (i = 0; i < my.length; i++) {
428       URL tmp = new_url(lines->line[i]);
429       url_set_ID(tmp, i);
430       array_npush(urls, tmp, URLSIZE);
431     }
432   }
433 
434   cookies = load_cookies(my.cookies);
435 
436   for (i = 0; i < my.cusers; i++) {
437     char    tmp[4096];
438     BROWSER B = new_browser(i);
439     memset(tmp, '\0', sizeof(tmp));
440     snprintf(tmp, 4096, "%d", i);
441     if (cookies != NULL) {
442       if (hash_get(cookies, tmp) != NULL) {
443         browser_set_cookies(B, (HASH)hash_get(cookies, tmp));
444       }
445     }
446     if (my.reps > 0 ) {
447       browser_set_urls(B, urls);
448     } else {
449       /**
450        * Scenario: -r once/--reps=once
451        */
452       int n_urls = array_length(urls);
453       int per_user = n_urls / my.cusers;
454       int remainder = n_urls % my.cusers;
455       int begin_url = i * per_user + ((i < remainder) ? i : remainder);
456       int end_url = (i + 1) * per_user + ((i < remainder) ? (i + 1) : remainder);
457       ARRAY url_slice = new_array();
458       for (j = begin_url; j < end_url && j < n_urls; j++) {
459         URL u = array_get(urls, j);
460         if (u != NULL && url_get_hostname(u) != NULL && strlen(url_get_hostname(u)) > 1) {
461           array_npush(url_slice, u, URLSIZE);
462         }
463       }
464       browser_set_urls(B, url_slice);
465     }
466     array_npush(browsers, B, BROWSERSIZE);
467   }
468 
469   if ((crew = new_crew(my.cusers, my.cusers, FALSE)) == NULL) {
470     NOTIFY(FATAL, "unable to allocate memory for %d simulated browser", my.cusers);
471   }
472 
473   if ((result = pthread_create(&cease, NULL, (void*)sig_handler, (void*)crew)) < 0) {
474     NOTIFY(FATAL, "failed to create handler: %d\n", result);
475   }
476   if (my.secs > 0) {
477     if ((result = pthread_create(&timer, NULL, (void*)siege_timer, (void*)cease)) < 0) {
478       NOTIFY(FATAL, "failed to create handler: %d\n", result);
479     }
480   }
481 
482   /**
483    * Display information about the siege to the user
484    * and prepare for verbose output if necessary.
485    */
486   if (!my.get && !my.quiet) {
487     fprintf(stderr, "** ");
488     display_version(FALSE);
489     fprintf(stderr, "** Preparing %d concurrent users for battle.\n", my.cusers);
490     fprintf(stderr, "The server is now under siege...");
491     if (my.verbose) { fprintf(stderr, "\n"); }
492   }
493 
494   data = new_data();
495   data_set_start(data);
496   for (i = 0; i < my.cusers && crew_get_shutdown(crew) != TRUE; i++) {
497     BROWSER B = (BROWSER)array_get(browsers, i);
498     result = crew_add(crew, (void*)start, B);
499     if (result == FALSE) {
500       my.verbose = FALSE;
501       fprintf(stderr, "Unable to spawn additional threads; you may need to\n");
502       fprintf(stderr, "upgrade your libraries or tune your system in order\n");
503       fprintf(stderr, "to exceed %d users.\n", my.cusers);
504       NOTIFY(FATAL, "system resources exhausted");
505     }
506   }
507   crew_join(crew, TRUE, &status);
508   data_set_stop(data);
509 
510 #ifdef HAVE_SSL
511   SSL_thread_cleanup();
512 #endif
513 
514   for (i = 0; i < ((crew_get_total(crew) > my.cusers ||
515                     crew_get_total(crew) == 0) ? my.cusers : crew_get_total(crew)); i++) {
516     BROWSER B = (BROWSER)array_get(browsers, i);
517     data_increment_count(data, browser_get_hits(B));
518     data_increment_bytes(data, browser_get_bytes(B));
519     data_increment_total(data, browser_get_time(B));
520     data_increment_code (data, browser_get_code(B));
521     data_increment_okay (data, browser_get_okay(B));
522     data_increment_fail (data, browser_get_fail(B));
523     data_set_highest    (data, browser_get_himark(B));
524     data_set_lowest     (data, browser_get_lomark(B));
525   } crew_destroy(crew);
526 
527   pthread_usleep_np(10000);
528 
529   if (!my.quiet) {
530     if (my.failures > 0 && my.failed >= my.failures) {
531       fprintf(stderr, "%s aborted due to excessive socket failure; you\n", program_name);
532       fprintf(stderr, "can change the failure threshold in $HOME/.%src\n", program_name);
533     }
534     fprintf(stderr, "\nTransactions:\t\t%12u hits\n",        data_get_count(data));
535     fprintf(stderr, "Availability:\t\t%12.2f %%\n",          data_get_count(data)==0 ? 0 :
536                                                              (double)data_get_count(data) /
537                                                              (data_get_count(data)+my.failed)*100
538     );
539     fprintf(stderr, "Elapsed time:\t\t%12.2f secs\n",        data_get_elapsed(data));
540     fprintf(stderr, "Data transferred:\t%12.2f MB\n",        data_get_megabytes(data)); /*%12llu*/
541     fprintf(stderr, "Response time:\t\t%12.2f secs\n",       data_get_response_time(data));
542     fprintf(stderr, "Transaction rate:\t%12.2f trans/sec\n", data_get_transaction_rate(data));
543     fprintf(stderr, "Throughput:\t\t%12.2f MB/sec\n",        data_get_throughput(data));
544     fprintf(stderr, "Concurrency:\t\t%12.2f\n",              data_get_concurrency(data));
545     fprintf(stderr, "Successful transactions:%12u\n",        data_get_code(data));
546     if (my.debug) {
547       fprintf(stderr, "HTTP OK received:\t%12u\n",             data_get_okay(data));
548     }
549     fprintf(stderr, "Failed transactions:\t%12u\n",          my.failed);
550     fprintf(stderr, "Longest transaction:\t%12.2f\n",        data_get_highest(data));
551     fprintf(stderr, "Shortest transaction:\t%12.2f\n",       data_get_lowest(data));
552     fprintf(stderr, " \n");
553   }
554 
555   if (my.json_output) {
556     fprintf(stderr, "\n");
557     printf("{\n");
558     printf("\t\"transactions\":\t\t\t%12u,\n", data_get_count(data));
559 
560     double availability;
561     if (data_get_count(data) == 0) {
562       availability = 0;
563     } else {
564       availability = (double)data_get_count(data) / (data_get_count(data) + my.failed) * 100;
565     }
566 
567     printf("\t\"availability\":\t\t\t%12.2f,\n", availability);
568     printf("\t\"elapsed_time\":\t\t\t%12.2f,\n", data_get_elapsed(data));
569     printf("\t\"data_transferred\":\t\t%12.2f,\n", data_get_megabytes(data)); /*%12llu*/
570     printf("\t\"response_time\":\t\t%12.2f,\n", data_get_response_time(data));
571     printf("\t\"transaction_rate\":\t\t%12.2f,\n", data_get_transaction_rate(data));
572     printf("\t\"throughput\":\t\t\t%12.2f,\n", data_get_throughput(data));
573     printf("\t\"concurrency\":\t\t\t%12.2f,\n", data_get_concurrency(data));
574     printf("\t\"successful_transactions\":\t%12u,\n", data_get_code(data));
575 
576     if (my.debug) {
577       printf("\t\"http_ok_received\":\t\t%12u,\n", data_get_okay(data));
578     }
579 
580     printf("\t\"failed_transactions\":\t\t%12u,\n", my.failed);
581     printf("\t\"longest_transaction\":\t\t%12.2f,\n", data_get_highest(data));
582     printf("\t\"shortest_transaction\":\t\t%12.2f\n", data_get_lowest(data));
583     puts("}");
584   }
585 
586   if (my.mark)    mark_log_file(my.markstr);
587   if (my.logging) {
588     log_transaction(data);
589     if (my.failures > 0 && my.failed >= my.failures) {
590       mark_log_file("siege aborted due to excessive socket failure.");
591     }
592   }
593 
594   /**
595    * Let's clean up after ourselves....
596    */
597   data       = data_destroy(data);
598   urls       = array_destroyer(urls, (void*)url_destroy);
599   browsers   = array_destroyer(browsers, (void*)browser_destroy);
600   cookies    = hash_destroy(cookies);
601   my.cookies = cookies_destroy(my.cookies);
602 
603   if (my.url == NULL) {
604     for (i = 0; i < my.length; i++)
605       xfree(lines->line[i]);
606     xfree(lines->line);
607     xfree(lines);
608   } else {
609     xfree(lines->line);
610     xfree(lines);
611   }
612 
613   exit(EXIT_SUCCESS);
614 } /* end of int main **/
615