bottleneck testcase based on rubbos
[bottlenecks.git] / rubbos / app / tomcat-connectors-1.2.32-src / tools / reports / tomcat_trend.pl
1 #!/usr/local/bin/perl
2
3 #
4 # Licensed to the Apache Software Foundation (ASF) under one or more
5 # contributor license agreements.  See the NOTICE file distributed with
6 # this work for additional information regarding copyright ownership.
7 # The ASF licenses this file to You under the Apache License, Version 2.0
8 # (the "License"); you may not use this file except in compliance with
9 # the License.  You may obtain a copy of the License at
10 #
11 #    http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18 #
19
20 # $Id: tomcat_trend.pl 466585 2006-10-21 22:16:34Z markt $
21
22 # Author:  Glenn Nielsen
23
24 # Script for analyzing mod_jk.log data when logging tomcat request data using
25 # the JkRequestLogFormat Apache mod_jk configuration.
26 #
27 # Generates statistics for request latency and errors.  Archives the generated
28 # data to files for later use in long term trend graphs and reports.
29 #
30 # tomcat_trend.pl <directory containing mod_jk.log> <directory for archiving statistics>
31
32 use FileHandle;
33 use Statistics::Descriptive;
34 use Time::Local;
35
36 # Constants
37
38 %MON = ('JAN' => 0, 'Jan' => 0,
39         'FEB' => 1, 'Feb' => 1,
40         'MAR' => 2, 'Mar' => 2,
41         'APR' => 3, 'Apr' => 3,
42         'MAY' => 4, 'May' => 4,
43         'JUN' => 5, 'Jun' => 5,
44         'JUL' => 6, 'Jul' => 6,
45         'AUG' => 7, 'Aug' => 7,
46         'SEP' => 8, 'Sep' => 8,
47         'OCT' => 9, 'Oct' => 9,
48         'NOV' => 10, 'Nov' => 10,
49         'DEC' => 11, 'Dec' => 11,);
50
51 @Months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
52
53 # Check the args
54
55 $logdir= $ARGV[0];
56 $archivedir = $ARGV[1];
57
58 die "Usage: $0 logdir archivedir"
59    unless( length($logdir) && length($archivedir) );
60
61 die "Log Directory $logdir doesn't exist"
62    unless( -d $logdir);
63
64 die "Archive Directory $archivedir doesn't exist"
65    unless( -d $archivedir);
66
67 # Get start date from global.data if it exists
68
69 if( -e "$archivedir/global.data" ) {
70    # Get the start date from the last entry in global.data
71    @tail = `tail -1 $archivedir/global.data`;
72    $startdate = (split /\s+/,$tail[0])[0];
73    ($day, $mon, $year) = (localtime($startdate))[3..5];
74    if ($day == 31) {
75       $day=1;
76       $month++;
77       if ($month > 11) {
78          $month=0;
79          $year++;
80       }
81    }
82    $startdate = timelocal(0,0,0,$day+1,$mon,$year);
83
84 }
85
86 ($day, $mon, $year) = (localtime(time))[3..5];
87 $curdate = timelocal(0,0,0,$day,$mon,$year);
88 print "Today: " . scalar(localtime($curdate)) . "\n";
89
90 # Get the log files names and date they start
91 @logs = `ls -1 $logdir/mod_jk.log*`;
92 foreach( @logs ) {
93    $logfile = $_;
94    chomp($logfile);
95    next if ( $logfile =~ /\.(bz2|gz|zip)$/ );
96    @head = `head -1 $logfile`;
97    ($mon, $day, $time, $year) = (split /\s+/,$head[0])[1..4];
98    ($hour, $min, $sec) = split /:/,$time;
99    $year =~ s/\]$//;
100    $logtime = timelocal($sec,$min,$hour,$day,$MON{$mon},$year-1900);
101    $modjklog{$logtime} = $logfile;
102 }
103
104 # Set the startdate if this is the first time processing the logs
105 # If we have a startdate, remove log files we con't need to process
106 foreach $logtime ( sort {$a <=> $b} keys %modjklog ) {
107    # If logs haven't been processed before, set startdate to time of 
108    # first log entry
109    if( $startdate !~ /^\d+$/ ) {
110       $startdate = $logtime;
111       ($day, $mon, $year) = (localtime($startdate))[3..5];
112       $startdate = timelocal(0,0,0,$day,$mon,$year);
113       last;
114    }
115    if( $logtime > $startdate ) {
116       last;
117    }
118    # Save the previous log file since start date may start here
119    $prevlogfile = $modjklog{$logtime};
120    $prevlogtime = $logtime;
121    # Remove log files we don't need to process
122    delete $modjklog{$logtime};
123 }
124
125 # Add back in the previous log file where we need to start processing
126 if( defined $prevlogtime ) {
127    $modjklog{$prevlogtime} = $prevlogfile;
128 }
129
130 print "StartDate: " . scalar(localtime($startdate)) . "\n";
131 $processdate = $startdate;
132
133 foreach $key ( sort {$a <=> $b} keys %modjklog ) {
134    $logtime = $processdate;
135    $logfile = $modjklog{$key};
136    print "Processing log: $logfile\n";
137    last if( $key >= $curdate );
138    $fh = new FileHandle "<$logfile";  
139    die "Open of logfile $logfile failed: $!"
140       unless defined $fh;
141    while( $line = $fh->getline) {
142       chomp($line);
143       ($mon, $day, $time, $year) = (split /\s+/,$line)[1..4];
144       ($hour, $min, $sec) = split /:/,$time;
145       $year =~ s/\]$//;
146       if( $day !~ /^\d+/ || $hour !~ /^\d+/ || $min!~ /^\d+/ || $sec !~ /^\d+/ ) {
147          print "Unknown log entry: $origline\n" unless $origline =~ /\.c /;
148          next;
149       }
150       $logtime = timelocal($sec,$min,$hour,$day,$MON{$mon},$year-1900);
151
152       if( $logtime > $processdate ) {
153          $origline = $line;
154          # Strip off the leading date and time
155          $line =~ s/^\[.*\] //;
156
157          # See if this is a new 5 minute period
158          $interval = int($logtime/300);
159          if( $interval != $previnterval ) {
160             if( defined $previnterval ) {
161                &IntervalStats(\%Global,\%Interval,$previnterval*300);
162             }
163             undef %Interval;
164             undef @IntervalLatency;
165             undef %IntervalWorkers;
166             $Interval{tomcat_full} = 0;
167             $Interval{client_gone} = 0;
168             $Interval{latency} = \@IntervalLatency;
169             $Interval{workers} = \%IntervalWorkers;
170             $previnterval = $interval;
171          }
172
173          # See if this is a new day
174          if( $day != $prevday ) {
175             if( defined $prevday ) {
176                &DailyStats($processdate,\%Global);
177             }
178             undef %Global;
179             undef %GlobalWorkers;
180             undef @GlobalLatency;
181             $Global{tomcat_full} = 0;
182             $Global{client_gone} = 0;
183             $Global{interval} = "";
184             $Global{latency} = \@GlobalLatency;
185             $Global{workers} = \%GlobalWorkers;
186             $Global{errors} = "";
187             $prevday = $day;
188             $processdate = $logtime;
189          }
190
191          # Stop processing if logtime is today
192          last if( $logtime >= $curdate );
193
194          if( $line =~ /\d\)\]{0,1}: / ) {
195             # Handle a mod_jk error
196             if( $line =~ /(jk_tcp_socket_recvfull failed|ERROR: Receiving from tomcat failed)/ ) {
197                $Global{tomcat_full}++;
198                $Interval{tomcat_full}++;
199             } elsif( $line =~ /(ajp_process_callback - write failed|ERROR sending data to client. Connection aborted or network problems|Client connection aborted or network problems)/ ) {
200                $Global{client_gone}++;
201                $Interval{client_gone}++;
202             }
203             next;
204          } else {
205             # Handle a mod_jk request log entry
206             $line =~ s/^\[.*\] //;
207             $line =~ s/\"(GET|POST|OPTIONS|HEAD)[^\"]*\" //;
208             $line =~ s/[\?\;].*\"//;
209             $line =~ s/\"//g;
210             ($work, $host, $page, $status, $latency) = split /\s+/,$line;
211             $page =~ s/\/\//\//g;
212             $page =~ s/\.\//\//g;
213             if( length($work) <= 0 || length($host) <= 0 ||
214                 length($page) <= 0 || $status !~ /^\d+$/ || $latency !~ /^\d+\.\d+$/ ) {
215                print "Unknown log entry: $origline\n" unless $origline =~ /\.c /;
216                next;
217             }
218
219             # Throw out abnormally long requests and log them as an error
220             if( $latency >= 1800 ) {
221                $Global{errors} .= "Error: $page has an HTTP status of $status and an ";
222                $Global{errors} .= "abnormally long request latency of $latency seconds\n";
223                next;
224             }
225
226             # Save the data by day for Global, Worker, and Host
227             push @{$Global{latency}},$latency;
228             $workers = $Global{workers};
229             if( !defined $$workers{$work} ) {
230                undef @{"$work"};
231                undef %{"$work"};
232                undef %{"$work-hosts"};
233                ${"$work"}{latency} = \@{"$work"};
234                ${"$work"}{hosts} = \%{"$work-hosts"};
235                ${"$work"}{interval} = "";
236                $$workers{$work} = \%{"$work"};
237             }
238             $worker = $$workers{$work};
239             push @{$$worker{latency}},$latency;
240
241             if( !defined $$worker{hosts}{$host} ) {
242                undef @{"$work-$host"};
243                undef %{"$work-$host"};
244                undef %{"$work-$host-pages"};
245                ${"$work-$host"}{latency} = \@{"$work-$host"};
246                ${"$work-$host"}{pages} = \%{"$work-$host-pages"};
247                ${"$work-$host"}{interval} = "";
248                $$worker{hosts}{$host} = \%{"$work-$host"};
249             }
250             $hoster = $$worker{hosts}{$host};
251             push @{$$hoster{latency}},$latency;
252
253             if( !defined $$hoster{pages}{$page} ) {
254                 undef @{"$work-$host-$page"};
255                 $$hoster{pages}{$page} = \@{"$work-$host-$page"};
256             }
257             push @{$$hoster{pages}{$page}},$latency;
258
259             # Save the data by 5 minute interval for Global, Worker, and Host
260             push @{$Interval{latency}},$latency;
261             $workers = $Interval{workers};
262             if( !defined $$workers{"$work"} ) {
263                undef @{"int-$work"};
264                undef %{"int-$work"};
265                undef %{"int-$work-hosts"};
266                ${"int-$work"}{latency} = \@{"int-$work"};
267                ${"int-$work"}{hosts} = \%{"int-$work-hosts"};
268                $$workers{$work} = \%{"int-$work"};
269             }
270             $worker = $$workers{$work};
271             push @{$$worker{latency}},$latency;
272
273             if( !defined $$worker{hosts}{$host} ) {
274                undef @{"int-$work-$host"};
275                undef %{"int-$work-$host"};
276                ${"int-$work-$host"}{latency} = \@{"int-$work-$host"};
277                $$worker{hosts}{$host} = \%{"int-$work-$host"};
278             }
279             $hoster = $$worker{hosts}{$host};
280             push @{$$hoster{latency}},$latency;
281          }
282       }
283    }
284    undef $fh;
285 }
286
287 # If the last log file ends before switch to the current day,
288 # output the last days data
289 if( $logtime < $curdate ) {
290    &IntervalStats(\%Global,\%Interval,$previnterval*300);
291    &DailyStats($processdate,\%Global);
292 }
293
294 exit;
295
296 sub IntervalStats($$$) {
297    my $global = $_[0];
298    my $data = $_[1];
299    my $interval = $_[2];
300
301    ($count,$median,$mean,$stddev,$min,$max) = &CalcStats($$data{latency});
302    $$global{interval} .= "$interval $count $median $mean $stddev $min $max $$data{client_gone} $$data{tomcat_full}\n";
303
304    foreach $work ( keys %{$$data{workers}} ) {
305       $worker = $$data{workers}{$work};
306       $gworker = $$global{workers}{$work};
307       ($count,$median,$mean,$stddev,$min,$max) = &CalcStats($$worker{latency});
308       $$gworker{interval} .= "$interval $count $median $mean $stddev $min $max\n";
309       foreach $host ( keys %{$$worker{hosts}} ) {
310          $hoster = $$worker{hosts}{$host};
311          $ghoster = $$gworker{hosts}{$host};
312          ($count,$median,$mean,$stddev,$min,$max) = &CalcStats($$hoster{latency});
313          $$ghoster{interval} .= "$interval $count $median $mean $stddev $min $max\n";
314       }
315    }
316 }
317
318 sub DailyStats($$) {
319    my $date = $_[0];
320    my $data = $_[1];
321
322    &SaveStats($data,$date,"","global");
323    &SaveFile($$data{interval},$date,"","daily");
324    foreach $work ( keys %{$$data{workers}} ) {
325       $worker = $$data{workers}{$work};
326       &SaveStats($worker,$date,$work,"global");
327       &SaveFile($$worker{interval},$date,$work,"daily");
328       foreach $host ( keys %{$$worker{hosts}} ) {
329          $hoster = $$worker{hosts}{$host};
330          &SaveStats($hoster,$date,"$work/$host","global");
331          &SaveFile($$hoster{interval},$date,"$work/$host","daily");
332          $pagedata = "";
333          foreach $page ( sort keys %{$$hoster{pages}} ) {
334             $pager = $$hoster{pages}{$page};
335             ($count,$median,$mean,$stddev,$min,$max) = &CalcStats($pager);
336             $pagedata .= "$page $count $median $mean $stddev $min $max\n";
337          }
338          $pagedata .= $$data{errors};
339          &SaveFile($pagedata,$date,"$work/$host","request");
340       }
341    }
342 }
343
344 sub CalcStats($) {
345    my $data = $_[0];
346
347    $stats = Statistics::Descriptive::Full->new();
348    $stats->add_data(@{$data});
349    $median = $stats->median();
350    $mean = $stats->mean();
351    $stddev = $stats->standard_deviation();
352    $max = $stats->max();
353    $min = $stats->min();
354    $count = $stats->count();
355    return ($count,$median,$mean,$stddev,$min,$max);
356 }
357
358 sub SaveStats($$$$) {
359    my $data = $_[0];
360    my $date = $_[1];
361    my $dir = $_[2];
362    my $file = $_[3];
363
364    if( length($dir) > 0 ) {
365       $dir = "$archivedir/$dir";
366    } else {
367       $dir = $archivedir;
368    }
369    mkdir "$dir",0755;
370
371    $outfile = "$dir/${file}.data";
372
373    ($count,$median,$mean,$stddev,$min,$max) = &CalcStats($$data{latency});
374
375    open DATA, ">>$outfile" or die $!;
376    print DATA "$date $count $median $mean $stddev $min $max";
377    print DATA " $$data{client_gone} $$data{tomcat_full}" if defined $$data{tomcat_full};
378    print DATA "\n";
379    close DATA;
380 }
381
382 sub SaveFile($$$$) {
383    my $data = $_[0];
384    my $date = $_[1];
385    my $dir = $_[2];
386    my $file = $_[3];
387    my ($day, $mon, $year);
388
389    ($day, $mon, $year) = (localtime($date))[3..5];
390    $year += 1900;
391    $mon++;
392    $mon = "0$mon" if $mon < 10;
393    $day = "0$day" if $day < 10;
394    $file = "$year-$mon-$day-$file";
395
396    if( length($dir) > 0 ) {
397       $dir = "$archivedir/$dir";
398    } else {
399       $dir = $archivedir;
400    }
401    mkdir "$dir",0755;
402
403    $outfile = "$dir/${file}.data";
404
405    open DATA, ">>$outfile" or die $!;
406    print DATA $data;
407    close DATA;
408 }