2 # This script implements a basic benchmarking and regression testing
8 # find Koha's Perl modules
9 # test carefully before changing this
11 eval { require "$FindBin::Bin/kohalib.pl" };
15 use HTTPD::Bench::ApacheBench;
23 my ($help, $steps, $baseurl, $max_tries, $user, $password,$short_print);
29 'password:s' => \$password,
30 'maxtries:s' => \$max_tries,
31 'short' => \$short_print,
34 $max_tries=20 unless $max_tries;
35 # if steps not provided, run all tests
36 $steps='0123456789' unless $steps;
38 # if short is set, we will only give number for direct inclusion on the wiki
39 my $short_ms="|-\n|ON\n";
40 my $short_psec="|-\n|ON\n";
42 if ($help || !$baseurl || !$user || !$password) {
44 This script runs a benchmark of the staff interface. It benchmarks 6 different pages:
45 \t1- the staff main page
46 \t2- the catalog detail page, with a random biblionumber
47 \t3- the catalog search page, using a term retrieved from one of the 10 first titles/authors in the database
48 \t4- the patron detail page, with a random borrowernumber
49 \t5- the patron search page, searching for "Jean"
50 \t6- the circulation itself, doing check-out and check-in of random items to random patrons
52 \t0 all those tests at once
55 \tsteps = which steps you want to run.
56 \t\tDon't use it if you want to run all tests. enter 125 if you want to run tests 1, 2 and 5
57 \t\tThe "all those tests at once" is numbered 0,and will run all tests previously run.
58 \t\tIf you run only one step, it's useless to run the 0, you'll get the same result.
59 \turl = the URL or your staff interface
61 \tpassword = Koha password
62 \tmaxtries = how many tries you want to do. Defaulted to 20
64 SAMPLE : ./benchmark_staff.pl --url=http://yourstaff.org/cgi-bin/koha/ --user=test --password=test --steps=12
72 # Authenticate via our handy dandy RESTful services
74 my $ua = LWP::UserAgent->new();
75 my $cookie_jar = HTTP::Cookies->new();
77 $ua->cookie_jar($cookie_jar);
78 my $resp = $ua->post( "$baseurl"."/svc/authentication" , {userid =>$user, password => $password} );
79 if( $resp->is_success and $resp->content =~ m|<status>ok</status>| ) {
80 $cookie_jar->extract_cookies( $resp );
81 $cookie = $cookie_jar->as_string;
82 unless ($short_print) {
83 print "Authentication successful\n";
85 } elsif ( $resp->is_success ) {
86 die "Authentication failure: bad login/password";
88 die "Authentication failure: \n\t" . $resp->status_line;
91 die "You cannot use the database administrator account to launch this script"
92 unless defined Koha::Patrons->find( { userid => $user } );
94 # remove some unnecessary garbage from the cookie
95 $cookie =~ s/ path_spec; discard; version=0//;
96 $cookie =~ s/Set-Cookie3: //;
98 # Get some data to work with
99 my $dbh=C4::Context->dbh();
100 # grab some borrowernumbers
101 my $sth = $dbh->prepare("select max(borrowernumber) from borrowers");
103 my ($borrowernumber_max) = $sth->fetchrow;
105 for (my $i=1;$i<=$max_tries;$i++) {
106 my $rand_borrowernumber = int(rand($borrowernumber_max)+1);
107 push @borrowers,"$baseurl/members/moremember.pl?borrowernumber=$rand_borrowernumber";
110 # grab some biblionumbers
111 $sth = $dbh->prepare("select max(biblionumber) from biblio");
113 my ($biblionumber_max) = $sth->fetchrow;
115 for (my $i=1;$i<=$max_tries;$i++) {
116 my $rand_biblionumber = int(rand($biblionumber_max)+1);
117 push @biblios,"$baseurl/catalogue/detail.pl?biblionumber=$rand_biblionumber";
120 # grab some title and author, for random search
121 $sth = $dbh->prepare ("SELECT title, author FROM biblio LIMIT 10");
125 while (($title,$author)=$sth->fetchrow) {
126 push @searchwords,split / /, $author//'';
127 push @searchwords,split / /, $title//'';
130 $sth = $dbh->prepare("select max(itemnumber) from items");
132 # find the biggest itemnumber
133 my ($itemnumber_max) = $sth->fetchrow;
136 unless ($short_print) {
137 print "--------------\n";
138 print "Koha STAFF benchmarking utility\n";
139 print "--------------\n";
140 print "Benchmarking with $max_tries occurrences of each operation and $concurrency concurrent sessions \n";
143 # the global benchmark we do at the end...
145 my $b = HTTPD::Bench::ApacheBench->new;
146 $b->concurrency( $concurrency );
149 # STEP 1: mainpage : (very) low RDBMS dependency
152 my $b0 = HTTPD::Bench::ApacheBench->new;
153 $b0->concurrency( $concurrency ); my @mainpage;
154 unless ($short_print) {
155 print "Step 1: staff interface main page ";
157 for (my $i=1;$i<=$max_tries;$i++) {
158 push @mainpage,"$baseurl/mainpage.pl";
160 my $run0 = HTTPD::Bench::ApacheBench::Run->new
161 ({ urls => \@mainpage,
162 cookies => [$cookie],
167 # send HTTP request sequences to server and time responses
171 $short_ms.= "|".$b0->total_time."\n";
172 $short_psec.="|".(int((1000*$b0->total_requests/$b0->total_time)*1000)/1000)."\n";
174 print ("\t".$b0->total_time."ms\t".(int((1000*$b0->total_requests/$b0->total_time)*1000)/1000)." pages/sec\n");
175 print "ALERT : ".$b0->total_responses_failed." failures\n" if $b0->total_responses_failed;
178 print "Skipping step 1\n";
185 my $b1 = HTTPD::Bench::ApacheBench->new;
186 $b1->concurrency( $concurrency );
188 unless ($short_print) {
189 print "Step 2: catalog detail page ";
191 my $run1 = HTTPD::Bench::ApacheBench::Run->new
192 ({ urls => \@biblios,
193 cookies => [$cookie],
198 # send HTTP request sequences to server and time responses
202 $short_ms.= "|".$b1->total_time."\n";
203 $short_psec.="|".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)."\n";
205 print ("\t".$b1->total_time."ms\t".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)." biblios/sec\n");
206 print "ALERT : ".$b1->total_responses_failed." failures\n" if $b1->total_responses_failed;
209 print "Skipping step 2\n";
215 my $b1 = HTTPD::Bench::ApacheBench->new;
216 $b1->concurrency( $concurrency );
217 unless ($short_print) {
218 print "Step 3: catalogue search ";
221 for (my $i=1;$i<=$max_tries;$i++) {
222 push @searches,"$baseurl/catalogue/search.pl?q=".@searchwords[int(rand(scalar @searchwords))];
224 my $run1 = HTTPD::Bench::ApacheBench::Run->new
225 ({ urls => \@searches,
226 cookies => [$cookie],
231 # send HTTP request sequences to server and time responses
235 $short_ms.= "|".$b1->total_time."\n";
236 $short_psec.="|".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)."\n";
238 print ("\t".$b1->total_time."ms\t".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)." biblios/sec\n");
239 print "ALERT : ".$b1->total_responses_failed." failures\n" if $b1->total_responses_failed;
242 print "Skipping step 3\n";
248 my $b2 = HTTPD::Bench::ApacheBench->new;
249 $b2->concurrency( $concurrency );
250 unless ($short_print) {
251 print "Step 4: patron detail page ";
253 my $run2 = HTTPD::Bench::ApacheBench::Run->new
254 ({ urls => \@borrowers,
255 cookies => [$cookie],
260 # send HTTP request sequences to server and time responses
264 $short_ms.= "|".$b2->total_time."\n";
265 $short_psec.="|".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)."\n";
267 print ("\t".$b2->total_time."ms\t".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)." borrowers/sec\n");
270 print "Skipping step 4\n";
274 # STEP 5: borrowers search
277 my $b2 = HTTPD::Bench::ApacheBench->new;
278 $b2->concurrency( $concurrency );
279 unless ($short_print) {
280 print "Step 5: patron search page ";
282 for (my $i=1;$i<=$max_tries;$i++) {
283 # print "$baseurl/members/moremember.pl?borrowernumber=$rand_borrowernumber\n";
284 push @borrowers,"$baseurl/members/member.pl?member=jean";
286 my $run2 = HTTPD::Bench::ApacheBench::Run->new
287 ({ urls => \@borrowers,
288 cookies => [$cookie],
293 # send HTTP request sequences to server and time responses
296 $short_ms.= "|".$b2->total_time."\n";
297 $short_psec.="|".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)."\n";
299 print ("\t".$b2->total_time."ms\t".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)." borrowers/sec\n");
302 print "Skipping step 5\n";
306 # STEP 6: issue (& then return) books
309 my $b3 = HTTPD::Bench::ApacheBench->new;
310 $b3->concurrency( $concurrency );
311 my $b4 = HTTPD::Bench::ApacheBench->new;
312 $b4->concurrency( $concurrency );
316 unless ($short_print) {
317 print "Step 6a circulation (checkouts) ";
319 $sth = $dbh->prepare("SELECT barcode FROM items WHERE itemnumber=?");
320 my $sth2 = $dbh->prepare("SELECT borrowernumber FROM borrowers WHERE borrowernumber=?");
321 for (my $i=1;$i<=$max_tries;$i++) {
322 my $rand_borrowernumber;
323 # check that the borrowernumber exist
324 until ($rand_borrowernumber) {
325 $rand_borrowernumber = int(rand($borrowernumber_max)+1);
326 $sth2->execute($rand_borrowernumber);
327 ($rand_borrowernumber) = $sth2->fetchrow;
329 # find a barcode & check it exists
331 until ($rand_barcode) {
332 my $rand_itemnumber = int(rand($itemnumber_max)+1);
333 $sth->execute($rand_itemnumber);
334 ($rand_barcode) = uri_escape_utf8($sth->fetchrow());
336 push @issues,"$baseurl/circ/circulation.pl?borrowernumber=$rand_borrowernumber&barcode=$rand_barcode&issueconfirmed=1";
337 push @returns,"$baseurl/circ/returns.pl?barcode=$rand_barcode";
339 my $run3 = HTTPD::Bench::ApacheBench::Run->new
341 cookies => [$cookie],
346 # send HTTP request sequences to server and time responses
350 $short_ms.= "|".$b3->total_time."\n";
351 $short_psec.="|".(int((1000*$b3->total_requests/$b3->total_time)*1000)/1000)."\n";
353 print ("\t".$b3->total_time."ms\t".(int((1000*$b3->total_requests/$b3->total_time)*1000)/1000)." checkouts/sec\n");
355 unless ($short_print) {
356 print "Step 6b circulation (checkins) ";
358 my $run4 = HTTPD::Bench::ApacheBench::Run->new
359 ({ urls => \@returns,
360 cookies => [$cookie],
365 # send HTTP request sequences to server and time responses
369 $short_ms.= "|".$b4->total_time."\n";
370 $short_psec.="|".(int((1000*$b4->total_requests/$b4->total_time)*1000)/1000)."\n";
372 print ("\t".$b4->total_time."ms\t".(int((1000*$b4->total_requests/$b4->total_time)*1000)/1000)." checkins/sec\n");
375 print "Skipping step 6\n";
379 unless ($short_print) {
380 print "all transactions at once ";
384 $short_ms.= "|".$b->total_time."\n";
385 $short_psec.="|".(int((1000*$b->total_requests/$b->total_time)*1000)/1000)."\n";
387 print ("\t".$b->total_time."ms\t".(int((1000*$b->total_requests/$b->total_time)*1000)/1000)." operations/sec\n");
390 print "Skipping 'testing all transactions at once'\n (step 0)";
394 print $short_ms."\n=====\n".$short_psec."\n";