App-livehttperf

 view release on metacpan or  search on metacpan

lib/App/livehttperf.pm  view on Meta::CPAN

        max_redirect => 0,
        timeout => $OPTS{timeout},
        keep_alive => 0,
    );

    if ( $OPTS{concurrency_max} && $OPTS{concurrency_step} ) {
        push @concurrency, 1
            unless $OPTS{concurrency_step} == 1;

        for ( my $c = $OPTS{concurrency_step}; $c <= $OPTS{concurrency_max}; $c += $OPTS{concurrency_step} ) {
            push @concurrency, $c;
        }
        push @concurrency, $OPTS{concurrency_max}
            unless $concurrency[-1] == $OPTS{concurrency_max};
    } else {
        push @concurrency, sort { $a <=> $b } @{ $OPTS{concurrency} };
    }

    if ( my $xlsx_file = $OPTS{output} ) {

        require Excel::Writer::XLSX;

        $xls = Excel::Writer::XLSX->new( $xlsx_file );
        $xls->set_optimization();
        $xls->set_properties(
            title => 'Performance tests',
            comments => "Generated by App::livehttperf/$App::livehttperf::VERSION",
        );
        $bold = $xls->add_format();
        $bold->set_bold();

        $xls_summary = $xls->add_worksheet('Summary');
        $xls_urls = $xls->add_worksheet('URLs');
    }

}

sub parse_livehttp_log {
    local $/ = "----------------------------------------------------------\r\n";

    open(my $ifh, "<$OPTS{input}") or die "Cannot open $OPTS{input}: $!\n";
    while(my $rrb = <$ifh>) { # Request-Response block
        trim($rrb);

        my ($url, $req, $res, $req_bytes, $res_bytes);
        my @fh = split(/^/, $rrb);
        RRB: for(my $i = 0; $i < @fh; $i++) {
            my $l = $fh[$i]; # single line
            unless ( defined $url ) {
                trim($l);
                $url = $l;
                $i++;
                next;
            }

            # request
            if ( ! defined $req && $l =~ /^[A-Z]+ /) {
                my $req_hdrs = $l;
                my $cl;
                REQ: while( defined( $l = $fh[++$i] ) ) {
                    if ( ! $OPTS{reuse_cookies} && $l =~ /^Cookie/i ) {
                        next REQ;
                    }
                    if ( $l =~ /^HTTP\// ) { # reached response block
                        $i--;
                        last REQ;
                    }
                    if ( $l =~ /^Content-Length:[ \t]+(\d+)/i ) {
                        $cl = int($1);
                    }
                    $req_hdrs .= $l;
                }
                $req_hdrs =~ s/\r?\n\z//;
                my $post_data;
                if ( $cl ) { # post data requires Content-Length
                    $post_data = substr($req_hdrs, -1 * $cl);
                    $req_hdrs = substr($req_hdrs, 0, -1 * $cl);
                }
                $req = HTTP::Request->parse($req_hdrs);
                if ( defined $post_data ) {
                    unless ( length($post_data) == $req->header('Content-Length')) {
                        die "Content-Length header doesn't match the length of post data:\n$rrb\n$post_data\n",
                    };
                    $req->content( $post_data );
                }

                $req->uri( $url );
                if ( $OPTS{hostname} ) {
                    if ( $req->header('Host') ) {
                        $req->header( Host => $OPTS{hostname} );
                    }
                    my $new_host = $req->uri;
                    $new_host->host( $OPTS{hostname} );
                    $req->uri( $new_host );
                }
                next RRB;
            # response
            } elsif ( defined $req && $l =~ /^HTTP/ ) {
                $l =~ s/\r?\n\z//;
                # status line is parsed up to \n by HTTP::Response->parse()
                my $res_hdrs = "$l\n";
                RES: while( $l = $fh[++$i] ) {
                    last RES if $l =~ /^\-{58}/;
                    unless ( $OPTS{reuse_cookies} ) {
                        next if $l =~ /^Set-Cookie/i;
                    }
                    $res_hdrs .= $l;
                }
                $res = HTTP::Response->parse($res_hdrs);
                unless ( $ua_opts{keep_alive} ) {
                    if ( my $ka = $res->header('Keep-Alive') ) {
                        my ($max) = $ka =~ /max=(\d+)/;
                        $ua_opts{keep_alive} = $max || 100;
                    }
                }
                last RRB;
            }
        }

        if ( $req ) {
            if ( $OPTS{use_delay} ) {
                if ( @recs > 0 ) {
                    my $prev_date = $recs[-1]->{res}->headers->date;
                    my $cur_date = $res->headers->date;
                    my $delay = $cur_date - $prev_date;
                    if ( $delay > 0 ) {
                        my $delay_sec = $OPTS{max_delay} && $delay > $OPTS{max_delay} ?
                                $OPTS{max_delay} : $delay;
                        $total_delays += $delay_sec;

                        push @recs, $delay_sec;
                    }
                }
            }

            push @recs, {
                req => $req,
                res => $res,
                req_bytes => length $req->as_string,
                res_bytes => 0,
            };

            $total_urls++;

        };
    }
}

