1package HTTPD::Bench::ApacheBench; 2 3use strict; 4use vars qw($VERSION @ISA); 5 6use base qw(DynaLoader HTTPD::Bench::ApacheBench::Regression); 7 8use HTTPD::Bench::ApacheBench::Run; 9use Scalar::Util qw/blessed/; 10 11$HTTPD::Bench::ApacheBench::VERSION = '0.73'; 12 13bootstrap HTTPD::Bench::ApacheBench $VERSION; 14 15################################################## 16## the constructor ## 17################################################## 18sub new { 19 my ($this, $self) = @_; 20 my $class = ref($this) || $this; 21 if (ref($self) ne 'HASH') { $self = {} } 22 bless $self, $class; 23 $self->initialize; 24 return $self; 25} 26 27################################################## 28## initialize defaults ## 29################################################## 30sub initialize { 31 my ($self) = @_; 32 $self->{runs} = [] if ref $self->{runs} ne 'ARRAY'; 33 $self->{concurrency} ||= 1; 34 $self->{repeat} ||= 1; 35 $self->{priority} ||= "equal_opportunity"; 36 $self->{buffersize} ||= 262144; 37 $self->{request_buffersize} ||= 8192; 38 $self->{timelimit} = undef if ! defined $self->{timelimit}; 39 $self->{keepalive} = 0 if ! defined $self->{keepalive}; 40 $self->{memory} = 1 if ! defined $self->{memory}; 41} 42 43 44################################################## 45## configure the global parameters ## 46################################################## 47sub config { 48 my ($self, $opt) = @_; 49 foreach (qw(concurrency priority buffersize repeat memory)) { 50 $self->{$_} = $opt->{$_} if defined $opt->{$_}; 51 } 52} 53 54sub concurrency { 55 my ($self, $arg) = @_; 56 $self->{concurrency} = $arg if $arg; 57 return $self->{concurrency}; 58} 59 60sub priority { 61 my ($self, $arg) = @_; 62 $self->{priority} = $arg if $arg; 63 return $self->{priority}; 64} 65 66sub memory { 67 my ($self, $arg) = @_; 68 $self->{memory} = $arg if defined $arg; 69 return $self->{memory}; 70} 71 72sub repeat { 73 my ($self, $arg) = @_; 74 $self->{repeat} = $arg if $arg; 75 return $self->{repeat}; 76} 77 78sub keepalive { 79 my ($self, $arg) = @_; 80 $self->{keepalive} = $arg if $arg; 81 return $self->{keepalive}; 82} 83 84sub timelimit { 85 my ($self, $arg) = @_; 86 $self->{timelimit} = $arg if $arg; 87 return $self->{timelimit}; 88} 89 90sub buffersize { 91 my ($self, $arg) = @_; 92 $self->{buffersize} = $arg if $arg; 93 return $self->{buffersize}; 94} 95 96sub request_buffersize { 97 my ($self, $arg) = @_; 98 $self->{request_buffersize} = $arg if $arg; 99 return $self->{request_buffersize}; 100} 101 102sub total_requests { 103 my ($self) = @_; 104 return 0 if ref $self->{runs} ne 'ARRAY'; 105 my $total = 0; 106 foreach my $run (@{$self->{runs}}) { 107 my $repeat = $run->repeat ? $run->repeat : $self->{repeat}; 108 $total += ($#{$run->urls} + 1) * $repeat 109 if ref $run->urls eq 'ARRAY'; 110 } 111 return $total; 112} 113 114 115################################################## 116## verify configuration of runs and execute ## 117################################################## 118sub execute { 119 my ($self) = @_; 120 # keep track of temporarily altered run object variables 121 my %altered; 122 123 # fail if they have not added any runs 124 return undef if ref $self->{runs} ne 'ARRAY'; 125 126 # pre execute initialization of each run 127 foreach my $run_no (0..$#{$self->{runs}}) { 128 my $runobj = $self->{runs}->[$run_no]; 129 130 $runobj->ready_to_execute or $runobj->prepare_for_execute or 131 return undef; 132 133 # default to base ApacheBench object variables if not specified in run 134 if (! $runobj->repeat) { 135 $runobj->repeat($self->{repeat}); 136 $altered{$run_no}->{repeat} = 1; 137 } 138 if (! defined $runobj->memory) { 139 $runobj->memory($self->{memory}); 140 $altered{$run_no}->{memory} = 1; 141 } 142 143 $runobj->pre_execute_warnings; 144 } 145 146 # call the XS code and store regression data 147 $self->{'regression'} = $self->ab; 148 149 # post execute polishing of each run 150 foreach my $run_no (0..$#{$self->{runs}}) { 151 my $runobj = $self->{runs}->[$run_no]; 152 $runobj->{'run_no'} = $run_no; 153 $runobj->{'regression'} = $self->{'regression'}; 154 foreach my $param (qw(repeat memory)) { 155 delete $runobj->{$param} 156 if (ref $altered{$run_no} and $altered{$run_no}->{$param}); 157 } 158 } 159 return HTTPD::Bench::ApacheBench::Regression->new 160 ({ 'regression' => $self->{'regression'} }); 161} 162 163 164################################################## 165## run accessors ## 166################################################## 167sub run { 168 my ($self, $run_no, $run) = @_; 169 return undef if ! (ref $self->{runs} eq 'ARRAY' && blessed $self->{runs}->[$run_no] 170 && $self->{runs}->[$run_no]->isa('HTTPD::Bench::ApacheBench::Run')); 171 if (blessed $run && $run->isa('HTTPD::Bench::ApacheBench::Run')) { 172 my $replaced_run = $self->{runs}->[$run_no]; 173 $self->{runs}->[$run_no] = $run; 174 return $replaced_run; 175 } 176 return $self->{runs}->[$run_no]; 177} 178 179sub add_run { 180 my ($self, $newrun) = @_; 181 return undef if ! (ref $self->{runs} eq 'ARRAY' && blessed $newrun 182 && $newrun->isa('HTTPD::Bench::ApacheBench::Run')); 183 push(@{$self->{runs}}, $newrun); 184 return $#{$self->{runs}}; 185} 186 187sub delete_run { 188 my ($self, $run_no) = @_; 189 return undef if ref $self->{runs} ne 'ARRAY'; 190 my $deleted_run = $self->{runs}->[$run_no]; 191 $self->{runs} = [ @{$self->{runs}}[0..$run_no-1], 192 @{$self->{runs}}[$run_no+1..$#{$self->{runs}}] ]; 193 return $deleted_run; 194} 195 196sub num_runs { 197 my ($self) = @_; 198 return scalar(@{$self->{runs} || []}); 199} 200 201 2021; 203 204__END__ 205 206=head1 NAME 207 208HTTPD::Bench::ApacheBench - Perl API for Apache benchmarking and regression testing. 209 210=head1 SYNOPSIS 211 212 use HTTPD::Bench::ApacheBench; 213 214 my $b = HTTPD::Bench::ApacheBench->new; 215 216 # global configuration 217 $b->concurrency(5); 218 $b->priority("run_priority"); 219 220 # add HTTP request sequences (aka: runs) 221 my $run1 = HTTPD::Bench::ApacheBench::Run->new 222 ({ urls => ["http://localhost/one", "http://localhost/two"] }); 223 $b->add_run($run1); 224 225 my $run2 = HTTPD::Bench::ApacheBench::Run->new 226 ({ urls => ["http://localhost/three", "http://localhost/four"], 227 cookies => ["Login_Cookie=b3dcc9bac34b7e60;"], 228 # note: manual cookies no longer necessary due to use_auto_cookies (enabled by default) 229 order => "depth_first", 230 repeat => 10, 231 memory => 2 }); 232 $b->add_run($run2); 233 234 my $run3 = HTTPD::Bench::ApacheBench::Run->new 235 ({ urls => ["http://localhost/five", "http://localhost/six"], 236 memory => 3, 237 postdata => [ 238 sub($) { sleep(int(rand(5)) + 1); return undef; }, 239 sub($) { 240 my $prev_response = shift; 241 sleep(int(rand(5)) + 1); 242 my ($username) = ( $prev_response =~ m|<div id="userName">([^<>]+)</div>|i ); 243 return $username ? 'cgi-username='.$username : undef; 244 }, 245 ] }); 246 $b->add_run($run3); 247 248 # send HTTP request sequences to server and time responses 249 my $ro = $b->execute; 250 251 # calculate hits/sec 252 print ((1000*$b->total_requests/$b->total_time)." req/sec\n"); 253 254 # show request times (in ms) for $run1, 1st repetition 255 print join(', ', @{$run1->request_times}) . "\n"; 256 257 # show response times (in ms) for $run2, 7th repetition 258 print join(', ', @{$run2->iteration(6)->response_times}) . "\n"; 259 260 # dump the entire regression object (WARNING, this could be a LOT OF DATA) 261 use Data::Dumper; 262 my $d = Data::Dumper->new([$ro]); 263 print $d->Dumpxs; 264 265 266=head1 GOALS 267 268This project is meant to be the foundation of a complete benchmarking 269and regression testing suite for an advanced, transaction-based mod_perl 270site. We need to be able to stress our server to its limit while also 271having a way to verify the HTTP responses for correctness. Since our site 272is transaction-based (as opposed to content-based), we needed to extend 273the single-URL ab model to a multiple-URL sequence model. 274 275ApacheBench was originally based on the Apache 1.3.12 ab code 276(src/support/ab.c), but has since undergone major rewrites and now barely 277resembles ab.c. 278 279Note: although this tool was designed to be used on an Apache mod_perl 280site, it is generally applicable to any HTTP-compliant server. Beware, 281however, that it sends a high volume of HTTP requests in a very short period 282of time, which may overwhelm some weaker HTTP server implementations 283like NT/IIS. 284 285=head1 DESCRIPTION 286 287ApacheBench sends sequences of HTTP requests to an HTTP server and keeps 288track of the time taken to receive a response, the data that was returned, 289the size of the data that was returned, and various other bits of information. 290 291Since it is implemented in C, it sends HTTP requests in a tight loop which 292can stress your server to 100% capacity, especially if invoked in multiple 293concurrent instances. It gives accurate time measurements down to the 294millisecond for each HTTP request-response interval. 295 296Included is a simplified re-implementation of ab using the ApacheBench/Perl 297API. This should help get you started with ApacheBench. 298 299=head1 CONFIGURATION METHODS 300 301You need to tell ApacheBench what the requests will be, in what order to 302send them, and how to prioritize sending them. ApacheBench was designed 303to simulate many users logged in simultaneously, each of whom may be doing 304many different transactions at once. 305 306=head2 Global configuration methods 307 308Global configuration methods apply to all benchmarking runs associated 309with this ApacheBench object. 310 311=over 4 312 313=item $b = HTTPD::Bench::ApacheBench->new() 314 315Constructor. 316 317=item $b->concurrency( $concur_level ) 318 319Number of requests to send simultaneously (default: B<1>) 320 321=item $b->priority( $priority ) 322 323$priority can be either "B<equal_opportunity>" or "B<run_priority>". 324 325If set to "B<equal_opportunity>", all benchmark runs that are configured 326(see below) under this ApacheBench object are given equal access to 327the concurrency level. This means requests are taken from each run 328and sent in parallel (the level of parallelism defined by concurrency()). 329 330If set to "B<run_priority>", the benchmark runs that are configured first 331get the highest priority. This means all requests in $b->run(0) will 332be sent before any requests in $b->run(1) are sent, provided that 333$b->run(0)->order eq "B<breadth_first>" (see below). 334 335See L<"EXAMPLES"> near the bottom of this manual for a tutorial on the 336effects of "B<equal_opportunity>" vs. "B<run_priority>". 337 338(default: "B<equal_opportunity>") 339 340=item $b->repeat( $n ) 341 342The number of times to repeat the request sequence in each run. 343This can be overridden on a per-run basis (see below). 344 345(default: B<1>) 346 347=item $b->keepalive( 0|1 ) 348 349Whether or not to use HTTP Keep-Alive feature for requests configured in this 350object. This can be overridden on a per-url/per-run basis (see below). 351 352B<Warning>: If you configure runs which contain requests to more than one 353hostname/port, be aware that setting $b->keepalive() may not improve 354performance. See the discussion in the $run->keepalive() section for more 355details. 356 357(default: B<0>) 358 359=item $b->timelimit( $sec_to_max_wait ) 360 361Set the maximum number of seconds to wait for requests configured in this 362object to complete (i.e. receive the full response from the server). 363B<Warning>: once the specified number of seconds have elapsed, ApacheBench 364will read one last chunk of data from each open socket, and exit. This could 365result in partially received responses, which will cause broken sockets for 366the server. 367 368Per-url/per-run time limits can also be specified using the $run->timelimits() 369method, but it does B<not> override this global setting. If $b->timelimit() 370is set, ApacheBench will exit after $sec_to_max_wait (or slightly over, due to 371final connection reads and building regression data), regardless of any other 372settings. 373 374(default: B<0>, which means wait an unlimited amount of time) 375 376=item $b->buffersize( $bufsz ) 377 378The size of the buffer in which to store HTTP response bodies, in bytes. 379If an HTTP response is received with a size larger than this limit, 380the content is truncated at length $bufsz, and a warning is issued. 381This method has no effect if $b->memory() < 3. 382This can be overridden on a per-run basis (see below). 383 384(default: B<262144> or 256K) 385 386=item $b->request_buffersize( $req_bufsz ) 387 388The size of the buffer in which to store HTTP requests, in bytes. If you 389configure an HTTP request that is larger than this limit, unpredictable 390things will happen. (most likely a segmentation fault) 391 392(default: B<8192> or 8K) 393 394=item $b->memory( $memlvl ) 395 396The memory level. Controls how much data ApacheBench will remember and 397return in the regression object. This can be overridden on a per-run basis 398(see below). 399 400(default: B<1>) 401 402Key: 403 $memlvl => Description 404 405 0 => Remember nothing. (actually still keeps global 406 regression data: total_time, bytes_received, 407 and warnings) 408 409 1 => Remember connect/response times and minimal 410 summary information about size of requests and 411 responses. 412 413 2 => Remember connect/response times and all 414 information about size of requests and responses. 415 Also keeps an array of all HTTP response 416 headers returned by the server for each request. 417 418 3 => Remember connect/response times, all information 419 about request/response sizes, HTTP response 420 headers, and also all content of every HTTP 421 response returned by the server. B<Warning>: 422 this can quickly eat up all available main 423 memory if used with large runs. 424 425=item $b->add_run( $run_object ) 426 427Schedule a run for this ApacheBench object. Returns the run number where 428this object was inserted, or undef on failure. See below for details on 429$run_object. 430 431=item $run = $b->run( $run_no, [$run_object] ) 432 433Returns the run object stored in location $run_no. If a run object is passed 434as the optional second argument, it is stored in location $run_no, displacing 435whatever was there. The displaced run object is then returned. 436 437=item $b->delete_run( $run_no ) 438 439Delete the run object stored in location $run_no. The deleted run object 440is returned to the caller for safety sake. 441 442=item $b->num_runs() 443 444Returns the number of runs currently configured in $b. 445 446=back 447 448=head2 Run configuration methods 449 450You need to configure one or more benchmark runs. A run is defined as an 451ordered sequence of HTTP requests which can be repeated multiple times, 452and scheduled to be sent in different ways. 453 454=over 4 455 456=item $run = HTTPD::Bench::ApacheBench::Run->new({ urls => [ @url_list ] }) 457 458Construct a run object with the ordered sequence of HTTP requests 459in @url_list. 460 461=item $run->repeat( $n ) 462 463Number of times to repeat this request sequence. 464 465(default: B<1>, or whatever is specified in global configuration) 466 467=item $run->use_auto_cookies( 0|1 ) 468 469Controls whether to enable dynamic setting of cookies based on previous 470response headers in this run. If set, will parse the Set-Cookie: headers 471in each response in the run, and set the corresponding Cookie: headers in 472all subsequent requests in this run. (basically a crude, but fast emulation 473of a browser's cookie handling mechanism) The cookies are cumulative for 474each iteration of the run, so they will accumulate with each request/response 475pair until the next iteration, when they get reset. 476 477(default: B<1>) 478 479=item $run->cookies( \@cookies ) 480 481Set any extra HTTP Cookie: headers for each B<repetition> of this run. 482Length of @cookies should equal $n (whatever you set $run->repeat to). 483If this option is omitted, only auto-set cookies will be sent in 484requests for this run. 485 486If you need to set different cookies within a single URL sequence, use 487the request_headers() method. 488 489Note: this is somewhat obsolete now that there is support for dynamic 490cookies, but is kept for backwards compatibility and in case you want to 491add your own "static" cookies. 492 493Example usage: You could simulate $n users all doing the same transaction 494simultaneously by giving $n different login cookies here. Say you have 495login cookies in an array called @login of length $n. Set $run->repeat($n), 496$run->order("breadth_first"), $run->cookies([map {$login[$_]} 0..$n-1]), and 497ApacheBench will perform the transaction sequence set by $run->urls $n times, 498each with a separate login cookie. 499 500=item $run->urls( \@url_list ) 501 502Set the HTTP request URLs for this run. A @url_list B<must> be given for 503each run, otherwise the run will not execute. Typically @url_list is set 504using the constructor. 505 506=item $run->postdata( \@postdata ) 507 508Set the HTTP POST request body contents. If an undef value is encountered 509in @postdata, that request will be a GET request (or a HEAD request if you 510used $run->head_requests() below). If this option is omitted, all requests 511for this run will be GET (or HEAD) requests. 512 513Length of @postdata should equal the length of @url_list. 514 515The @postdata array should consist of either strings or code refs (or undef). 516A string value will make this request always a POST. An undef makes this 517request always a GET. If a code ref, it will depend on the return value of 518the called code. (see below for details) 519 520The strings should contain exactly what you want sent in the HTTP POST request 521body. For example, 522 523 @postdata = (undef, undef, 'cgikey1=val1&cgikey2=val2'); 524 525will send two GET requests, then a POST request with the CGI parameter 526'cgikey1' set to 'val1' and the CGI parameter 'cgikey2' set to 'val2'. 527 528The code refs should be references to subroutines that take one string 529argument, which will be the http response (both headers and body) returned 530from the *previous* request in this run, and return CGI post data as above. 531If a subroutine returns undef, then the request will be a GET rather than a 532POST. If the return value is a string (even empty string), the request sent 533will be a POST with the string as the request body. 534 535For example, 536 537 @postdata = (undef, sub($) { 538 my $prev_response = shift; 539 my ($username) = ( $prev_response =~ m|<div id="userName">([^<>]+)</div>|i ); 540 return $username ? 'cgi-username='.$username : undef; 541 }); 542 543will send one GET request, then it will scan the HTTP response body returned by 544the server for that GET request, and pull out any text (non-HTML) inside the HTML div 545with id = "userName". 546 547This text is then sent in a POST request as the value of CGI parameter 'cgi-username'. 548If the <div id="userName"> element is not found in the most recent http response, 549then the second request will be a GET instead of a POST request. 550 551WARNING: when using code refs in @postdata, you need to be running with $run->memory(3), 552since it needs to remember the most recent http response content. If you forget, you will 553be warned at runtime (and your POST requests will likely not send the desired content). 554 555NOTES: 556 557 1. The time taken to call your postdata function is not included in the 558 times reported in the Regression object. This is intentional, so you can 559 benchmark your server response time, not your load testing script. 560 561 2. You can use the above property to implement a random delay between http 562 requests; while this is sort of a hack of the postdata function feature, 563 it is much easier to implement a random delay this way than to add it in C. 564 565 e.g. the following postdata coderef would produce a GET request with a 566 random 1-5 sec delay: 567 568 sub($) { 569 sleep(int(rand(5)) + 1); 570 return undef; 571 } 572 573 574=item $run->head_requests( \@head_reqs ) 575 576Send HTTP HEAD requests for the specified requests in this run. The 577length of @head_reqs should equal the length of @url_list, and it is 578interpreted as an array of booleans. Any true value in @head_reqs will 579result in a HEAD request being sent for the corresponding URL (unless the 580corresponding postdata() value is defined, in which case a POST will be sent). 581 582You can configure a run composed of any combination of the three HTTP 583request types (GET, HEAD, and POST), but note that an individual URL with a 584defined postdata() value will cause a POST request regardless of whether 585head_requests() is set for that URL. The following precedence table 586illustrates which type of request will be sent for URL $url_no in the sequence. 587 588 defined $run->postdata->[$url_no] && ! ref($run->postdata->[$url_no]) 589 ? ==> POST request 590 defined $run->postdata->[$url_no] && ref($run->postdata->[$url_no]) eq 'CODE' 591 && defined $run->postdata->[$url_no]->( $preceding_response ) 592 ? ==> POST request 593 defined $run->postdata->[$url_no] && ref($run->postdata->[$url_no]) eq 'CODE' 594 && ! defined $run->postdata->[$url_no]->( $preceding_response ) 595 ? ==> GET request 596 $run->head_requests->[$url_no] ? ==> HEAD request 597 else : ==> GET request 598 599=item $run->content_types( \@ctypes ) 600 601Set the Content-type: header for each POST request in this run. Default 602is "application/x-www-form-urlencoded" which will be used if an undef 603value is encountered in @ctypes. Length of @ctypes should equal the 604length of @postdata. Only sends the Content-type: header for POST requests: 605a defined value in @ctypes with an undef in the corresponding @postdata will 606result in no Content-type: header being sent. 607 608=item $run->request_headers( \@req_headers ) 609 610Set arbitrary HTTP request headers for each request in this run, which will 611be inserted after all normal headers. Multiple extra headers for a single 612url should be separated with "\r\n". An undef value in @req_headers results 613in no extra HTTP request headers being sent for the corresponding url. 614If this option is omitted, no extra HTTP request headers will be sent in 615any of the requests for this run. Length of @req_headers should equal the 616length of @url_list. 617 618The following example for a @url_list of length 4 produces two requests with 619no extra headers, one with 1 extra header, and one with 2 extra headers. 620 621 $run->request_headers([ undef, undef, "Extra-Header: bread", 622 "Extra-Header1: butter\r\nExtra-Header2: toaster" ]) 623 624=item $run->keepalive( \@keepalives ) 625 626Use HTTP Keep-Alive feature for the specified requests in this run. The 627length of @keepalives should equal the length of @url_list, and it is 628interpreted as an array of booleans, with undef indicating to use the 629object default set by $b->keepalive(). Any true value in @keepalives will 630result in a Keep-Alive HTTP request being sent for the corresponding URL. 631 632To achieve full performance benefits from this feature, you need to be sure 633your Keep-Alive requests are consecutive. If a non-Keep-Alive request or 634a request for a different hostname or port immediately follows a Keep-Alive 635request B<in the connection slot>, I<the connection will be closed> and a 636new connection will be opened. 637 638Further, keep in mind that for $b->concurrency() > 1, there are many 639connection slots open and even though requests in @url_list will be sent 640in order, there is no guarantee they will all use the same connection slot. 641The HTTP Keep-Alive feature only yields performance benefits when consecutive 642Keep-Alive requests use the same connection slot. Otherwise ApacheBench has 643to close and re-open connections, resulting in the same performance as not 644using keepalive() at all. 645 646To guarantee consecutive Keep-Alive requests with $b->concurrency() > 1, 647I recommend you either declare I<all> URLs in all runs as keepalive() 648(this can be done by setting $b->keepalive( 1 ) and not overriding it by 649calling keepalive() for any runs), or set $run->order( "depth_first" ) and 650$b->priority( "run_priority" ). This is the only combination of configuration 651options that guarantees consecutive, same-slot Keep-Alive requests 652regardless of your concurrency setting. 653 654For $b->concurrency() == 1, things are simpler. At any given time, there 655is only one connection slot open, so just make sure your keepalive URLs are 656consecutive within each run (if in "run_priority" mode), or that 657equal-numbered repetitions of URLs in all runs are keepalive (if in 658"equal_opportunity" mode), and be sure that all requests are to the 659same hostname/port. 660 661=item $run->timelimits( \@timelimits ) 662 663Set the maximum number of seconds to wait for requests in this 664run to complete (i.e. receive the full response from the server). The 665length of @timelimits should equal the length of @url_list, and it is 666interpreted as an array of double precision floating point numbers 667(representing the number of seconds to wait for a response). An undef or 0 668represents waiting an indefinite amount of time for that particular response. 669If this option is not configured, there will be no time limit on any responses. 670 671B<Warning>: once the specified number of seconds have elapsed on the specified 672URL, ApacheBench will close the connection immediately. This can cause strange 673results in the regression data for this request. It could also result in 674partially received responses, which will cause broken sockets for the server. 675 676=item $run->order( $order ) 677 678Either "B<depth_first>" or "B<breadth_first>" 679 680"B<breadth_first>" mode sends $n of the first request in the 681@url_list, then $n of the second request in the @urls_list, 682then $n of the third... and so on. (e.g. If $n == 3 and @url_list 683contains two requests, then ApacheBench would send the first 684request 3 times, and then the second request 3 times.) 685 686"B<depth_first>" mode ensures that HTTP requests in the sequence are 687sent in order, completing a full sequence before starting again for the 688next repeat() iteration. (e.g. If $n == 3 and @url_list contains two 689requests, then ApacheBench would send the @url_list sequence in order, 690then send it again, and then again. A total of six requests would be sent.) 691 692See L<"EXAMPLES"> near the bottom of this manual for a tutorial on the 693effects of "B<breadth_first>" vs. "B<depth_first>". 694 695(default: "B<breadth_first>") 696 697B<Note:> if $run->repeat() == 1, or the length of $run->urls() is 1, then the 698B<order> option has no effect 699 700=item $run->buffersize( $bufsz ) 701 702The size of the buffer in which to store HTTP response bodies. 703If an HTTP response is received with a size larger than this limit, 704the content is truncated at length $bufsz, and a warning is issued. 705This method has no effect if $run->memory() < 3. 706 707(default: B<262144> or 256K, or whatever is specified in global configuration) 708 709=item $run->memory( $memlvl ) 710 711The memory level. Controls how much data ApacheBench will remember and 712return in the regression object for this run. See global configuration 713method of same name for detailed description. 714 715(default: B<1>, or whatever is specified in global configuration) 716 717=back 718 719=head1 EXECUTION METHODS 720 721=head2 Global execution methods 722 723=over 4 724 725=item $b->execute 726 727Send all scheduled runs to their respective destinations, and record 728response data. 729 730=back 731 732=head2 Run execution methods 733 734=over 4 735 736=item $run->ready_to_execute 737 738Check all run configuration parameters for sanity. Returns 1 to 739indicate a call to $b->execute() I<should> complete safely. Returns 0 to 740indicate some more parameters need setting before $b->execute() should be 741called. Some of these parameters are automatically set by 742prepare_for_execute() (below). 743 744=item $run->prepare_for_execute 745 746Set any run configuration parameters that were unspecified to sane values 747in order to prevent a segmentation fault on execute(). Returns 1 if it 748could set all parameters necessary to cause ready_to_execute() to return 1. 749Returns 0 otherwise. 750 751Note: this method is automatically called from execute() before entering 752any XS code. 753 754=back 755 756=head1 REGRESSION METHODS 757 758All of the following methods will return B<undef> unless the underlying 759ApacheBench object has been execute()'d at least once. 760 761=head2 Global regression methods 762 763=over 4 764 765=item $b->total_time 766 767Total time, in milliseconds, between the start of the first request in the 768first run, and the end of the final response in the final run. 769 770=item $b->bytes_received 771 772Total bytes received from all responses in all runs. 773 774=item $b->total_requests 775 776Total number of HTTP requests which were configured in this object. 777 778=item $b->total_requests_sent 779 780Total number of HTTP requests which were successfully sent to the server(s). 781 782=item $b->total_responses_received 783 784Total number of complete, successful HTTP responses received. 785 786=item $b->total_responses_failed 787 788Total number of HTTP responses which were not received successfully. 789Check the warning messages for possible explanations of why they failed. 790 791=item $b->warnings 792 793Various warning messages. 794 795=back 796 797=head2 Run regression methods 798 799=over 4 800 801=item $run->sent_requests( $url_no ) 802 803Returns the number of HTTP requests which were B<successfully> sent to the 804server for the URL specified by $url_no. 805 806If $url_no is not given, returns a reference to an array the same length as 807$run->urls() which contains the number of B<successful> HTTP requests sent 808for each url in this run. 809 810=item $run->good_responses( $url_no ) 811 812Returns the number of complete, B<successful> HTTP responses received for the 813URL specified by $url_no. 814 815If $url_no is not given, returns a reference to an array the same length as 816$run->urls() which contains the number of B<successful> HTTP responses 817received for each HTTP request in this run. 818 819=item $run->failed_responses( $url_no ) 820 821Returns the number of HTTP responses which failed or were otherwise 822B<unsuccessful>, for the URL specified by $url_no. 823 824If $url_no is not given, returns a reference to an array the same length as 825$run->urls() which contains the number of B<unsuccessful> HTTP responses 826received for each HTTP request in this run. 827 828=item $i = $run->iteration( $iter_no ) 829 830Return a regression object specific to iteration $iter_no of this run. 831If $iter_no is not given, it assumes 0, or the first iteration of the run. 832The number of iterations for the run can be retrieved with $run->repeat(). 833 834=item $i->connect_times( $url_no ) 835 836Returns the connection time, in milliseconds, for the URL specified by $url_no. 837 838If $url_no is not given, returns a reference to an array the same length as 839$run->urls() which contains connection times, in milliseconds, for each HTTP 840request in this iteration of the sequence. 841 842=item $i->min_connect_time 843 844The minimum connect time of all requests in the sequence. 845 846=item $i->max_connect_time 847 848The maximum connect time of all requests in the sequence. 849 850=item $i->avg_connect_time 851 852The average connect time of all requests in the sequence. 853 854=item $i->sum_connect_time 855 856The total connect time of all requests in the sequence (equal to the 857summation of all elements of $i->connect_times). 858 859=item $i->request_times( $url_no ) 860 861Returns the time taken to send a request to the server, in milliseconds, 862for the URL specified by $url_no. 863 864If $url_no is not given, returns a reference to an array the same length as 865$run->urls() which contains the times taken to send a request to the server, 866in milliseconds, for each HTTP request in the sequence. 867 868=item $i->min_request_time 869 870The minimum request time of all requests in the sequence. 871 872=item $i->max_request_time 873 874The maximum request time of all requests in the sequence. 875 876=item $i->avg_request_time 877 878The average request time of all requests in the sequence. 879 880=item $i->sum_request_time 881 882The total request time of all requests in the sequence (equal to the 883summation of all elements of $i->request_times). 884 885=item $i->response_times( $url_no ) 886 887Returns the time taken to receive a response from the server, in milliseconds, 888for the URL specified by $url_no. 889 890If $url_no is not given, returns a reference to an array the same length as 891$run->urls() which contains the times taken to receive a response from the 892server, in milliseconds, for each HTTP request in the sequence. 893 894=item $i->min_response_time 895 896The minimum response time of all requests in the sequence. 897 898=item $i->max_response_time 899 900The maximum response time of all requests in the sequence. 901 902=item $i->avg_response_time 903 904The average response time of all requests in the sequence. 905 906=item $i->sum_response_time 907 908The total response time of all requests in the sequence (equal to the 909summation of all elements of $i->response_times). 910 911=item $i->bytes_posted( $url_no ) 912 913Returns the length of the HTTP POST request body for the URL specified 914by $url_no. 915 916If $url_no is not given, returns a reference to an array the same length as 917$run->urls() which contains the number of bytes posted to the server for each 918HTTP request in the sequence. 919 920(return value will be undefined if B<memory> < 2) 921 922=item $i->sum_bytes_posted 923 924The total number of bytes posted to the server in HTTP requests over this 925iteration of the request sequence. 926 927=item $i->bytes_read( $url_no ) 928 929Returns the length of the HTTP response body for the URL specified by $url_no. 930 931If $url_no is not given, returns a reference to an array the same length as 932$run->urls() which contains the total number of bytes read from the server in 933each HTTP response in the sequence. 934 935(return value will be undefined if B<memory> < 2) 936 937=item $i->sum_bytes_read 938 939The total number of bytes read from the server in HTTP responses over this 940iteration of the request sequence. 941 942=item $i->request_body( $url_no ) 943 944Returns the full HTTP request sent to the server for the URL specified 945by $url_no. 946 947If $url_no is not given, returns a reference to an array the same length as 948$run->urls() which contains the full HTTP request (including HTTP request 949headers) sent to the server for each URL in the sequence. 950 951(return value will be undefined if B<memory> < 3) 952 953=item $i->response_headers( $url_no ) 954 955Returns the HTTP response header returned by the server for the URL specified 956by $url_no. 957 958If $url_no is not given, returns a reference to an array the same length as 959$run->urls() which contains the HTTP response headers returned by the server 960for each URL in the sequence. 961 962(return value will be undefined if B<memory> < 2) 963 964=item $i->response_body( $url_no ) 965 966Returns the full HTTP response page content returned by the server 967(including HTTP headers) for the URL specified by $url_no. 968 969If $url_no is not given, returns a reference to an array the same length as 970$run->urls() which contains the full HTTP responses returned by the server for 971each URL in the sequence. 972 973(return value will be undefined if B<memory> < 3) 974 975=item $i->response_body_lengths( $url_no ) 976 977Returns the length of the document (in bytes) returned by the server, 978for the URL specified by $url_no. 979 980If $url_no is not given, returns a reference to an array the same length as 981$run->urls() which contains the length of the document (in bytes) returned by 982the server for each HTTP response in the sequence. This should be equivalent 983to the Content-Length: line in the response headers, if the server set them 984correctly. The following should also be true, 985for 1 <= $j <= size($run->urls()): 986 987( $i->response_body_lengths()->[$j] == $i->bytes_read()->[$j] - length($i->response_headers()->[$j]) ) 988 989(return value will be undefined if B<memory> < 2) 990 991=back 992 993=head1 HTTP Proxies 994 995ApacheBench can be used to make HTTP requests via an HTTP proxy. To do so, 996one passes a "special" URI to the ApacheBench::Run constructor or urls 997method. This special URI is of the form 998http://proxyserver:proxyport/http://realserver:realport/... 999 1000=head1 EXAMPLES 1001 1002The following examples of ApacheBench usage are paired with the resulting 1003output from an Apache access_log. This should give you a feel for how the 1004global $b->priority() method and the per-run $run->order() method affect how 1005HTTP requests are sent. 1006 1007First, let's set $b->priority("B<equal_opportunity>") (its default). 1008 1009 my $b = HTTPD::Bench::ApacheBench->new; 1010 $b->concurrency(1); 1011 $b->priority("equal_opportunity"); 1012 1013Add a single run and execute, then look at what gets sent to Apache. 1014 1015 my $run = HTTPD::Bench::ApacheBench::Run->new 1016 ({ 1017 repeat => 3, 1018 urls => [ "http://localhost/", 1019 "http://localhost/server-status" ], 1020 order => "breadth_first", 1021 }); 1022 $b->add_run($run); 1023 $b->execute; 1024 1025Apache access_log output: 1026 1027 127.0.0.1 - - [20/Sep/2000:18:43:32 -0400] "GET / HTTP/1.0" 200 5565 1028 127.0.0.1 - - [20/Sep/2000:18:43:32 -0400] "GET / HTTP/1.0" 200 5565 1029 127.0.0.1 - - [20/Sep/2000:18:43:32 -0400] "GET / HTTP/1.0" 200 5565 1030 127.0.0.1 - - [20/Sep/2000:18:43:32 -0400] "GET /server-status HTTP/1.0" 200 5294 1031 127.0.0.1 - - [20/Sep/2000:18:43:32 -0400] "GET /server-status HTTP/1.0" 200 5294 1032 127.0.0.1 - - [20/Sep/2000:18:43:32 -0400] "GET /server-status HTTP/1.0" 200 5294 1033 1034Let's add another run and execute, and see what Apache sees. 1035 1036 my $run2 = HTTPD::Bench::ApacheBench::Run->new 1037 ({ 1038 repeat => 3, 1039 urls => [ "http://localhost/perl-status", 1040 "http://localhost/proxy-status" ], 1041 order => "breadth_first", 1042 }); 1043 $b->add_run($run2); 1044 $b->execute; 1045 1046Notice that both the first and second runs get equal opportunity. 1047Apache access_log output: 1048 1049 127.0.0.1 - - [20/Sep/2000:18:49:10 -0400] "GET / HTTP/1.0" 200 5565 1050 127.0.0.1 - - [20/Sep/2000:18:49:11 -0400] "GET / HTTP/1.0" 200 5565 1051 127.0.0.1 - - [20/Sep/2000:18:49:11 -0400] "GET / HTTP/1.0" 200 5565 1052 127.0.0.1 - - [20/Sep/2000:18:49:11 -0400] "GET /perl-status HTTP/1.0" 200 848 1053 127.0.0.1 - - [20/Sep/2000:18:49:11 -0400] "GET /perl-status HTTP/1.0" 200 848 1054 127.0.0.1 - - [20/Sep/2000:18:49:11 -0400] "GET /perl-status HTTP/1.0" 200 848 1055 127.0.0.1 - - [20/Sep/2000:18:49:11 -0400] "GET /server-status HTTP/1.0" 200 5294 1056 127.0.0.1 - - [20/Sep/2000:18:49:11 -0400] "GET /server-status HTTP/1.0" 200 5294 1057 127.0.0.1 - - [20/Sep/2000:18:49:11 -0400] "GET /server-status HTTP/1.0" 200 5294 1058 127.0.0.1 - - [20/Sep/2000:18:49:11 -0400] "GET /proxy-status HTTP/1.0" 200 5886 1059 127.0.0.1 - - [20/Sep/2000:18:49:11 -0400] "GET /proxy-status HTTP/1.0" 200 5888 1060 127.0.0.1 - - [20/Sep/2000:18:49:11 -0400] "GET /proxy-status HTTP/1.0" 200 5889 1061 1062Now let's set $b->priority("B<run_priority>"). 1063 1064 $b->priority("run_priority"); 1065 $b->execute; 1066 1067Notice that now ApacheBench completes the entire first run before it starts 1068the second. Here's the Apache access_log output: 1069 1070 127.0.0.1 - - [20/Sep/2000:18:52:47 -0400] "GET / HTTP/1.0" 200 5565 1071 127.0.0.1 - - [20/Sep/2000:18:52:47 -0400] "GET / HTTP/1.0" 200 5565 1072 127.0.0.1 - - [20/Sep/2000:18:52:47 -0400] "GET / HTTP/1.0" 200 5565 1073 127.0.0.1 - - [20/Sep/2000:18:52:47 -0400] "GET /server-status HTTP/1.0" 200 5294 1074 127.0.0.1 - - [20/Sep/2000:18:52:47 -0400] "GET /server-status HTTP/1.0" 200 5294 1075 127.0.0.1 - - [20/Sep/2000:18:52:47 -0400] "GET /server-status HTTP/1.0" 200 5294 1076 127.0.0.1 - - [20/Sep/2000:18:52:47 -0400] "GET /perl-status HTTP/1.0" 200 848 1077 127.0.0.1 - - [20/Sep/2000:18:52:47 -0400] "GET /perl-status HTTP/1.0" 200 848 1078 127.0.0.1 - - [20/Sep/2000:18:52:47 -0400] "GET /perl-status HTTP/1.0" 200 848 1079 127.0.0.1 - - [20/Sep/2000:18:52:47 -0400] "GET /proxy-status HTTP/1.0" 200 5858 1080 127.0.0.1 - - [20/Sep/2000:18:52:47 -0400] "GET /proxy-status HTTP/1.0" 200 5861 1081 127.0.0.1 - - [20/Sep/2000:18:52:47 -0400] "GET /proxy-status HTTP/1.0" 200 5864 1082 1083Let's now set our runs to $run->order("B<depth_first>") instead of 1084"B<breadth_first>". With "B<depth_first>", $b->priority() has no effect, 1085since each run can only use a maximum of one concurrent server 1086(by definition of "B<depth_first>", it can only be sending one request 1087at a time). 1088 1089 $b->run(0)->order("depth_first"); 1090 $b->run(1)->order("depth_first"); 1091 $b->execute; 1092 1093Notice each sequence gets sent in full before it repeats. 1094Apache access_log output: 1095 1096 127.0.0.1 - - [20/Sep/2000:19:02:01 -0400] "GET / HTTP/1.0" 200 5565 1097 127.0.0.1 - - [20/Sep/2000:19:02:01 -0400] "GET /server-status HTTP/1.0" 200 5294 1098 127.0.0.1 - - [20/Sep/2000:19:02:01 -0400] "GET / HTTP/1.0" 200 5565 1099 127.0.0.1 - - [20/Sep/2000:19:02:01 -0400] "GET /server-status HTTP/1.0" 200 5294 1100 127.0.0.1 - - [20/Sep/2000:19:02:01 -0400] "GET / HTTP/1.0" 200 5565 1101 127.0.0.1 - - [20/Sep/2000:19:02:01 -0400] "GET /server-status HTTP/1.0" 200 5294 1102 127.0.0.1 - - [20/Sep/2000:19:02:01 -0400] "GET /perl-status HTTP/1.0" 200 848 1103 127.0.0.1 - - [20/Sep/2000:19:02:01 -0400] "GET /proxy-status HTTP/1.0" 200 5858 1104 127.0.0.1 - - [20/Sep/2000:19:02:01 -0400] "GET /perl-status HTTP/1.0" 200 848 1105 127.0.0.1 - - [20/Sep/2000:19:02:01 -0400] "GET /proxy-status HTTP/1.0" 200 5860 1106 127.0.0.1 - - [20/Sep/2000:19:02:01 -0400] "GET /perl-status HTTP/1.0" 200 848 1107 127.0.0.1 - - [20/Sep/2000:19:02:01 -0400] "GET /proxy-status HTTP/1.0" 200 5860 1108 1109Now let's send the same runs, but with a higher concurrency level. 1110 1111 $b->concurrency(2); 1112 $b->execute; 1113 1114Notice that ApacheBench sends requests from all runs in order to fill up 1115the specified level of concurrent requests. Apache access_log output: 1116 1117 127.0.0.1 - - [20/Sep/2000:19:04:38 -0400] "GET / HTTP/1.0" 200 5565 1118 127.0.0.1 - - [20/Sep/2000:19:04:38 -0400] "GET /perl-status HTTP/1.0" 200 848 1119 127.0.0.1 - - [20/Sep/2000:19:04:38 -0400] "GET /server-status HTTP/1.0" 200 5294 1120 127.0.0.1 - - [20/Sep/2000:19:04:38 -0400] "GET /proxy-status HTTP/1.0" 200 5891 1121 127.0.0.1 - - [20/Sep/2000:19:04:38 -0400] "GET / HTTP/1.0" 200 5565 1122 127.0.0.1 - - [20/Sep/2000:19:04:38 -0400] "GET /perl-status HTTP/1.0" 200 848 1123 127.0.0.1 - - [20/Sep/2000:19:04:38 -0400] "GET /server-status HTTP/1.0" 200 5294 1124 127.0.0.1 - - [20/Sep/2000:19:04:38 -0400] "GET /proxy-status HTTP/1.0" 200 5878 1125 127.0.0.1 - - [20/Sep/2000:19:04:38 -0400] "GET / HTTP/1.0" 200 5565 1126 127.0.0.1 - - [20/Sep/2000:19:04:38 -0400] "GET /perl-status HTTP/1.0" 200 848 1127 127.0.0.1 - - [20/Sep/2000:19:04:38 -0400] "GET /server-status HTTP/1.0" 200 5294 1128 127.0.0.1 - - [20/Sep/2000:19:04:38 -0400] "GET /proxy-status HTTP/1.0" 200 5878 1129 1130Let's take a look at the regression data from that last execute() call. 1131 1132 foreach my $run_no (0..1) { 1133 foreach my $repeat (0..2) { 1134 print "response times (in ms) for run $run_no, iteration ".($repeat+1).":\n "; 1135 print join("\n ", @{$b->run($run_no)->iteration($repeat)->response_times}); 1136 print "\n"; 1137 } 1138 } 1139 1140Perl output: 1141 1142 response times (in ms) for run 0, iteration 1: 1143 69 1144 39 1145 response times (in ms) for run 0, iteration 2: 1146 67 1147 39 1148 response times (in ms) for run 0, iteration 3: 1149 65 1150 41 1151 response times (in ms) for run 1, iteration 1: 1152 67 1153 40 1154 response times (in ms) for run 1, iteration 2: 1155 66 1156 39 1157 response times (in ms) for run 1, iteration 3: 1158 65 1159 39 1160 1161 1162=head1 BUGS 1163 1164Error checking in configuration is good but not perfect. 1165If you do not configure correctly, you may experience a 1166segmentation fault on execute(). 1167 1168The body of any response which comes back larger than the B<buffersize> 1169applicable to it may be truncated to zero length. If you expect to receive 1170responses larger than the default 256K buffersize, make sure to set your 1171$run->buffersize() big enough for the largest page you anticipate receiving. 1172 1173If you are running in perl's taint-checking mode, and you pass tainted data 1174to ApacheBench (e.g. a tainted URL), it will barf. Don't ask me why. 1175 1176HTTP Proxy support needs to be expanded to allow for a username and 1177password. 1178 1179=head1 AUTHORS 1180 1181The ApacheBench Perl API is based on code from 1182Apache 1.3.12 ab (src/support/ab.c), by the Apache group. 1183 1184The ApacheBench Perl API was originally written by Ling Wu <ling@certsite.com> 1185 1186Rewritten and currently maintained by Adi Fairbank <adi@adiraj.org> 1187 1188Recent efforts have been made to incorporate the newest ab code from the Apache 1189Group. As of version 0.62, most features of Apache 1.3.22 ab are supported. 1190The main exception being SSL. 1191 1192Please e-mail Adi with bug reports, or preferably patches. 1193 1194=head1 LICENSE 1195 1196This package is free software and is provided AS IS without express or 1197implied warranty. It may be used, redistributed and/or modified under the 1198terms of the Perl Artistic License 1199(http://www.perl.com/perl/misc/Artistic.html) 1200 1201=cut 1202