JPRED-2 Initial commit of software for the Jpred website (some files excluded due...
[jpred.git] / websoft / bin / arco_stats.pl
1 #!/usr/bin/perl
2
3 =head1 NAME
4
5 arco_stats.pl - script to collate SGE job stats from ARCo
6
7 =cut
8
9 use strict;
10 use warnings;
11
12 use Getopt::Long;
13 use Pod::Usage;
14 use File::Basename;
15 use GD::Graph::bars;
16 use DBI;
17
18 # path for nicer fonts for the graph labels
19 my $FONTPATH = "/homes/www-jpred/live/public_html/fonts/";
20 my @month    = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
21 my $user     = 'www-jpred';
22 my $out;
23 my $xDim     = 700;
24 my $yDim     = 400;
25 my $showPlot = 0;
26 my $showCSV  = 1;
27 my $runStats = 0;
28 my $quiet    = 0;
29 my $help;
30 my $man;
31
32 GetOptions(
33   'user=s'      => \$user,
34   'x=i'         => \$xDim,
35   'y=i'         => \$yDim,
36   'plot!'       => \$showPlot,
37   'csv!'        => \$showCSV,
38   'run-stats=i' => \$runStats,
39   'out=s'       => \$out,
40   'quiet'       => \$quiet,
41   'man'         => \$man,
42   'help|?'      => \$help,
43 ) or pod2usage();
44
45 pod2usage( -verbose => 2 ) if ($man);
46 pod2usage( -verbose => 1 ) if ($help);
47
48 my $dbh = DBI->connect( "dbi:Pg:host=postgres.compbio.dundee.ac.uk;dbname=arco", 'account', 'saffron' ) or die "ERROR - can't connect to db: ", $DBI::errstr;
49
50 # generate output prefix name unless already specified
51 $out = "${user}_stats" unless ($out);
52
53 # find out the current month and year. Correct year to real 4-digit number.
54 my ( $currDate, $currMon, $currYr ) = ( localtime() )[ 3 .. 5 ];
55 $currYr += 1900;
56
57 my $monthly = get_monthly($dbh);
58 warn "Warning - no monthly data found\n" unless ( scalar @{$monthly} );
59 my $daily = get_daily( $dbh, $currMon, $currYr );
60 warn "Warning - no daily data found for $month[$currMon] $currYr\n" unless ( scalar @{$daily} );
61 print_run_stats( $dbh, $runStats ) if ($runStats);
62 $dbh->disconnect;
63
64 if ($showPlot) {
65   print "Drawing plots...\n" unless ($quiet);
66   draw_graph( $monthly, 'Month', "Monthly Totals", "${out}_monthly.png" );
67   draw_graph( $daily, 'Date', "Daily Totals for $month[$currMon]", "${out}_daily.png" );
68 }
69
70 if ($showCSV) {
71   print "Writing CSV files...\n" unless ($quiet);
72   print_data( $daily,   "${out}_daily.csv" );
73   print_data( $monthly, "${out}_monthly.csv" );
74 }
75 print "Finished!\n" unless ($quiet);
76 exit;
77
78 #########################################################################################################
79 # count the number of jobs per month since records began
80 sub get_monthly {
81   my $dbh = shift;
82
83   # retrieve all jobs run and give the epoch time they started.
84   # has a kludge to remove some extraneous run info for Aug 2008
85   my $sth = $dbh->prepare(
86     "SELECT EXTRACT(EPOCH FROM start_time)  AS epoch
87                             FROM view_accounting 
88                             WHERE username = '$user'
89                             AND submission_time > '2008-sep-01'::Date
90                             ORDER BY epoch ASC"
91   ) or die "ERROR - unable to prepare SELECT statement: ", $dbh->errstr();
92
93   $sth->execute();
94
95   # foreach epoch time retrieve month and year
96   # and count the number of jobs run per month
97   my %data;
98   while ( my @row = $sth->fetchrow_array ) {
99     my ( $mnth, $year ) = ( localtime( $row[0] ) )[ 4 .. 5 ];
100     $year += 1900;
101     $data{$year}{$mnth}++;
102   }
103
104   # convert month counts into data structure readable by GD::Graph
105   my $i = 0;
106   my @sortedData;
107   foreach my $year ( sort keys %data ) {
108     foreach my $mon ( sort { $a <=> $b } keys %{ $data{$year} } ) {
109       my $date = sprintf "%s %02d", $month[$mon], $year - 2000;    # convert into 2-digit version (not Y2K compatible)
110       $sortedData[0][$i] = $date;
111       $sortedData[1][$i] = $data{$year}{$mon};
112       ++$i;
113     }
114   }
115   return ( \@sortedData );
116 }
117
118 #########################################################################################################
119 # count the number of jobs per day of current month
120 sub get_daily {
121   my $dbh  = shift;
122   my $mnth = shift;
123   my $year = shift;
124
125   # retrieve the number of jobs run per day during this month
126   my $sth = $dbh->prepare(
127     "SELECT DISTINCT(CAST(start_time AS DATE)) AS start_date, COUNT(CAST(start_time AS DATE)) 
128                             FROM view_accounting 
129                             WHERE username = '$user' 
130                             AND start_time >= '$year-$month[$mnth]-01'::Date 
131                             GROUP BY start_date
132                             ORDER BY start_date ASC"
133   ) or die "ERROR - unable to prepare SELECT statement: ", $dbh->errstr();
134
135   $sth->execute();
136
137   # generate data structure for GD::Graph with day counts
138   my $i = 0;
139   my @data;
140   while ( my @row = $sth->fetchrow_array ) {
141     # $row[0] is the date
142     # $row[1] is the count
143     my $date = ( split( /-/, $row[0] ) )[2];
144
145     #print "$date: $row[1]\n";
146     $data[0][$i] = $date;
147     $data[1][$i] = $row[1];
148     ++$i;
149   }
150   $sth->finish();
151
152   return ( \@data );
153 }
154
155 #########################################################################################################
156 # print out specific stats relating to run time, queuing time and exit status
157 sub print_run_stats {
158   my ($dbh) = shift;
159   my $days = shift;
160
161   # get the date n days ago
162   my $secsInDays = 86400 * $days;              # num seconds in a day * number of days
163   my $daysAgo    = ( time() - $secsInDays );
164   my ( $date, $mnth, $year ) = ( localtime($daysAgo) )[ 3 .. 5 ];
165   $year += 1900;
166
167   #print "$days days ago was: $year-$month[$mnth]-$date\n";
168
169   # retrieve run-specific stats for the user
170   my $sth = $dbh->prepare(
171     "SELECT  wallclock_time, maxvmem, exit_status, EXTRACT(EPOCH FROM start_time - submission_time) AS wait_time
172                             FROM view_accounting 
173                             WHERE username = '$user' 
174                             AND submission_time >= '$year-$month[$mnth]-$date'::Date"
175   ) or die "ERROR - unable to prepare SELECT statement: ", $dbh->errstr();
176   $sth->execute() or die;
177   my $nRows = $sth->rows();
178
179   # open stats file
180   my $file = 'run_stats.csv';
181   open( my $OUT, ">>", $file ) or die "ERROR - unable to open '$file' for write: ${!}\nDied";
182   print $OUT "$currYr-$month[$currMon]-$currDate,$nRows,";
183
184   # if no jobs run in time frame warn, set everything to zero and return
185   if ( $nRows == 0 ) {
186     warn "Warning - no jobs found for user '$user' in the last $days days\n";
187     print $OUT "0,0,0,0,0\n";
188     close($OUT);
189     return;
190   }
191
192   # collate useful data
193   my %data;
194   while ( my @row = $sth->fetchrow_array ) {
195     $data{runtime} += $row[0];
196     $data{vmem}    += $row[1];
197     if ( $row[2] > 0 ) {
198       if ( $row[2] == 4 ) {
199         $data{timeouts}++;
200       } else {
201         $data{errors}++;
202       }
203     }
204     $data{waittime} += $row[3];
205
206   }
207
208   # define potentially undefined variables
209   $data{timeouts} = 0 unless ( $data{timeouts} );
210   $data{errors}   = 0 unless ( $data{errors} );
211
212   # write out data to file
213   foreach my $k qw(runtime vmem waittime) {
214     printf $OUT "%.0f,", $data{$k} / $nRows;
215   }
216   print $OUT "$data{timeouts},$data{errors}\n";
217 }
218
219 #########################################################################################################
220 sub draw_graph {
221   my ( $dataref, $x_label, $title, $outFile ) = @_;
222
223   my $graph = GD::Graph::bars->new( 700, 400 );
224
225   $graph->set_title_font ( "$FONTPATH/VeraBd.ttf", 12 );
226   $graph->set_x_label_font ( "$FONTPATH/VeraBd.ttf", 8 );
227   $graph->set_y_label_font ( "$FONTPATH/VeraBd.ttf", 8 );
228   $graph->set_x_axis_font ( "$FONTPATH/Vera.ttf", 6 );
229   $graph->set_y_axis_font ( "$FONTPATH/Vera.ttf", 8 );
230   $graph->set(
231     x_label           => $x_label,
232     y_label           => 'No. Jpred Submissions',
233     title             => $title,
234     shadow_depth      => -2,
235     shadowclr         => 'lgray',
236     x_labels_vertical => 1,
237
238     # borderclrs                        => undef,
239     bar_width   => 12,
240     bar_spacing => 4
241   ) or die $graph->error;
242
243   my $gd = $graph->plot($dataref) or die $graph->error;
244
245   open( my $IMG, ">", $outFile ) or die "ERROR - unable to open '$outFile' for write: ${!}\nDied";
246   binmode $IMG;
247   print $IMG $gd->png;
248   close($IMG);
249
250 }
251
252 #########################################################################################################
253 sub print_data {
254   my $data = shift;
255   my $file = shift;
256
257   if ( !scalar @{$data} ) {
258     warn "Warning - no data to print out. Nothing to do.\n";
259     return;
260   }
261
262   my $total = scalar @{ $data->[0] };
263
264   open( my $OUT, ">", $file ) or die "ERROR - unable to open '$file' for write: ${!}\nDied";
265   print $OUT "Date,nRuns\n";
266   for ( my $i = 0 ; $i < $total ; ++$i ) {
267     print $OUT "$data->[0][$i],$data->[1][$i]\n";
268   }
269 }
270
271 #########################################################################################################
272 =head1 SYNOPSIS
273
274 arco_stats.pl --user <sge_user> [--out <file_prefix> --x <pixels> --y <pixels>] [--csv] [--plot] [--run-stats <days>] [--quiet] [--man] [--help]
275
276 =head1 DESCRIPTION
277
278 Script to collate run statistics from the SGE ARCo system.
279
280 The script will retrieve all historical data for the specified, count the number of jobs run and collate them by month. For the current month, data will be broken down by day.
281
282 With no options script will get stats for the www-jpred user and output CSV formatted data only. Filenames will take the form <username>_stats_[daily|monthly].csv, unless specified with the --out switch.
283
284 =head1 OPTIONS
285
286 =over 5
287
288 =item B<--user>
289
290 Specify SGE user. [Default: www-jpred]
291
292 =item B<--out>
293
294 Ouput filename prefix.
295
296 =item B<--x>
297
298 Specific X-dimension of plot figure in pixels. [Default: 700]
299
300 =item B<--y>
301
302 Specific Y-dimension of plot figure in pixels. [Default: 400]
303
304 =item B<--csv>,B<--nocsv>
305
306 Toggle for CSV output. [Default: on]
307
308 =item B<--plot>,B<--noplot>
309
310 Toggle for plotting of data. [Default: off] 
311
312 =item B<--run-stats>
313
314 Set the number of days for collating run statistics (e.g. mean run time, mean wait time). [Default: 0]
315
316 =item B<--quiet>
317
318 Switch off progress messages. Useful if running in cron.
319
320 =item B<--help>
321
322 Brief help.
323
324 =item B<--man>
325
326 Full manpage of program.
327
328 =back
329
330 =head1 BUGS
331
332 Script assumes it won't run in the past and threfore is not Y2K compliant.
333
334 =head1 AUTHOR
335
336 Chris Cole <christian@cole.name>
337
338 =cut