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" };
14 use Getopt::Long qw( GetOptions );
15 use HTTPD::Bench::ApacheBench;
19 use URI::Escape qw( uri_escape_utf8 );
22 my ($help, $steps, $baseurl, $max_tries, $user, $password,$short_print);
28 'password:s' => \$password,
29 'maxtries:s' => \$max_tries,
30 'short' => \$short_print,
33 $max_tries=20 unless $max_tries;
34 # if steps not provided, run all tests
35 $steps='0123456789' unless $steps;
37 # if short is set, we will only give number for direct inclusion on the wiki
38 my $short_ms="|-\n|ON\n";
39 my $short_psec="|-\n|ON\n";
41 if ($help || !$baseurl || !$user || !$password) {
43 This script runs a benchmark of the staff interface. It benchmarks 6 different pages:
44 \t1- the staff main page
45 \t2- the catalog detail page, with a random biblionumber
46 \t3- the catalog search page, using a term retrieved from one of the 10 first titles/authors in the database
47 \t4- the patron detail page, with a random borrowernumber
48 \t5- the patron search page, searching for "Jean"
49 \t6- the circulation itself, doing check-out and check-in of random items to random patrons
51 \t0 all those tests at once
54 \tsteps = which steps you want to run.
55 \t\tDon't use it if you want to run all tests. enter 125 if you want to run tests 1, 2 and 5
56 \t\tThe "all those tests at once" is numbered 0,and will run all tests previously run.
57 \t\tIf you run only one step, it's useless to run the 0, you'll get the same result.
58 \turl = the URL or your staff interface
60 \tpassword = Koha password
61 \tmaxtries = how many tries you want to do. Defaulted to 20
63 SAMPLE : ./benchmark_staff.pl --url=http://yourstaff.org/cgi-bin/koha/ --user=test --password=test --steps=12
71 # Authenticate via our handy dandy RESTful services
73 my $ua = LWP::UserAgent->new();
74 my $cookie_jar = HTTP::Cookies->new();
76 $ua->cookie_jar($cookie_jar);
77 my $resp = $ua->post( "$baseurl"."/svc/authentication" , {userid =>$user, password => $password} );
78 if( $resp->is_success and $resp->content =~ m|<status>ok</status>| ) {
79 $cookie_jar->extract_cookies( $resp );
80 $cookie = $cookie_jar->as_string;
81 unless ($short_print) {
82 print "Authentication successful\n";
84 } elsif ( $resp->is_success ) {
85 die "Authentication failure: bad login/password";
87 die "Authentication failure: \n\t" . $resp->status_line;
90 die "You cannot use the database administrator account to launch this script"
91 unless defined Koha::Patrons->find( { userid => $user } );
93 # remove some unnecessary garbage from the cookie
94 $cookie =~ s/ path_spec; discard; version=0//;
95 $cookie =~ s/Set-Cookie3: //;
97 # Get some data to work with
98 my $dbh=C4::Context->dbh();
99 # grab some borrowernumbers
100 my $sth = $dbh->prepare("select max(borrowernumber) from borrowers");
102 my ($borrowernumber_max) = $sth->fetchrow;
104 for (my $i=1;$i<=$max_tries;$i++) {
105 my $rand_borrowernumber = int(rand($borrowernumber_max)+1);
106 push @borrowers,"$baseurl/members/moremember.pl?borrowernumber=$rand_borrowernumber";
109 # grab some biblionumbers
110 $sth = $dbh->prepare("select max(biblionumber) from biblio");
112 my ($biblionumber_max) = $sth->fetchrow;
114 for (my $i=1;$i<=$max_tries;$i++) {
115 my $rand_biblionumber = int(rand($biblionumber_max)+1);
116 push @biblios,"$baseurl/catalogue/detail.pl?biblionumber=$rand_biblionumber";
119 # grab some title and author, for random search
120 $sth = $dbh->prepare ("SELECT title, author FROM biblio LIMIT 10");
124 while (($title,$author)=$sth->fetchrow) {
125 push @searchwords,split / /, $author//'';
126 push @searchwords,split / /, $title//'';
129 $sth = $dbh->prepare("select max(itemnumber) from items");
131 # find the biggest itemnumber
132 my ($itemnumber_max) = $sth->fetchrow;
135 unless ($short_print) {
136 print "--------------\n";
137 print "Koha STAFF benchmarking utility\n";
138 print "--------------\n";
139 print "Benchmarking with $max_tries occurrences of each operation and $concurrency concurrent sessions \n";
142 # the global benchmark we do at the end...
144 my $b = HTTPD::Bench::ApacheBench->new;
145 $b->concurrency( $concurrency );
148 # STEP 1: mainpage : (very) low RDBMS dependency
151 my $b0 = HTTPD::Bench::ApacheBench->new;
152 $b0->concurrency( $concurrency ); my @mainpage;
153 unless ($short_print) {
154 print "Step 1: staff interface main page ";
156 for (my $i=1;$i<=$max_tries;$i++) {
157 push @mainpage,"$baseurl/mainpage.pl";
159 my $run0 = HTTPD::Bench::ApacheBench::Run->new
160 ({ urls => \@mainpage,
161 cookies => [$cookie],
166 # send HTTP request sequences to server and time responses
170 $short_ms.= "|".$b0->total_time."\n";
171 $short_psec.="|".(int((1000*$b0->total_requests/$b0->total_time)*1000)/1000)."\n";
173 print ("\t".$b0->total_time."ms\t".(int((1000*$b0->total_requests/$b0->total_time)*1000)/1000)." pages/sec\n");
174 print "ALERT : ".$b0->total_responses_failed." failures\n" if $b0->total_responses_failed;
177 print "Skipping step 1\n";
184 my $b1 = HTTPD::Bench::ApacheBench->new;
185 $b1->concurrency( $concurrency );
187 unless ($short_print) {
188 print "Step 2: catalog detail page ";
190 my $run1 = HTTPD::Bench::ApacheBench::Run->new
191 ({ urls => \@biblios,
192 cookies => [$cookie],
197 # send HTTP request sequences to server and time responses
201 $short_ms.= "|".$b1->total_time."\n";
202 $short_psec.="|".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)."\n";
204 print ("\t".$b1->total_time."ms\t".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)." biblios/sec\n");
205 print "ALERT : ".$b1->total_responses_failed." failures\n" if $b1->total_responses_failed;
208 print "Skipping step 2\n";
214 my $b1 = HTTPD::Bench::ApacheBench->new;
215 $b1->concurrency( $concurrency );
216 unless ($short_print) {
217 print "Step 3: catalogue search ";
220 for (my $i=1;$i<=$max_tries;$i++) {
221 push @searches,"$baseurl/catalogue/search.pl?q=".@searchwords[int(rand(scalar @searchwords))];
223 my $run1 = HTTPD::Bench::ApacheBench::Run->new
224 ({ urls => \@searches,
225 cookies => [$cookie],
230 # send HTTP request sequences to server and time responses
234 $short_ms.= "|".$b1->total_time."\n";
235 $short_psec.="|".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)."\n";
237 print ("\t".$b1->total_time."ms\t".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)." biblios/sec\n");
238 print "ALERT : ".$b1->total_responses_failed." failures\n" if $b1->total_responses_failed;
241 print "Skipping step 3\n";
247 my $b2 = HTTPD::Bench::ApacheBench->new;
248 $b2->concurrency( $concurrency );
249 unless ($short_print) {
250 print "Step 4: patron detail page ";
252 my $run2 = HTTPD::Bench::ApacheBench::Run->new
253 ({ urls => \@borrowers,
254 cookies => [$cookie],
259 # send HTTP request sequences to server and time responses
263 $short_ms.= "|".$b2->total_time."\n";
264 $short_psec.="|".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)."\n";
266 print ("\t".$b2->total_time."ms\t".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)." borrowers/sec\n");
269 print "Skipping step 4\n";
273 # STEP 5: borrowers search
276 my $b2 = HTTPD::Bench::ApacheBench->new;
277 $b2->concurrency( $concurrency );
278 unless ($short_print) {
279 print "Step 5: patron search page ";
281 for (my $i=1;$i<=$max_tries;$i++) {
282 # print "$baseurl/members/moremember.pl?borrowernumber=$rand_borrowernumber\n";
283 push @borrowers,"$baseurl/members/member.pl?member=jean";
285 my $run2 = HTTPD::Bench::ApacheBench::Run->new
286 ({ urls => \@borrowers,
287 cookies => [$cookie],
292 # send HTTP request sequences to server and time responses
295 $short_ms.= "|".$b2->total_time."\n";
296 $short_psec.="|".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)."\n";
298 print ("\t".$b2->total_time."ms\t".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)." borrowers/sec\n");
301 print "Skipping step 5\n";
305 # STEP 6: issue (& then return) books
308 my $b3 = HTTPD::Bench::ApacheBench->new;
309 $b3->concurrency( $concurrency );
310 my $b4 = HTTPD::Bench::ApacheBench->new;
311 $b4->concurrency( $concurrency );
315 unless ($short_print) {
316 print "Step 6a circulation (checkouts) ";
318 $sth = $dbh->prepare("SELECT barcode FROM items WHERE itemnumber=?");
319 my $sth2 = $dbh->prepare("SELECT borrowernumber FROM borrowers WHERE borrowernumber=?");
320 for (my $i=1;$i<=$max_tries;$i++) {
321 my $rand_borrowernumber;
322 # check that the borrowernumber exist
323 until ($rand_borrowernumber) {
324 $rand_borrowernumber = int(rand($borrowernumber_max)+1);
325 $sth2->execute($rand_borrowernumber);
326 ($rand_borrowernumber) = $sth2->fetchrow;
328 # find a barcode & check it exists
330 until ($rand_barcode) {
331 my $rand_itemnumber = int(rand($itemnumber_max)+1);
332 $sth->execute($rand_itemnumber);
333 ($rand_barcode) = uri_escape_utf8($sth->fetchrow());
335 push @issues,"$baseurl/circ/circulation.pl?borrowernumber=$rand_borrowernumber&barcode=$rand_barcode&issueconfirmed=1";
336 push @returns,"$baseurl/circ/returns.pl?barcode=$rand_barcode";
338 my $run3 = HTTPD::Bench::ApacheBench::Run->new
340 cookies => [$cookie],
345 # send HTTP request sequences to server and time responses
349 $short_ms.= "|".$b3->total_time."\n";
350 $short_psec.="|".(int((1000*$b3->total_requests/$b3->total_time)*1000)/1000)."\n";
352 print ("\t".$b3->total_time."ms\t".(int((1000*$b3->total_requests/$b3->total_time)*1000)/1000)." checkouts/sec\n");
354 unless ($short_print) {
355 print "Step 6b circulation (checkins) ";
357 my $run4 = HTTPD::Bench::ApacheBench::Run->new
358 ({ urls => \@returns,
359 cookies => [$cookie],
364 # send HTTP request sequences to server and time responses
368 $short_ms.= "|".$b4->total_time."\n";
369 $short_psec.="|".(int((1000*$b4->total_requests/$b4->total_time)*1000)/1000)."\n";
371 print ("\t".$b4->total_time."ms\t".(int((1000*$b4->total_requests/$b4->total_time)*1000)/1000)." checkins/sec\n");
374 print "Skipping step 6\n";
378 unless ($short_print) {
379 print "all transactions at once ";
383 $short_ms.= "|".$b->total_time."\n";
384 $short_psec.="|".(int((1000*$b->total_requests/$b->total_time)*1000)/1000)."\n";
386 print ("\t".$b->total_time."ms\t".(int((1000*$b->total_requests/$b->total_time)*1000)/1000)." operations/sec\n");
389 print "Skipping 'testing all transactions at once'\n (step 0)";
393 print $short_ms."\n=====\n".$short_psec."\n";