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