b2596885d2023c01a99d1968473025059b5965e6
[srvgit] / 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 URI::Escape;
21 use Koha::Patrons;
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 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
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 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";
84     }
85 } elsif ( $resp->is_success ) {
86     die "Authentication failure: bad login/password";
87 } else {
88     die "Authentication failure: \n\t" . $resp->status_line;
89 }
90
91 die "You cannot use the database administrator account to launch this script"
92     unless defined Koha::Patrons->find( { userid => $user } );
93
94 # remove some unnecessary garbage from the cookie
95 $cookie =~ s/ path_spec; discard; version=0//;
96 $cookie =~ s/Set-Cookie3: //;
97
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");
102 $sth->execute;
103 my ($borrowernumber_max) = $sth->fetchrow;
104 my @borrowers;
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";
108 }
109
110 # grab some biblionumbers
111 $sth = $dbh->prepare("select max(biblionumber) from biblio");
112 $sth->execute;
113 my ($biblionumber_max) = $sth->fetchrow;
114 my @biblios;
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";
118 }
119
120 # grab some title and author, for random search
121 $sth = $dbh->prepare ("SELECT title, author FROM biblio LIMIT 10");
122 $sth->execute;
123 my ($title,$author);
124 my @searchwords;
125 while (($title,$author)=$sth->fetchrow) {
126     push @searchwords,split / /, $author//'';
127     push @searchwords,split / /, $title//'';
128 }
129
130 $sth = $dbh->prepare("select max(itemnumber) from items");
131 $sth->execute;
132 # find the biggest itemnumber
133 my ($itemnumber_max) = $sth->fetchrow;
134
135 $|=1;
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";
141 }
142 #
143 # the global benchmark we do at the end...
144 #
145 my $b = HTTPD::Bench::ApacheBench->new;
146 $b->concurrency( $concurrency );
147 my $ro;
148 #
149 # STEP 1: mainpage : (very) low RDBMS dependency
150 #
151 if ($steps=~ /1/) {
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     ";
156     }
157     for (my $i=1;$i<=$max_tries;$i++) {
158         push @mainpage,"$baseurl/mainpage.pl";
159     }
160     my $run0 = HTTPD::Bench::ApacheBench::Run->new
161         ({ urls => \@mainpage,
162            cookies => [$cookie],
163         });
164     $b0->add_run($run0);
165     $b->add_run($run0);
166
167     # send HTTP request sequences to server and time responses
168     $ro = $b0->execute;
169     # calculate hits/sec
170     if ($short_print) {
171         $short_ms.= "|".$b0->total_time."\n";
172         $short_psec.="|".(int((1000*$b0->total_requests/$b0->total_time)*1000)/1000)."\n";
173     } else {
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;
176     }
177 } else {
178     print "Skipping step 1\n";
179 }
180
181 #
182 # STEP 2: biblios
183 #
184 if ($steps=~ /2/) {
185     my $b1 = HTTPD::Bench::ApacheBench->new;
186     $b1->concurrency( $concurrency );
187
188     unless ($short_print) {
189         print "Step 2: catalog detail page        ";
190     }
191     my $run1 = HTTPD::Bench::ApacheBench::Run->new
192         ({ urls => \@biblios,
193            cookies => [$cookie],
194         });
195     $b1->add_run($run1);
196     $b->add_run($run1);
197
198     # send HTTP request sequences to server and time responses
199     $ro = $b1->execute;
200     # calculate hits/sec
201     if ($short_print) {
202         $short_ms.= "|".$b1->total_time."\n";
203         $short_psec.="|".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)."\n";
204     } else {
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;
207     }
208 } else {
209     print "Skipping step 2\n";
210 }
211 #
212 # STEP 3: search
213 #
214 if ($steps=~ /3/) {
215     my $b1 = HTTPD::Bench::ApacheBench->new;
216     $b1->concurrency( $concurrency );
217     unless ($short_print) {
218         print "Step 3: catalogue search               ";
219     }
220     my @searches;
221     for (my $i=1;$i<=$max_tries;$i++) {
222         push @searches,"$baseurl/catalogue/search.pl?q=".@searchwords[int(rand(scalar @searchwords))];
223     }
224     my $run1 = HTTPD::Bench::ApacheBench::Run->new
225         ({ urls => \@searches,
226            cookies => [$cookie],
227         });
228     $b1->add_run($run1);
229     $b->add_run($run1);
230
231     # send HTTP request sequences to server and time responses
232     $ro = $b1->execute;
233     # calculate hits/sec
234     if ($short_print) {
235         $short_ms.= "|".$b1->total_time."\n";
236         $short_psec.="|".(int((1000*$b1->total_requests/$b1->total_time)*1000)/1000)."\n";
237     } else {
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;
240     }
241 } else {
242     print "Skipping step 3\n";
243 }
244 #
245 # STEP 4: borrowers
246 #
247 if ($steps=~ /4/) {
248     my $b2 = HTTPD::Bench::ApacheBench->new;
249     $b2->concurrency( $concurrency );
250     unless ($short_print) {
251         print "Step 4: patron detail page         ";
252     }
253     my $run2 = HTTPD::Bench::ApacheBench::Run->new
254         ({ urls => \@borrowers,
255            cookies => [$cookie],
256         });
257     $b2->add_run($run2);
258     $b->add_run($run2);
259
260     # send HTTP request sequences to server and time responses
261     $ro = $b2->execute;
262     # calculate hits/sec
263     if ($short_print) {
264         $short_ms.= "|".$b2->total_time."\n";
265         $short_psec.="|".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)."\n";
266     } else {
267         print ("\t".$b2->total_time."ms\t".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)." borrowers/sec\n");
268     }
269 } else {
270     print "Skipping step 4\n";
271 }
272
273 #
274 # STEP 5: borrowers search
275 #
276 if ($steps=~ /5/) {
277     my $b2 = HTTPD::Bench::ApacheBench->new;
278     $b2->concurrency( $concurrency );
279     unless ($short_print) {
280         print "Step 5: patron search page             ";
281     }
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";
285     }
286     my $run2 = HTTPD::Bench::ApacheBench::Run->new
287         ({ urls => \@borrowers,
288            cookies => [$cookie],
289         });
290     $b2->add_run($run2);
291     $b->add_run($run2);
292
293     # send HTTP request sequences to server and time responses
294     $ro = $b2->execute;
295     if ($short_print) {
296         $short_ms.= "|".$b2->total_time."\n";
297         $short_psec.="|".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)."\n";
298     } else {
299         print ("\t".$b2->total_time."ms\t".(int((1000*$b2->total_requests/$b2->total_time)*1000)/1000)." borrowers/sec\n");
300     }
301 } else {
302     print "Skipping step 5\n";
303 }
304
305 #
306 # STEP 6: issue (& then return) books
307 #
308 if ($steps=~ /6/) {
309     my $b3 = HTTPD::Bench::ApacheBench->new;
310     $b3->concurrency( $concurrency );
311     my $b4 = HTTPD::Bench::ApacheBench->new;
312     $b4->concurrency( $concurrency );
313
314     my @issues;
315     my @returns;
316     unless ($short_print) {
317         print "Step 6a circulation (checkouts)        ";
318     }
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;
328         }
329         # find a barcode & check it exists
330         my $rand_barcode;
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());
335         }
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";
338     }
339     my $run3 = HTTPD::Bench::ApacheBench::Run->new
340         ({ urls => \@issues,
341            cookies => [$cookie],
342         });
343     $b3->add_run($run3);
344     $b->add_run($run3);
345
346     # send HTTP request sequences to server and time responses
347     $ro = $b3->execute;
348     # calculate hits/sec
349     if ($short_print) {
350         $short_ms.= "|".$b3->total_time."\n";
351         $short_psec.="|".(int((1000*$b3->total_requests/$b3->total_time)*1000)/1000)."\n";
352     } else {
353         print ("\t".$b3->total_time."ms\t".(int((1000*$b3->total_requests/$b3->total_time)*1000)/1000)." checkouts/sec\n");
354     }
355     unless ($short_print) {
356         print "Step 6b circulation (checkins)         ";
357     }
358     my $run4 = HTTPD::Bench::ApacheBench::Run->new
359         ({ urls => \@returns,
360            cookies => [$cookie],
361         });
362     $b4->add_run($run4);
363     $b->add_run($run4);
364
365     # send HTTP request sequences to server and time responses
366     $ro = $b4->execute;
367     # calculate hits/sec
368     if ($short_print) {
369         $short_ms.= "|".$b4->total_time."\n";
370         $short_psec.="|".(int((1000*$b4->total_requests/$b4->total_time)*1000)/1000)."\n";
371     } else {
372         print ("\t".$b4->total_time."ms\t".(int((1000*$b4->total_requests/$b4->total_time)*1000)/1000)." checkins/sec\n");
373     }
374 } else {
375     print "Skipping step 6\n";
376 }
377
378 if ($steps=~ /0/) {
379     unless ($short_print) {
380         print "all transactions at once               ";
381     }
382     $ro = $b->execute;
383     if ($short_print) {
384         $short_ms.= "|".$b->total_time."\n";
385         $short_psec.="|".(int((1000*$b->total_requests/$b->total_time)*1000)/1000)."\n";
386     } else {
387         print ("\t".$b->total_time."ms\t".(int((1000*$b->total_requests/$b->total_time)*1000)/1000)." operations/sec\n");
388     }
389 } else {
390     print "Skipping 'testing all transactions at once'\n (step 0)";
391 }
392
393 if ($short_print) {
394 print $short_ms."\n=====\n".$short_psec."\n";
395 }