Bug 24807: (follow-up) Add support for spaces as unknown characters
[koha.git] / misc / load_testing / benchmark_staff.pl
blob9c0a92fc54e45b580848bba832f967b5db034b5c
1 #!/usr/bin/perl
2 # This script implements a basic benchmarking and regression testing
3 # utility for Koha
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" };
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;
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,
34 my $concurrency = 30;
35 $max_tries=20 unless $max_tries;
36 # if steps not provided, run all tests
37 $steps='0123456789' unless $steps;
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";
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
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
65 SAMPLE : ./benchmark_staff.pl --url=http://yourstaff.org/cgi-bin/koha/ --user=test --password=test --steps=12
67 EOF
69 exit;
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;
87 } elsif ( $resp->is_success ) {
88 die "Authentication failure: bad login/password";
89 } else {
90 die "Authentication failure: \n\t" . $resp->status_line;
93 die "You cannot use the database administrator account to launch this script"
94 unless defined Koha::Patrons->find( { userid => $user } );
96 # remove some unnecessary garbage from the cookie
97 $cookie =~ s/ path_spec; discard; version=0//;
98 $cookie =~ s/Set-Cookie3: //;
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";
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";
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//'';
132 $sth = $dbh->prepare("select max(itemnumber) from items");
133 $sth->execute;
134 # find the biggest itemnumber
135 my ($itemnumber_max) = $sth->fetchrow;
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";
145 # the global benchmark we do at the end...
147 my $b = HTTPD::Bench::ApacheBench->new;
148 $b->concurrency( $concurrency );
149 my $ro;
151 # STEP 1: mainpage : (very) low RDBMS dependency
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 interface main page ";
159 for (my $i=1;$i<=$max_tries;$i++) {
160 push @mainpage,"$baseurl/mainpage.pl";
162 my $run0 = HTTPD::Bench::ApacheBench::Run->new
163 ({ urls => \@mainpage,
164 cookies => [$cookie],
166 $b0->add_run($run0);
167 $b->add_run($run0);
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;
179 } else {
180 print "Skipping step 1\n";
184 # STEP 2: biblios
186 if ($steps=~ /2/) {
187 my $b1 = HTTPD::Bench::ApacheBench->new;
188 $b1->concurrency( $concurrency );
190 unless ($short_print) {
191 print "Step 2: catalog detail page ";
193 my $run1 = HTTPD::Bench::ApacheBench::Run->new
194 ({ urls => \@biblios,
195 cookies => [$cookie],
197 $b1->add_run($run1);
198 $b->add_run($run1);
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;
210 } else {
211 print "Skipping step 2\n";
214 # STEP 3: search
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 ";
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))];
226 my $run1 = HTTPD::Bench::ApacheBench::Run->new
227 ({ urls => \@searches,
228 cookies => [$cookie],
230 $b1->add_run($run1);
231 $b->add_run($run1);
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;
243 } else {
244 print "Skipping step 3\n";
247 # STEP 4: borrowers
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 ";
255 my $run2 = HTTPD::Bench::ApacheBench::Run->new
256 ({ urls => \@borrowers,
257 cookies => [$cookie],
259 $b2->add_run($run2);
260 $b->add_run($run2);
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");
271 } else {
272 print "Skipping step 4\n";
276 # STEP 5: borrowers search
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 ";
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";
288 my $run2 = HTTPD::Bench::ApacheBench::Run->new
289 ({ urls => \@borrowers,
290 cookies => [$cookie],
292 $b2->add_run($run2);
293 $b->add_run($run2);
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");
303 } else {
304 print "Skipping step 5\n";
308 # STEP 6: issue (& then return) books
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 );
316 my @issues;
317 my @returns;
318 unless ($short_print) {
319 print "Step 6a circulation (checkouts) ";
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;
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());
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";
341 my $run3 = HTTPD::Bench::ApacheBench::Run->new
342 ({ urls => \@issues,
343 cookies => [$cookie],
345 $b3->add_run($run3);
346 $b->add_run($run3);
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");
357 unless ($short_print) {
358 print "Step 6b circulation (checkins) ";
360 my $run4 = HTTPD::Bench::ApacheBench::Run->new
361 ({ urls => \@returns,
362 cookies => [$cookie],
364 $b4->add_run($run4);
365 $b->add_run($run4);
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");
376 } else {
377 print "Skipping step 6\n";
380 if ($steps=~ /0/) {
381 unless ($short_print) {
382 print "all transactions at once ";
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");
391 } else {
392 print "Skipping 'testing all transactions at once'\n (step 0)";
395 if ($short_print) {
396 print $short_ms."\n=====\n".$short_psec."\n";