Bug 17829: Move GetMember to Koha::Patron
[koha.git] / misc / load_testing / benchmark_staff.pl
1 #!/usr/bin/perl
2 # This script implements a basic benchmarking and regression testing
3 # utility for Koha
4
5 use strict;
6 use warnings;
7 BEGIN {
8     # find Koha's Perl modules
9     # test carefully before changing this
10     use FindBin;
11     eval { require "$FindBin::Bin/kohalib.pl" };
12 }
13
14 use Getopt::Long;
15 use HTTPD::Bench::ApacheBench;
16 use LWP::UserAgent;
17 use Data::Dumper;
18 use HTTP::Cookies;
19 use C4::Context;
20 use C4::Debug;
21 use URI::Escape;
22 use Koha::Patrons;
23
24 my ($help, $steps, $baseurl, $max_tries, $user, $password,$short_print);
25 GetOptions(
26     'help'    => \$help,
27     'steps:s'   => \$steps,
28     'url:s' => \$baseurl,
29     'user:s' => \$user,
30     'password:s' => \$password,
31     'maxtries:s' => \$max_tries,
32     'short' => \$short_print,
33 );
34 my $concurrency = 30;
35 $max_tries=20 unless $max_tries;
36 # if steps not provided, run all tests
37 $steps='0123456789' unless $steps;
38
39 # if short is set, we will only give number for direct inclusion on the wiki
40 my $short_ms="|-\n|ON\n";
41 my $short_psec="|-\n|ON\n";
42
43 if ($help || !$baseurl || !$user || !$password) {
44     print <<EOF
45 This script runs a benchmark of the staff interface. It benchmarks 6 different pages:
46 \t1- the staff main page
47 \t2- the catalog detail page, with a random biblionumber
48 \t3- the catalog search page, using a term retrieved from one of the 10 first titles/authors in the database
49 \t4- the patron detail page, with a random borrowernumber
50 \t5- the patron search page, searching for "Jean"
51 \t6- the circulation itself, doing check-out and check-in of random items to random patrons
52
53 \t0 all those tests at once
54 parameters :
55 \thelp = this screen
56 \tsteps = which steps you want to run. 
57 \t\tDon't use it if you want to run all tests. enter 125 if you want to run tests 1, 2 and 5
58 \t\tThe "all those tests at once" is numbered 0,and will run all tests previously run.
59 \t\tIf you run only one step, it's useless to run the 0, you'll get the same result.
60 \turl = the URL or your staff interface
61 \tlogin = Koha login
62 \tpassword = Koha password
63 \tmaxtries = how many tries you want to do. Defaulted to 20
64
65 SAMPLE : ./benchmark_staff.pl --url=http://yourstaff.org/cgi-bin/koha/ --user=test --password=test --steps=12
66
67 EOF
68 ;
69 exit;
70 }
71
72
73 # Authenticate via our handy dandy RESTful services
74 # and grab a cookie
75 my $ua = LWP::UserAgent->new();
76 my $cookie_jar = HTTP::Cookies->new();
77 my $cookie;
78 $ua->cookie_jar($cookie_jar);
79 my $resp = $ua->post( "$baseurl"."/svc/authentication" , {userid =>$user, password => $password} );
80 if( $resp->is_success and $resp->content =~ m|<status>ok</status>| ) {
81     $cookie_jar->extract_cookies( $resp );
82     $cookie = $cookie_jar->as_string;
83     unless ($short_print) {
84         print "Authentication successful\n";
85         print "Auth:\n $resp->content" if $debug;
86     }
87 } elsif ( $resp->is_success ) {
88     die "Authentication failure: bad login/password";
89 } else {
90     die "Authentication failure: \n\t" . $resp->status_line;
91 }
92
93 die "You cannot use the database administrator account to launch this script"
94     unless defined Koha::Patrons->find( { userid => $user } );
95
96 # remove some unnecessary garbage from the cookie
97 $cookie =~ s/ path_spec; discard; version=0//;
98 $cookie =~ s/Set-Cookie3: //;
99
100 # Get some data to work with
101 my $dbh=C4::Context->dbh();
102 # grab some borrowernumbers
103 my $sth = $dbh->prepare("select max(borrowernumber) from borrowers");
104 $sth->execute;
105 my ($borrowernumber_max) = $sth->fetchrow;
106 my @borrowers;
107 for (my $i=1;$i<=$max_tries;$i++) {
108     my $rand_borrowernumber = int(rand($borrowernumber_max)+1);
109     push @borrowers,"$baseurl/members/moremember.pl?borrowernumber=$rand_borrowernumber";
110 }
111
112 # grab some biblionumbers
113 $sth = $dbh->prepare("select max(biblionumber) from biblio");
114 $sth->execute;
115 my ($biblionumber_max) = $sth->fetchrow;
116 my @biblios;
117 for (my $i=1;$i<=$max_tries;$i++) {
118     my $rand_biblionumber = int(rand($biblionumber_max)+1);
119     push @biblios,"$baseurl/catalogue/detail.pl?biblionumber=$rand_biblionumber";
120 }
121
122 # grab some title and author, for random search
123 $sth = $dbh->prepare ("SELECT title, author FROM biblio LIMIT 10");
124 $sth->execute;
125 my ($title,$author);
126 my @searchwords;
127 while (($title,$author)=$sth->fetchrow) {
128     push @searchwords,split / /, $author//'';
129     push @searchwords,split / /, $title//'';
130 }
131
132 $sth = $dbh->prepare("select max(itemnumber) from items");
133 $sth->execute;
134 # find the biggest itemnumber
135 my ($itemnumber_max) = $sth->fetchrow;
136
137 $|=1;
138 unless ($short_print) {
139     print "--------------\n";
140     print "Koha STAFF benchmarking utility\n";
141     print "--------------\n";
142     print "Benchmarking with $max_tries occurrences of each operation and $concurrency concurrent sessions \n";
143 }
144 #
145 # the global benchmark we do at the end...
146 #
147 my $b = HTTPD::Bench::ApacheBench->new;
148 $b->concurrency( $concurrency );
149 my $ro;
150 #
151 # STEP 1: mainpage : (very) low RDBMS dependency
152 #
153 if ($steps=~ /1/) {
154     my $b0 = HTTPD::Bench::ApacheBench->new;
155     $b0->concurrency( $concurrency );    my @mainpage;
156     unless ($short_print) {
157         print "Step 1: staff client main page     ";
158     }
159     for (my $i=1;$i<=$max_tries;$i++) {
160         push @mainpage,"$baseurl/mainpage.pl";
161     }
162     my $run0 = HTTPD::Bench::ApacheBench::Run->new
163         ({ urls => \@mainpage,
164            cookies => [$cookie],
165         });
166     $b0->add_run($run0);
167     $b->add_run($run0);
168
169     # send HTTP request sequences to server and time responses
170     $ro = $b0->execute;
171     # calculate hits/sec
172     if ($short_print) {
173         $short_ms.= "|".$b0->total_time."\n";
174         $short_psec.="|".(int((1000*$b0->total_requests/$b0->total_time)*1000)/1000)."\n";
175     } else {
176         print ("\t".$b0->total_time."ms\t".(int((1000*$b0->total_requests/$b0->total_time)*1000)/1000)." pages/sec\n");
177         print "ALERT : ".$b0->total_responses_failed." failures\n" if $b0->total_responses_failed;
178     }
179 } else {
180     print "Skipping step 1\n";
181 }
182
183 #
184 # STEP 2: biblios
185 #
186 if ($steps=~ /2/) {
187     my $b1 = HTTPD::Bench::ApacheBench->new;
188     $b1->concurrency( $concurrency );
189
190     unless ($short_print) {
191         print "Step 2: catalog detail page        ";
192     }
193     my $run1 = HTTPD::Bench::ApacheBench::Run->new
194         ({ urls => \@biblios,
195            cookies => [$cookie],
196         });
197     $b1->add_run($run1);
198     $b->add_run($run1);
199
200     # send HTTP request sequences to server and time responses
201     $ro = $b1->execute;
202     # calculate hits/sec
203     if ($short_print) {
204         $short_ms.= "|".$b1->total_time."\n";
205         $short_psec.="|".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)."\n";
206     } else {
207         print ("\t".$b1->total_time."ms\t".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)." biblios/sec\n");
208         print "ALERT : ".$b1->total_responses_failed." failures\n" if $b1->total_responses_failed;
209     }
210 } else {
211     print "Skipping step 2\n";
212 }
213 #
214 # STEP 3: search
215 #
216 if ($steps=~ /3/) {
217     my $b1 = HTTPD::Bench::ApacheBench->new;
218     $b1->concurrency( $concurrency );
219     unless ($short_print) {
220         print "Step 3: catalogue search               ";
221     }
222     my @searches;
223     for (my $i=1;$i<=$max_tries;$i++) {
224         push @searches,"$baseurl/catalogue/search.pl?q=".@searchwords[int(rand(scalar @searchwords))];
225     }
226     my $run1 = HTTPD::Bench::ApacheBench::Run->new
227         ({ urls => \@searches,
228            cookies => [$cookie],
229         });
230     $b1->add_run($run1);
231     $b->add_run($run1);
232
233     # send HTTP request sequences to server and time responses
234     $ro = $b1->execute;
235     # calculate hits/sec
236     if ($short_print) {
237         $short_ms.= "|".$b1->total_time."\n";
238         $short_psec.="|".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)."\n";
239     } else {
240         print ("\t".$b1->total_time."ms\t".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)." biblios/sec\n");
241         print "ALERT : ".$b1->total_responses_failed." failures\n" if $b1->total_responses_failed;
242     }
243 } else {
244     print "Skipping step 3\n";
245 }
246 #
247 # STEP 4: borrowers
248 #
249 if ($steps=~ /4/) {
250     my $b2 = HTTPD::Bench::ApacheBench->new;
251     $b2->concurrency( $concurrency );
252     unless ($short_print) {
253         print "Step 4: patron detail page         ";
254     }
255     my $run2 = HTTPD::Bench::ApacheBench::Run->new
256         ({ urls => \@borrowers,
257            cookies => [$cookie],
258         });
259     $b2->add_run($run2);
260     $b->add_run($run2);
261
262     # send HTTP request sequences to server and time responses
263     $ro = $b2->execute;
264     # calculate hits/sec
265     if ($short_print) {
266         $short_ms.= "|".$b2->total_time."\n";
267         $short_psec.="|".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)."\n";
268     } else {
269         print ("\t".$b2->total_time."ms\t".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)." borrowers/sec\n");
270     }
271 } else {
272     print "Skipping step 4\n";
273 }
274
275 #
276 # STEP 5: borrowers search
277 #
278 if ($steps=~ /5/) {
279     my $b2 = HTTPD::Bench::ApacheBench->new;
280     $b2->concurrency( $concurrency );
281     unless ($short_print) {
282         print "Step 5: patron search page             ";
283     }
284     for (my $i=1;$i<=$max_tries;$i++) {
285     #     print "$baseurl/members/moremember.pl?borrowernumber=$rand_borrowernumber\n";
286         push @borrowers,"$baseurl/members/member.pl?member=jean";
287     }
288     my $run2 = HTTPD::Bench::ApacheBench::Run->new
289         ({ urls => \@borrowers,
290            cookies => [$cookie],
291         });
292     $b2->add_run($run2);
293     $b->add_run($run2);
294
295     # send HTTP request sequences to server and time responses
296     $ro = $b2->execute;
297     if ($short_print) {
298         $short_ms.= "|".$b2->total_time."\n";
299         $short_psec.="|".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)."\n";
300     } else {
301         print ("\t".$b2->total_time."ms\t".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)." borrowers/sec\n");
302     }
303 } else {
304     print "Skipping step 5\n";
305 }
306
307 #
308 # STEP 6: issue (& then return) books
309 #
310 if ($steps=~ /6/) {
311     my $b3 = HTTPD::Bench::ApacheBench->new;
312     $b3->concurrency( $concurrency );
313     my $b4 = HTTPD::Bench::ApacheBench->new;
314     $b4->concurrency( $concurrency );
315
316     my @issues;
317     my @returns;
318     unless ($short_print) {
319         print "Step 6a circulation (checkouts)        ";
320     }
321     $sth = $dbh->prepare("SELECT barcode FROM items WHERE itemnumber=?");
322     my $sth2 = $dbh->prepare("SELECT borrowernumber FROM borrowers WHERE borrowernumber=?");
323     for (my $i=1;$i<=$max_tries;$i++) {
324         my $rand_borrowernumber;
325         # check that the borrowernumber exist
326         until ($rand_borrowernumber) {
327             $rand_borrowernumber = int(rand($borrowernumber_max)+1);
328             $sth2->execute($rand_borrowernumber);
329             ($rand_borrowernumber) = $sth2->fetchrow;
330         }
331         # find a barcode & check it exists
332         my $rand_barcode;
333         until ($rand_barcode) {
334             my $rand_itemnumber = int(rand($itemnumber_max)+1);
335             $sth->execute($rand_itemnumber);
336             ($rand_barcode) = uri_escape_utf8($sth->fetchrow());
337         }
338         push @issues,"$baseurl/circ/circulation.pl?borrowernumber=$rand_borrowernumber&barcode=$rand_barcode&issueconfirmed=1";
339         push @returns,"$baseurl/circ/returns.pl?barcode=$rand_barcode";
340     }
341     my $run3 = HTTPD::Bench::ApacheBench::Run->new
342         ({ urls => \@issues,
343            cookies => [$cookie],
344         });
345     $b3->add_run($run3);
346     $b->add_run($run3);
347
348     # send HTTP request sequences to server and time responses
349     $ro = $b3->execute;
350     # calculate hits/sec
351     if ($short_print) {
352         $short_ms.= "|".$b3->total_time."\n";
353         $short_psec.="|".(int((1000*$b3->total_requests/$b3->total_time)*1000)/1000)."\n";
354     } else {
355         print ("\t".$b3->total_time."ms\t".(int((1000*$b3->total_requests/$b3->total_time)*1000)/1000)." checkouts/sec\n");
356     }
357     unless ($short_print) {
358         print "Step 6b circulation (checkins)         ";
359     }
360     my $run4 = HTTPD::Bench::ApacheBench::Run->new
361         ({ urls => \@returns,
362            cookies => [$cookie],
363         });
364     $b4->add_run($run4);
365     $b->add_run($run4);
366
367     # send HTTP request sequences to server and time responses
368     $ro = $b4->execute;
369     # calculate hits/sec
370     if ($short_print) {
371         $short_ms.= "|".$b4->total_time."\n";
372         $short_psec.="|".(int((1000*$b4->total_requests/$b4->total_time)*1000)/1000)."\n";
373     } else {
374         print ("\t".$b4->total_time."ms\t".(int((1000*$b4->total_requests/$b4->total_time)*1000)/1000)." checkins/sec\n");
375     }
376 } else {
377     print "Skipping step 6\n";
378 }
379
380 if ($steps=~ /0/) {
381     unless ($short_print) {
382         print "all transactions at once               ";
383     }
384     $ro = $b->execute;
385     if ($short_print) {
386         $short_ms.= "|".$b->total_time."\n";
387         $short_psec.="|".(int((1000*$b->total_requests/$b->total_time)*1000)/1000)."\n";
388     } else {
389         print ("\t".$b->total_time."ms\t".(int((1000*$b->total_requests/$b->total_time)*1000)/1000)." operations/sec\n");
390     }
391 } else {
392     print "Skipping 'testing all transactions at once'\n (step 0)";
393 }
394
395 if ($short_print) {
396 print $short_ms."\n=====\n".$short_psec."\n";
397 }