ApacheBench

 view release on metacpan or  search on metacpan

lib/HTTPD/Bench/ApacheBench.pm  view on Meta::CPAN

package HTTPD::Bench::ApacheBench;

use strict;
use vars qw($VERSION @ISA);

use base qw(DynaLoader HTTPD::Bench::ApacheBench::Regression);

use HTTPD::Bench::ApacheBench::Run;
use Scalar::Util qw/blessed/;

$HTTPD::Bench::ApacheBench::VERSION = '0.73';

bootstrap HTTPD::Bench::ApacheBench $VERSION;

##################################################
## the constructor                              ##
##################################################
sub new {
    my ($this, $self) = @_;
    my $class = ref($this) || $this;
    if (ref($self) ne 'HASH') { $self = {} }
    bless $self, $class;
    $self->initialize;
    return $self;
}

##################################################
## initialize defaults                          ##
##################################################
sub initialize {
    my ($self) = @_;
    $self->{runs} = [] if ref $self->{runs} ne 'ARRAY';
    $self->{concurrency} ||= 1;
    $self->{repeat} ||= 1;
    $self->{priority} ||= "equal_opportunity";
    $self->{buffersize} ||= 262144;
    $self->{request_buffersize} ||= 8192;
    $self->{timelimit} = undef if ! defined $self->{timelimit};
    $self->{keepalive} = 0 if ! defined $self->{keepalive};
    $self->{memory} = 1 if ! defined $self->{memory};
}


##################################################
## configure the global parameters              ##
##################################################
sub config {
    my ($self, $opt) = @_;
    foreach (qw(concurrency priority buffersize repeat memory)) {
	$self->{$_} = $opt->{$_} if defined $opt->{$_};
    }
}

sub concurrency {
    my ($self, $arg) = @_;
    $self->{concurrency} = $arg if $arg;
    return $self->{concurrency};
}

sub priority {
    my ($self, $arg) = @_;
    $self->{priority} = $arg if $arg;
    return $self->{priority};
}

sub memory {
    my ($self, $arg) = @_;
    $self->{memory} = $arg if defined $arg;
    return $self->{memory};
}

sub repeat {
    my ($self, $arg) = @_;
    $self->{repeat} = $arg if $arg;
    return $self->{repeat};
}

sub keepalive {
    my ($self, $arg) = @_;
    $self->{keepalive} = $arg if $arg;
    return $self->{keepalive};
}

sub timelimit {
    my ($self, $arg) = @_;
    $self->{timelimit} = $arg if $arg;
    return $self->{timelimit};
}

sub buffersize {
    my ($self, $arg) = @_;
    $self->{buffersize} = $arg if $arg;
    return $self->{buffersize};
}

sub request_buffersize {
    my ($self, $arg) = @_;
    $self->{request_buffersize} = $arg if $arg;
    return $self->{request_buffersize};
}