sub run_tests {
    $test_started = [ gettimeofday ];

    $|=1;

    for my $concurrency ( @concurrency ) {
        LOG "\nRunning with concurrency of $concurrency" if INFO;
        my $pm = Parallel::ForkManager->new( $concurrency );

        $stats{$concurrency} = {
            reqs => Statistics::Descriptive::Full->new(),
            recs => {},
            counts => {
                successful_requests => 0,
                failed_requests => 0,
                bytes_sent => 0,
                bytes_recv => 0,

lib/App/livehttperf.pm  view on Meta::CPAN

        }
        $xls_s_row++;

        print "Data transfers:\n";
        print $t->render, "\n";
        print "\n";
    }

    {
        my @columns = ('Concurrency', 'URL', 'Min', 'Max', 'Avg', 'StdDev', 'Median', 'Errors');

        $xls_urls->write_row($xls_u_row++, 0, xlsx_row(@columns), $bold) if $xls;

        my $t = Text::TabularDisplay->new(@columns);

        for (my $rec_no = 1; $rec_no <= @recs; $rec_no++) {
            next unless ref $recs[$rec_no-1];
            for my $concurrency ( @concurrency ) {
                my $rec_stats = $stats{$concurrency}->{recs};
                my $rec_errors = $stats{$concurrency}->{errors}->{recs};

                my $url = $recs[$rec_no-1]->{req}->uri;
                my @row = (
                    $concurrency,
                    $url,
                    ( map { sprintf("%.6f", $rec_stats->{$rec_no}->$_() ) } qw( min max mean standard_deviation median ) ),
                    $rec_errors->{$rec_no} || 0
                );
                $t->add( @row );
                $xls_urls->write_row($xls_u_row++, 0, xlsx_row(@row)) if $xls;
            }
        }

        print "URLs:\n";
        print $t->render, "\n";
        print "\n";
    }

    $xls->close if $xls;
}

sub print_usage {
    print <<'EOH';
Usage: livehttperf [OPTIONS]

Input:
  -i, --input=file      Input file with recoreded session from LiveHTTP headers
                        Firefox extension.
                        Default: "-" (STDIN)

  -nd, --no_delay       Send requests one after another without detected delays.
                        Default: use delay

  -md, --max_delay=NUM  If using delay, wait for no more then NUM seconds
                        Default: none

  -h, --hostname=STRING Override hostname in requests and set Host header to
                        STRING.
                        Default: no change

  -rc, --reuse_cookies  Use Cookie/Set-Cookie headers from recorded session.
                        Default: do not reuse

Sessions:
  -n, --repeat=NUM      Repeat recorded session NUM times.
                        Default: 10

  -t, --timeout=NUM     Connection timeout.
                        Default: 10

  -m, --match=STRING    In addition to comparing HTTP response status line,
                        use specified STRING header to confirm successful
                        request. If provided multiple times all headers have
                        to match.
                        Default: none

  -c, --concurrency=NUM Run NUM concurrent connections. Can be provided
                        multiple times.
                        Default: 1

  -cs, --concurrency_step=NUM
  -cm, --concurrency_max=NUM
                        Alternatively specify maximum concurrency and
                        incremented by provided step value.
                        Default max: none
                        Default step: 5

Display and results:
  -o, --output=file     Save results in Excel 2007 (XLSX) format.
                        Default: none

  -v, --verbose         Repeat to increase verbosity level.
                        Available levels: WARN, INFO, DEBUG, TRACE.
                        Default: WARN

  -q, --quiet           Display only results.
                        Default: none


Examples:

  livehttperf -md 1 -cm 20 -o test.xlsx < session.txt
        Read session from session.txt, set maximum delay between requests to
        1 second and run with concurrency: 1, 5, 10, 15 and 20. Save results
        in test.xlsx

  livehttperf -nd -m Content-Length -t 5 -c 20 -c 50 < session.txt
        Read session from session.txt, require matching Content-Length header
        and run without any delay with concurrency: 20 and 50. Save results
        in test.xlsx

EOH
}

sub run {
    # configure
    configure();

    # parse input
    parse_livehttp_log();



( run in 2.079 seconds using v1.01-cache-2.11-cpan-ecdf5575e8d )