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