#!/usr/bin/perl # This script implements a basic benchmarking and regression testing # utility for Koha use strict; use warnings; BEGIN { # find Koha's Perl modules # test carefully before changing this use FindBin (); eval { require "$FindBin::Bin/kohalib.pl" }; } use Getopt::Long qw( GetOptions ); use HTTPD::Bench::ApacheBench; use LWP::UserAgent; use HTTP::Cookies; use C4::Context; use URI::Escape qw( uri_escape_utf8 ); use Koha::Patrons; my ($help, $steps, $baseurl, $max_tries, $user, $password,$short_print); GetOptions( 'help' => \$help, 'steps:s' => \$steps, 'url:s' => \$baseurl, 'user:s' => \$user, 'password:s' => \$password, 'maxtries:s' => \$max_tries, 'short' => \$short_print, ); my $concurrency = 30; $max_tries=20 unless $max_tries; # if steps not provided, run all tests $steps='0123456789' unless $steps; # if short is set, we will only give number for direct inclusion on the wiki my $short_ms="|-\n|ON\n"; my $short_psec="|-\n|ON\n"; if ($help || !$baseurl || !$user || !$password) { print <new(); my $cookie_jar = HTTP::Cookies->new(); my $cookie; $ua->cookie_jar($cookie_jar); my $resp = $ua->post( "$baseurl"."/svc/authentication" , {userid =>$user, password => $password} ); if( $resp->is_success and $resp->content =~ m|ok| ) { $cookie_jar->extract_cookies( $resp ); $cookie = $cookie_jar->as_string; unless ($short_print) { print "Authentication successful\n"; } } elsif ( $resp->is_success ) { die "Authentication failure: bad login/password"; } else { die "Authentication failure: \n\t" . $resp->status_line; } die "You cannot use the database administrator account to launch this script" unless defined Koha::Patrons->find( { userid => $user } ); # remove some unnecessary garbage from the cookie $cookie =~ s/ path_spec; discard; version=0//; $cookie =~ s/Set-Cookie3: //; # Get some data to work with my $dbh=C4::Context->dbh(); # grab some borrowernumbers my $sth = $dbh->prepare("select max(borrowernumber) from borrowers"); $sth->execute; my ($borrowernumber_max) = $sth->fetchrow; my @borrowers; for (my $i=1;$i<=$max_tries;$i++) { my $rand_borrowernumber = int(rand($borrowernumber_max)+1); push @borrowers,"$baseurl/members/moremember.pl?borrowernumber=$rand_borrowernumber"; } # grab some biblionumbers $sth = $dbh->prepare("select max(biblionumber) from biblio"); $sth->execute; my ($biblionumber_max) = $sth->fetchrow; my @biblios; for (my $i=1;$i<=$max_tries;$i++) { my $rand_biblionumber = int(rand($biblionumber_max)+1); push @biblios,"$baseurl/catalogue/detail.pl?biblionumber=$rand_biblionumber"; } # grab some title and author, for random search $sth = $dbh->prepare ("SELECT title, author FROM biblio LIMIT 10"); $sth->execute; my ($title,$author); my @searchwords; while (($title,$author)=$sth->fetchrow) { push @searchwords,split / /, $author//''; push @searchwords,split / /, $title//''; } $sth = $dbh->prepare("select max(itemnumber) from items"); $sth->execute; # find the biggest itemnumber my ($itemnumber_max) = $sth->fetchrow; $|=1; unless ($short_print) { print "--------------\n"; print "Koha STAFF benchmarking utility\n"; print "--------------\n"; print "Benchmarking with $max_tries occurrences of each operation and $concurrency concurrent sessions \n"; } # # the global benchmark we do at the end... # my $b = HTTPD::Bench::ApacheBench->new; $b->concurrency( $concurrency ); my $ro; # # STEP 1: mainpage : (very) low RDBMS dependency # if ($steps=~ /1/) { my $b0 = HTTPD::Bench::ApacheBench->new; $b0->concurrency( $concurrency ); my @mainpage; unless ($short_print) { print "Step 1: staff interface main page "; } for (my $i=1;$i<=$max_tries;$i++) { push @mainpage,"$baseurl/mainpage.pl"; } my $run0 = HTTPD::Bench::ApacheBench::Run->new ({ urls => \@mainpage, cookies => [$cookie], }); $b0->add_run($run0); $b->add_run($run0); # send HTTP request sequences to server and time responses $ro = $b0->execute; # calculate hits/sec if ($short_print) { $short_ms.= "|".$b0->total_time."\n"; $short_psec.="|".(int((1000*$b0->total_requests/$b0->total_time)*1000)/1000)."\n"; } else { print ("\t".$b0->total_time."ms\t".(int((1000*$b0->total_requests/$b0->total_time)*1000)/1000)." pages/sec\n"); print "ALERT : ".$b0->total_responses_failed." failures\n" if $b0->total_responses_failed; } } else { print "Skipping step 1\n"; } # # STEP 2: biblios # if ($steps=~ /2/) { my $b1 = HTTPD::Bench::ApacheBench->new; $b1->concurrency( $concurrency ); unless ($short_print) { print "Step 2: catalog detail page "; } my $run1 = HTTPD::Bench::ApacheBench::Run->new ({ urls => \@biblios, cookies => [$cookie], }); $b1->add_run($run1); $b->add_run($run1); # send HTTP request sequences to server and time responses $ro = $b1->execute; # calculate hits/sec if ($short_print) { $short_ms.= "|".$b1->total_time."\n"; $short_psec.="|".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)."\n"; } else { print ("\t".$b1->total_time."ms\t".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)." biblios/sec\n"); print "ALERT : ".$b1->total_responses_failed." failures\n" if $b1->total_responses_failed; } } else { print "Skipping step 2\n"; } # # STEP 3: search # if ($steps=~ /3/) { my $b1 = HTTPD::Bench::ApacheBench->new; $b1->concurrency( $concurrency ); unless ($short_print) { print "Step 3: catalogue search "; } my @searches; for (my $i=1;$i<=$max_tries;$i++) { push @searches,"$baseurl/catalogue/search.pl?q=".@searchwords[int(rand(scalar @searchwords))]; } my $run1 = HTTPD::Bench::ApacheBench::Run->new ({ urls => \@searches, cookies => [$cookie], }); $b1->add_run($run1); $b->add_run($run1); # send HTTP request sequences to server and time responses $ro = $b1->execute; # calculate hits/sec if ($short_print) { $short_ms.= "|".$b1->total_time."\n"; $short_psec.="|".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)."\n"; } else { print ("\t".$b1->total_time."ms\t".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)." biblios/sec\n"); print "ALERT : ".$b1->total_responses_failed." failures\n" if $b1->total_responses_failed; } } else { print "Skipping step 3\n"; } # # STEP 4: borrowers # if ($steps=~ /4/) { my $b2 = HTTPD::Bench::ApacheBench->new; $b2->concurrency( $concurrency ); unless ($short_print) { print "Step 4: patron detail page "; } my $run2 = HTTPD::Bench::ApacheBench::Run->new ({ urls => \@borrowers, cookies => [$cookie], }); $b2->add_run($run2); $b->add_run($run2); # send HTTP request sequences to server and time responses $ro = $b2->execute; # calculate hits/sec if ($short_print) { $short_ms.= "|".$b2->total_time."\n"; $short_psec.="|".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)."\n"; } else { print ("\t".$b2->total_time."ms\t".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)." borrowers/sec\n"); } } else { print "Skipping step 4\n"; } # # STEP 5: borrowers search # if ($steps=~ /5/) { my $b2 = HTTPD::Bench::ApacheBench->new; $b2->concurrency( $concurrency ); unless ($short_print) { print "Step 5: patron search page "; } for (my $i=1;$i<=$max_tries;$i++) { # print "$baseurl/members/moremember.pl?borrowernumber=$rand_borrowernumber\n"; push @borrowers,"$baseurl/members/member.pl?member=jean"; } my $run2 = HTTPD::Bench::ApacheBench::Run->new ({ urls => \@borrowers, cookies => [$cookie], }); $b2->add_run($run2); $b->add_run($run2); # send HTTP request sequences to server and time responses $ro = $b2->execute; if ($short_print) { $short_ms.= "|".$b2->total_time."\n"; $short_psec.="|".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)."\n"; } else { print ("\t".$b2->total_time."ms\t".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)." borrowers/sec\n"); } } else { print "Skipping step 5\n"; } # # STEP 6: issue (& then return) books # if ($steps=~ /6/) { my $b3 = HTTPD::Bench::ApacheBench->new; $b3->concurrency( $concurrency ); my $b4 = HTTPD::Bench::ApacheBench->new; $b4->concurrency( $concurrency ); my @issues; my @returns; unless ($short_print) { print "Step 6a circulation (checkouts) "; } $sth = $dbh->prepare("SELECT barcode FROM items WHERE itemnumber=?"); my $sth2 = $dbh->prepare("SELECT borrowernumber FROM borrowers WHERE borrowernumber=?"); for (my $i=1;$i<=$max_tries;$i++) { my $rand_borrowernumber; # check that the borrowernumber exist until ($rand_borrowernumber) { $rand_borrowernumber = int(rand($borrowernumber_max)+1); $sth2->execute($rand_borrowernumber); ($rand_borrowernumber) = $sth2->fetchrow; } # find a barcode & check it exists my $rand_barcode; until ($rand_barcode) { my $rand_itemnumber = int(rand($itemnumber_max)+1); $sth->execute($rand_itemnumber); ($rand_barcode) = uri_escape_utf8($sth->fetchrow()); } push @issues,"$baseurl/circ/circulation.pl?borrowernumber=$rand_borrowernumber&barcode=$rand_barcode&issueconfirmed=1"; push @returns,"$baseurl/circ/returns.pl?barcode=$rand_barcode"; } my $run3 = HTTPD::Bench::ApacheBench::Run->new ({ urls => \@issues, cookies => [$cookie], }); $b3->add_run($run3); $b->add_run($run3); # send HTTP request sequences to server and time responses $ro = $b3->execute; # calculate hits/sec if ($short_print) { $short_ms.= "|".$b3->total_time."\n"; $short_psec.="|".(int((1000*$b3->total_requests/$b3->total_time)*1000)/1000)."\n"; } else { print ("\t".$b3->total_time."ms\t".(int((1000*$b3->total_requests/$b3->total_time)*1000)/1000)." checkouts/sec\n"); } unless ($short_print) { print "Step 6b circulation (checkins) "; } my $run4 = HTTPD::Bench::ApacheBench::Run->new ({ urls => \@returns, cookies => [$cookie], }); $b4->add_run($run4); $b->add_run($run4); # send HTTP request sequences to server and time responses $ro = $b4->execute; # calculate hits/sec if ($short_print) { $short_ms.= "|".$b4->total_time."\n"; $short_psec.="|".(int((1000*$b4->total_requests/$b4->total_time)*1000)/1000)."\n"; } else { print ("\t".$b4->total_time."ms\t".(int((1000*$b4->total_requests/$b4->total_time)*1000)/1000)." checkins/sec\n"); } } else { print "Skipping step 6\n"; } if ($steps=~ /0/) { unless ($short_print) { print "all transactions at once "; } $ro = $b->execute; if ($short_print) { $short_ms.= "|".$b->total_time."\n"; $short_psec.="|".(int((1000*$b->total_requests/$b->total_time)*1000)/1000)."\n"; } else { print ("\t".$b->total_time."ms\t".(int((1000*$b->total_requests/$b->total_time)*1000)/1000)." operations/sec\n"); } } else { print "Skipping 'testing all transactions at once'\n (step 0)"; } if ($short_print) { print $short_ms."\n=====\n".$short_psec."\n"; }