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