sub total_requests {
    my ($self) = @_;
    return 0 if ref $self->{runs} ne 'ARRAY';
    my $total = 0;
    foreach my $run (@{$self->{runs}}) {
	my $repeat = $run->repeat ? $run->repeat : $self->{repeat};
	$total += ($#{$run->urls} + 1) * $repeat
	  if ref $run->urls eq 'ARRAY';
    }
    return $total;
}


##################################################
## verify configuration of runs and execute     ##
##################################################
sub execute {
    my ($self) = @_;
    # keep track of temporarily altered run object variables
    my %altered;

    # fail if they have not added any runs
    return undef if ref $self->{runs} ne 'ARRAY';

    # pre execute initialization of each run
    foreach my $run_no (0..$#{$self->{runs}}) {
	my $runobj = $self->{runs}->[$run_no];

	$runobj->ready_to_execute or $runobj->prepare_for_execute or
	  return undef;

	# default to base ApacheBench object variables if not specified in run
	if (! $runobj->repeat) {
	    $runobj->repeat($self->{repeat});
	    $altered{$run_no}->{repeat} = 1;
	}
	if (! defined $runobj->memory) {
	    $runobj->memory($self->{memory});
	    $altered{$run_no}->{memory} = 1;
	}

lib/HTTPD/Bench/ApacheBench.pm  view on Meta::CPAN

ApacheBench sends sequences of HTTP requests to an HTTP server and keeps
track of the time taken to receive a response, the data that was returned,
the size of the data that was returned, and various other bits of information.

Since it is implemented in C, it sends HTTP requests in a tight loop which
can stress your server to 100% capacity, especially if invoked in multiple
concurrent instances.  It gives accurate time measurements down to the
millisecond for each HTTP request-response interval.

Included is a simplified re-implementation of ab using the ApacheBench/Perl
API.  This should help get you started with ApacheBench.

=head1 CONFIGURATION METHODS

You need to tell ApacheBench what the requests will be, in what order to
send them, and how to prioritize sending them.  ApacheBench was designed
to simulate many users logged in simultaneously, each of whom may be doing
many different transactions at once.

=head2 Global configuration methods

Global configuration methods apply to all benchmarking runs associated
with this ApacheBench object.

=over 4

=item $b = HTTPD::Bench::ApacheBench->new()

Constructor.

=item $b->concurrency( $concur_level )

Number of requests to send simultaneously (default: B<1>)

=item $b->priority( $priority )

$priority can be either "B<equal_opportunity>" or "B<run_priority>".

If set to "B<equal_opportunity>", all benchmark runs that are configured
(see below) under this ApacheBench object are given equal access to
the concurrency level.  This means requests are taken from each run
and sent in parallel (the level of parallelism defined by concurrency()).

If set to "B<run_priority>", the benchmark runs that are configured first
get the highest priority.  This means all requests in $b->run(0) will
be sent before any requests in $b->run(1) are sent, provided that
$b->run(0)->order eq "B<breadth_first>" (see below).

See L<"EXAMPLES"> near the bottom of this manual for a tutorial on the
effects of "B<equal_opportunity>" vs. "B<run_priority>".

(default: "B<equal_opportunity>")

=item $b->repeat( $n )

The number of times to repeat the request sequence in each run.
This can be overridden on a per-run basis (see below).

(default: B<1>)

=item $b->keepalive( 0|1 )

Whether or not to use HTTP Keep-Alive feature for requests configured in this
object.  This can be overridden on a per-url/per-run basis (see below).

B<Warning>: If you configure runs which contain requests to more than one
hostname/port, be aware that setting $b->keepalive() may not improve
performance.  See the discussion in the $run->keepalive() section for more
details.

(default: B<0>)

=item $b->timelimit( $sec_to_max_wait )

Set the maximum number of seconds to wait for requests configured in this
object to complete (i.e. receive the full response from the server).
B<Warning>: once the specified number of seconds have elapsed, ApacheBench
will read one last chunk of data from each open socket, and exit.  This could
result in partially received responses, which will cause broken sockets for
the server.

Per-url/per-run time limits can also be specified using the $run->timelimits()
method, but it does B<not> override this global setting.  If $b->timelimit()
is set, ApacheBench will exit after $sec_to_max_wait (or slightly over, due to
final connection reads and building regression data), regardless of any other
settings.

(default: B<0>, which means wait an unlimited amount of time)

=item $b->buffersize( $bufsz )

The size of the buffer in which to store HTTP response bodies, in bytes.
If an HTTP response is received with a size larger than this limit,
the content is truncated at length $bufsz, and a warning is issued.
This method has no effect if $b->memory() < 3.
This can be overridden on a per-run basis (see below).

(default: B<262144> or 256K)

=item $b->request_buffersize( $req_bufsz )

The size of the buffer in which to store HTTP requests, in bytes.  If you
configure an HTTP request that is larger than this limit, unpredictable
things will happen. (most likely a segmentation fault)

(default: B<8192> or 8K)

=item $b->memory( $memlvl )

The memory level.  Controls how much data ApacheBench will remember and
return in the regression object.  This can be overridden on a per-run basis
(see below).

(default: B<1>)

Key:
 $memlvl => Description

  0  =>  Remember nothing. (actually still keeps global
         regression data: total_time, bytes_received,
         and warnings)

  1  =>  Remember connect/response times and minimal
         summary information about size of requests and
         responses.

  2  =>  Remember connect/response times and all
         information about size of requests and responses.

lib/HTTPD/Bench/ApacheBench.pm  view on Meta::CPAN


    e.g. the following postdata coderef would produce a GET request with a
         random 1-5 sec delay:

    sub($) {
        sleep(int(rand(5)) + 1);
        return undef;
    }
    

=item $run->head_requests( \@head_reqs )

Send HTTP HEAD requests for the specified requests in this run.  The
length of @head_reqs should equal the length of @url_list, and it is
interpreted as an array of booleans.  Any true value in @head_reqs will
result in a HEAD request being sent for the corresponding URL (unless the
corresponding postdata() value is defined, in which case a POST will be sent).

You can configure a run composed of any combination of the three HTTP
request types (GET, HEAD, and POST), but note that an individual URL with a
defined postdata() value will cause a POST request regardless of whether
head_requests() is set for that URL.  The following precedence table
illustrates which type of request will be sent for URL $url_no in the sequence.

  defined $run->postdata->[$url_no] && ! ref($run->postdata->[$url_no])
                                     ?  ==> POST request
  defined $run->postdata->[$url_no] && ref($run->postdata->[$url_no]) eq 'CODE'
           && defined $run->postdata->[$url_no]->( $preceding_response )
                                     ?  ==> POST request
  defined $run->postdata->[$url_no] && ref($run->postdata->[$url_no]) eq 'CODE'
           && ! defined $run->postdata->[$url_no]->( $preceding_response )
                                     ?  ==> GET request
  $run->head_requests->[$url_no]     ?  ==> HEAD request
  else                               :  ==> GET request

=item $run->content_types( \@ctypes )

Set the Content-type: header for each POST request in this run.  Default
is "application/x-www-form-urlencoded" which will be used if an undef
value is encountered in @ctypes.  Length of @ctypes should equal the
length of @postdata.  Only sends the Content-type: header for POST requests:
a defined value in @ctypes with an undef in the corresponding @postdata will
result in no Content-type: header being sent.

=item $run->request_headers( \@req_headers )

Set arbitrary HTTP request headers for each request in this run, which will
be inserted after all normal headers.  Multiple extra headers for a single
url should be separated with "\r\n".  An undef value in @req_headers results
in no extra HTTP request headers being sent for the corresponding url.
If this option is omitted, no extra HTTP request headers will be sent in
any of the requests for this run.  Length of @req_headers should equal the
length of @url_list.

The following example for a @url_list of length 4 produces two requests with
no extra headers, one with 1 extra header, and one with 2 extra headers.

  $run->request_headers([ undef, undef, "Extra-Header: bread",
                          "Extra-Header1: butter\r\nExtra-Header2: toaster" ])

=item $run->keepalive( \@keepalives )

Use HTTP Keep-Alive feature for the specified requests in this run.  The
length of @keepalives should equal the length of @url_list, and it is
interpreted as an array of booleans, with undef indicating to use the
object default set by $b->keepalive().  Any true value in @keepalives will
result in a Keep-Alive HTTP request being sent for the corresponding URL.

To achieve full performance benefits from this feature, you need to be sure
your Keep-Alive requests are consecutive.  If a non-Keep-Alive request or
a request for a different hostname or port immediately follows a Keep-Alive
request B<in the connection slot>, I<the connection will be closed> and a
new connection will be opened.

Further, keep in mind that for $b->concurrency() > 1, there are many
connection slots open and even though requests in @url_list will be sent
in order, there is no guarantee they will all use the same connection slot.
The HTTP Keep-Alive feature only yields performance benefits when consecutive
Keep-Alive requests use the same connection slot.  Otherwise ApacheBench has
to close and re-open connections, resulting in the same performance as not
using keepalive() at all.

To guarantee consecutive Keep-Alive requests with $b->concurrency() > 1,
I recommend you either declare I<all> URLs in all runs as keepalive()
(this can be done by setting $b->keepalive( 1 ) and not overriding it by
calling keepalive() for any runs), or set $run->order( "depth_first" ) and
$b->priority( "run_priority" ).  This is the only combination of configuration
options that guarantees consecutive, same-slot Keep-Alive requests
regardless of your concurrency setting.

For $b->concurrency() == 1, things are simpler.  At any given time, there
is only one connection slot open, so just make sure your keepalive URLs are
consecutive within each run (if in "run_priority" mode), or that
equal-numbered repetitions of URLs in all runs are keepalive (if in
"equal_opportunity" mode), and be sure that all requests are to the
same hostname/port.

=item $run->timelimits( \@timelimits )

Set the maximum number of seconds to wait for requests in this
run to complete (i.e. receive the full response from the server).  The
length of @timelimits should equal the length of @url_list, and it is
interpreted as an array of double precision floating point numbers
(representing the number of seconds to wait for a response).  An undef or 0
represents waiting an indefinite amount of time for that particular response.
If this option is not configured, there will be no time limit on any responses.

B<Warning>: once the specified number of seconds have elapsed on the specified
URL, ApacheBench will close the connection immediately.  This can cause strange
results in the regression data for this request.  It could also result in
partially received responses, which will cause broken sockets for the server.

=item $run->order( $order )

Either "B<depth_first>" or "B<breadth_first>"

"B<breadth_first>" mode sends $n of the first request in the
@url_list, then $n of the second request in the @urls_list,
then $n of the third... and so on.  (e.g. If $n == 3 and @url_list
contains two requests, then ApacheBench would send the first
request 3 times, and then the second request 3 times.)

"B<depth_first>" mode ensures that HTTP requests in the sequence are
sent in order, completing a full sequence before starting again for the
next repeat() iteration.  (e.g. If $n == 3 and @url_list contains two
requests, then ApacheBench would send the @url_list sequence in order,
then send it again, and then again.  A total of six requests would be sent.)

See L<"EXAMPLES"> near the bottom of this manual for a tutorial on the
effects of "B<breadth_first>" vs. "B<depth_first>".

(default: "B<breadth_first>")

B<Note:> if $run->repeat() == 1, or the length of $run->urls() is 1, then the
B<order> option has no effect

=item $run->buffersize( $bufsz )

The size of the buffer in which to store HTTP response bodies.
If an HTTP response is received with a size larger than this limit,
the content is truncated at length $bufsz, and a warning is issued.
This method has no effect if $run->memory() < 3.

(default: B<262144> or 256K, or whatever is specified in global configuration)

=item $run->memory( $memlvl )

The memory level.  Controls how much data ApacheBench will remember and
return in the regression object for this run.  See global configuration
method of same name for detailed description.

(default: B<1>, or whatever is specified in global configuration)

=back



( run in 1.477 second using v1.01-cache-2.11-cpan-df04353d9ac )