Bug 29072: Move reference route /cities spec to YAML
[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 qw( GetOptions );
15 use HTTPD::Bench::ApacheBench;
16 use LWP::UserAgent;
17 use HTTP::Cookies;
18 use C4::Context;
19 use URI::Escape qw( uri_escape_utf8 );
20 use Koha::Patrons;
21
22 my ($help, $steps, $baseurl, $max_tries, $user, $password,$short_print);
23 GetOptions(
24     'help'    => \$help,
25     'steps:s'   => \$steps,
26     'url:s' => \$baseurl,
27     'user:s' => \$user,
28     'password:s' => \$password,
29     'maxtries:s' => \$max_tries,
30     'short' => \$short_print,
31 );
32 my $concurrency = 30;
33 $max_tries=20 unless $max_tries;
34 # if steps not provided, run all tests
35 $steps='0123456789' unless $steps;
36
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";
40
41 if ($help || !$baseurl || !$user || !$password) {
42     print <<EOF
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
50
51 \t0 all those tests at once
52 parameters :
53 \thelp = this screen
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
59 \tlogin = Koha login
60 \tpassword = Koha password
61 \tmaxtries = how many tries you want to do. Defaulted to 20
62
63 SAMPLE : ./benchmark_staff.pl --url=http://yourstaff.org/cgi-bin/koha/ --user=test --password=test --steps=12
64
65 EOF
66 ;
67 exit;
68 }
69
70
71 # Authenticate via our handy dandy RESTful services
72 # and grab a cookie
73 my $ua = LWP::UserAgent->new();
74 my $cookie_jar = HTTP::Cookies->new();
75 my $cookie;
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";
83     }
84 } elsif ( $resp->is_success ) {
85     die "Authentication failure: bad login/password";
86 } else {
87     die "Authentication failure: \n\t" . $resp->status_line;
88 }
89
90 die "You cannot use the database administrator account to launch this script"
91     unless defined Koha::Patrons->find( { userid => $user } );
92
93 # remove some unnecessary garbage from the cookie
94 $cookie =~ s/ path_spec; discard; version=0//;
95 $cookie =~ s/Set-Cookie3: //;
96
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");
101 $sth->execute;
102 my ($borrowernumber_max) = $sth->fetchrow;
103 my @borrowers;
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";
107 }
108
109 # grab some biblionumbers
110 $sth = $dbh->prepare("select max(biblionumber) from biblio");
111 $sth->execute;
112 my ($biblionumber_max) = $sth->fetchrow;
113 my @biblios;
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";
117 }
118
119 # grab some title and author, for random search
120 $sth = $dbh->prepare ("SELECT title, author FROM biblio LIMIT 10");
121 $sth->execute;
122 my ($title,$author);
123 my @searchwords;
124 while (($title,$author)=$sth->fetchrow) {
125     push @searchwords,split / /, $author//'';
126     push @searchwords,split / /, $title//'';
127 }
128
129 $sth = $dbh->prepare("select max(itemnumber) from items");
130 $sth->execute;
131 # find the biggest itemnumber
132 my ($itemnumber_max) = $sth->fetchrow;
133
134 $|=1;
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";
140 }
141 #
142 # the global benchmark we do at the end...
143 #
144 my $b = HTTPD::Bench::ApacheBench->new;
145 $b->concurrency( $concurrency );
146 my $ro;
147 #
148 # STEP 1: mainpage : (very) low RDBMS dependency
149 #
150 if ($steps=~ /1/) {
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     ";
155     }
156     for (my $i=1;$i<=$max_tries;$i++) {
157         push @mainpage,"$baseurl/mainpage.pl";
158     }
159     my $run0 = HTTPD::Bench::ApacheBench::Run->new
160         ({ urls => \@mainpage,
161            cookies => [$cookie],
162         });
163     $b0->add_run($run0);
164     $b->add_run($run0);
165
166     # send HTTP request sequences to server and time responses
167     $ro = $b0->execute;
168     # calculate hits/sec
169     if ($short_print) {
170         $short_ms.= "|".$b0->total_time."\n";
171         $short_psec.="|".(int((1000*$b0->total_requests/$b0->total_time)*1000)/1000)."\n";
172     } else {
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;
175     }
176 } else {
177     print "Skipping step 1\n";
178 }
179
180 #
181 # STEP 2: biblios
182 #
183 if ($steps=~ /2/) {
184     my $b1 = HTTPD::Bench::ApacheBench->new;
185     $b1->concurrency( $concurrency );
186
187     unless ($short_print) {
188         print "Step 2: catalog detail page        ";
189     }
190     my $run1 = HTTPD::Bench::ApacheBench::Run->new
191         ({ urls => \@biblios,
192            cookies => [$cookie],
193         });
194     $b1->add_run($run1);
195     $b->add_run($run1);
196
197     # send HTTP request sequences to server and time responses
198     $ro = $b1->execute;
199     # calculate hits/sec
200     if ($short_print) {
201         $short_ms.= "|".$b1->total_time."\n";
202         $short_psec.="|".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)."\n";
203     } else {
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;
206     }
207 } else {
208     print "Skipping step 2\n";
209 }
210 #
211 # STEP 3: search
212 #
213 if ($steps=~ /3/) {
214     my $b1 = HTTPD::Bench::ApacheBench->new;
215     $b1->concurrency( $concurrency );
216     unless ($short_print) {
217         print "Step 3: catalogue search               ";
218     }
219     my @searches;
220     for (my $i=1;$i<=$max_tries;$i++) {
221         push @searches,"$baseurl/catalogue/search.pl?q=".@searchwords[int(rand(scalar @searchwords))];
222     }
223     my $run1 = HTTPD::Bench::ApacheBench::Run->new
224         ({ urls => \@searches,
225            cookies => [$cookie],
226         });
227     $b1->add_run($run1);
228     $b->add_run($run1);
229
230     # send HTTP request sequences to server and time responses
231     $ro = $b1->execute;
232     # calculate hits/sec
233     if ($short_print) {
234         $short_ms.= "|".$b1->total_time."\n";
235         $short_psec.="|".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)."\n";
236     } else {
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;
239     }
240 } else {
241     print "Skipping step 3\n";
242 }
243 #
244 # STEP 4: borrowers
245 #
246 if ($steps=~ /4/) {
247     my $b2 = HTTPD::Bench::ApacheBench->new;
248     $b2->concurrency( $concurrency );
249     unless ($short_print) {
250         print "Step 4: patron detail page         ";
251     }
252     my $run2 = HTTPD::Bench::ApacheBench::Run->new
253         ({ urls => \@borrowers,
254            cookies => [$cookie],
255         });
256     $b2->add_run($run2);
257     $b->add_run($run2);
258
259     # send HTTP request sequences to server and time responses
260     $ro = $b2->execute;
261     # calculate hits/sec
262     if ($short_print) {
263         $short_ms.= "|".$b2->total_time."\n";
264         $short_psec.="|".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)."\n";
265     } else {
266         print ("\t".$b2->total_time."ms\t".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)." borrowers/sec\n");
267     }
268 } else {
269     print "Skipping step 4\n";
270 }
271
272 #
273 # STEP 5: borrowers search
274 #
275 if ($steps=~ /5/) {
276     my $b2 = HTTPD::Bench::ApacheBench->new;
277     $b2->concurrency( $concurrency );
278     unless ($short_print) {
279         print "Step 5: patron search page             ";
280     }
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";
284     }
285     my $run2 = HTTPD::Bench::ApacheBench::Run->new
286         ({ urls => \@borrowers,
287            cookies => [$cookie],
288         });
289     $b2->add_run($run2);
290     $b->add_run($run2);
291
292     # send HTTP request sequences to server and time responses
293     $ro = $b2->execute;
294     if ($short_print) {
295         $short_ms.= "|".$b2->total_time."\n";
296         $short_psec.="|".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)."\n";
297     } else {
298         print ("\t".$b2->total_time."ms\t".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)." borrowers/sec\n");
299     }
300 } else {
301     print "Skipping step 5\n";
302 }
303
304 #
305 # STEP 6: issue (& then return) books
306 #
307 if ($steps=~ /6/) {
308     my $b3 = HTTPD::Bench::ApacheBench->new;
309     $b3->concurrency( $concurrency );
310     my $b4 = HTTPD::Bench::ApacheBench->new;
311     $b4->concurrency( $concurrency );
312
313     my @issues;
314     my @returns;
315     unless ($short_print) {
316         print "Step 6a circulation (checkouts)        ";
317     }
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;
327         }
328         # find a barcode & check it exists
329         my $rand_barcode;
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());
334         }
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";
337     }
338     my $run3 = HTTPD::Bench::ApacheBench::Run->new
339         ({ urls => \@issues,
340            cookies => [$cookie],
341         });
342     $b3->add_run($run3);
343     $b->add_run($run3);
344
345     # send HTTP request sequences to server and time responses
346     $ro = $b3->execute;
347     # calculate hits/sec
348     if ($short_print) {
349         $short_ms.= "|".$b3->total_time."\n";
350         $short_psec.="|".(int((1000*$b3->total_requests/$b3->total_time)*1000)/1000)."\n";
351     } else {
352         print ("\t".$b3->total_time."ms\t".(int((1000*$b3->total_requests/$b3->total_time)*1000)/1000)." checkouts/sec\n");
353     }
354     unless ($short_print) {
355         print "Step 6b circulation (checkins)         ";
356     }
357     my $run4 = HTTPD::Bench::ApacheBench::Run->new
358         ({ urls => \@returns,
359            cookies => [$cookie],
360         });
361     $b4->add_run($run4);
362     $b->add_run($run4);
363
364     # send HTTP request sequences to server and time responses
365     $ro = $b4->execute;
366     # calculate hits/sec
367     if ($short_print) {
368         $short_ms.= "|".$b4->total_time."\n";
369         $short_psec.="|".(int((1000*$b4->total_requests/$b4->total_time)*1000)/1000)."\n";
370     } else {
371         print ("\t".$b4->total_time."ms\t".(int((1000*$b4->total_requests/$b4->total_time)*1000)/1000)." checkins/sec\n");
372     }
373 } else {
374     print "Skipping step 6\n";
375 }
376
377 if ($steps=~ /0/) {
378     unless ($short_print) {
379         print "all transactions at once               ";
380     }
381     $ro = $b->execute;
382     if ($short_print) {
383         $short_ms.= "|".$b->total_time."\n";
384         $short_psec.="|".(int((1000*$b->total_requests/$b->total_time)*1000)/1000)."\n";
385     } else {
386         print ("\t".$b->total_time."ms\t".(int((1000*$b->total_requests/$b->total_time)*1000)/1000)." operations/sec\n");
387     }
388 } else {
389     print "Skipping 'testing all transactions at once'\n (step 0)";
390 }
391
392 if ($short_print) {
393 print $short_ms."\n=====\n".$short_psec."\n";
394 }