prstats.pl revision 86309
1#!/usr/bin/perl -w 2#- 3# Copyright (c) 2001 Dag-Erling Co�dan Sm�rgrav 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions 8# are met: 9# 1. Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer 11# in this position and unchanged. 12# 2. Redistributions in binary form must reproduce the above copyright 13# notice, this list of conditions and the following disclaimer in the 14# documentation and/or other materials provided with the distribution. 15# 3. The name of the author may not be used to endorse or promote products 16# derived from this software without specific prior written permission. 17# 18# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28# 29# $FreeBSD: head/tools/tools/prstats/prstats.pl 86309 2001-11-12 23:59:56Z des $ 30# 31 32use strict; 33use Data::Dumper; 34use Fcntl; 35use POSIX qw(isatty mktime strftime tzset); 36use vars qw($TTY %MONTH %PR @EVENTS %STATE %CATEGORY @COUNT); 37 38%MONTH = ( 39 'Jan' => 1, 40 'Feb' => 2, 41 'Mar' => 3, 42 'Apr' => 4, 43 'May' => 5, 44 'Jun' => 6, 45 'Jul' => 7, 46 'Aug' => 8, 47 'Sep' => 9, 48 'Oct' => 10, 49 'Nov' => 11, 50 'Dec' => 12, 51); 52 53sub GNATS_DIR { "/home/gnats" } 54sub GNATS_TZ { "America/Los_Angeles" } 55sub DATFILE { "/tmp/prstats.dat" } 56sub GNUPLOT { "|/usr/local/bin/gnuplot /dev/stdin" } 57sub TIMEFMT { "%Y-%m-%d/%H:%M:%S" } 58 59sub parse_date($) { 60 my $date = shift; # Date to parse 61 62 my $year; 63 my $month; 64 my $day; 65 my $hour; 66 my $minute; 67 my $second; 68 69 $date =~ s/\s+/ /g; 70 $date =~ s/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\w*\s*//; 71# $date =~ s/mi$/96/; # XXX bin/129 72# $date =~ s/ed$/96/; # XXX bin/184 73# $date =~ s/ug$/96/; # XXX bin/323 74# $date =~ s/-u$/96/; # XXX bin/636 75# $date =~ s/ow$/96/; # XXX bin/657 76# $date =~ s/:f$/96/; # XXX gnu/1047 77# $date =~ s/rs$/96/; # XXX bin/1132 78# $date =~ s/ca$/96/; # XXX bin/1158 79# $date =~ s/s:$/96/; # XXX gnu/1209 80# $date =~ s/:c$/96/; # XXX bin/1215 81# $date =~ s/io$/96/; # XXX bin/1279 82# $date =~ s/d-$/96/; # XXX bin/1385 83# $date =~ s/bs$/96/; # XXX bin/1403 84# $date =~ s/-b$/96/; # XXX gnu/1471 85# $date =~ s/sd$/96/; # XXX gnu/1472 86# $date =~ s/al$/96/; # XXX bin/1785 87# $date =~ s/eb$/96/; # XXX bin/1823 88# $date =~ s/it$/96/; # XXX bin/1832 89# $date =~ s/re$/96/; # XXX gnu/1835 90# $date =~ s/ri$/97/; # XXX gnu/3247 91 if ($date =~ m/^(\w{3}) (\d\d?) (\d\d):(\d\d):(\d\d) [A-Z ]*(\d{4})$/) { 92 ($month, $day, $hour, $minute, $second, $year) = 93 ($1, $2, $3, $4, $5, $6); 94 } else { 95 die("Unrecognized date format: $date\n"); 96 } 97 defined($month = $MONTH{$month}) 98 or die("Invalid month: $month\n"); 99 return mktime($second, $minute, $hour, $day, $month - 1, $year - 1900); 100} 101 102sub scan_pr($) { 103 my $fn = shift; # File name 104 105 local *FILE; # File handle 106 my $pr = {}; # PR hash 107 108 sysopen(FILE, $fn, O_RDONLY) 109 or die("$fn: open(): $!\n"); 110 while (<FILE>) { 111 if (m/^>([A-Za-z-]+):\s+(.*?)\s*$/o || 112 m/^(State-Changed-[A-Za-z-]+):\s+(.*?)\s*$/o) { 113 $pr->{lc($1)} = $2; 114 } 115 } 116 117 exists($PR{$pr->{'number'}}) 118 and die("$fn: PR $pr->{'number'} already exists\n"); 119 120 if ($TTY) { 121 print(" "x40, "\r", scalar(keys(%PR)), 122 " $pr->{'category'}/$pr->{'number'} "); 123 } 124 125 foreach ('arrival-date', 'closed-date', 'last-modified', 126 'state-changed-when') { 127 if (defined($pr->{$_}) && length($pr->{$_})) { 128 $pr->{$_} = parse_date($pr->{$_}); 129 } 130 } 131 132 $pr->{'_created'} = $pr->{'arrival-date'}; 133 if ($pr->{'state'} eq 'closed') { 134 $pr->{'_closed'} = $pr->{'closed-date'} || $pr->{'state-changed-when'}; 135 $pr->{'_closed_by'} = $pr->{'state-changed-by'}; 136 } 137 138# $PR{$pr->{'number'} = $pr; 139 $PR{$pr->{'number'}} = { 140 #'category' => $pr->{'category'}, 141 #'number' => $pr->{'number'}, 142 #'responsible' => $pr->{'responsible'}, 143 'created' => $pr->{'created'}, 144 'closed' => $pr->{'closed'}, 145 #'closer' => $pr->{'_closed_by'}, 146 }; 147 push(@EVENTS, [ $pr->{'_created'}, +1 ]); 148 push(@EVENTS, [ $pr->{'_closed'}, -1 ]) 149 if defined($pr->{'_closed'}); 150 ++$STATE{$pr->{'state'}}; 151 ++$CATEGORY{$pr->{'category'}}; 152} 153 154sub scan_recurse($); 155sub scan_recurse($) { 156 my $dn = shift; # Directory name 157 158 local *DIR; # Directory handle 159 my $entry; # Entry 160 161 opendir(DIR, $dn) 162 or die("$dn: opendir(): $!\n"); 163 while ($entry = readdir(DIR)) { 164 next if ($entry eq '.' || $entry eq '..'); 165 if (-d "$dn/$entry") { 166 scan_recurse("$dn/$entry"); 167 } elsif ($entry =~ m/^\d+$/) { 168 eval { 169 scan_pr("$dn/$entry"); 170 }; 171 } 172 } 173 closedir(DIR); 174} 175 176sub count_prs() { 177 178 my $pr; # Iterator 179 my @events; # Creations or closures 180 my $event; # Iterator 181 my $count; # PR count 182 183 print(int(@EVENTS), " events\n"); 184 @COUNT = ( [ 0, 0 ] ); 185 foreach $event (sort({ $a->[0] <=> $b->[0] } @EVENTS)) { 186 if ($event->[0] == $COUNT[-1]->[0]) { 187 $COUNT[-1]->[1] += $event->[1]; 188 } else { 189 push(@COUNT, [ $event->[0], $COUNT[-1]->[1] + $event->[1] ]); 190 } 191 } 192 if (@COUNT > 1) { 193 $COUNT[0]->[0] = $COUNT[1]->[0] - 1; 194 unshift(@COUNT, [ 0, 0 ]); 195 } 196} 197 198sub gnuplot(@) { 199 my @commands = @_; # Commands 200 201 my $pid; # Child PID 202 local *PIPE; # Pipe 203 204 open(PIPE, &GNUPLOT) 205 or die("fork(): $!\n"); 206 print(PIPE join("\n", @commands, "")); 207 close(PIPE); 208 if ($? & 0x7f) { 209 die("gnuplot caught a signal " . ($? & 0x7f) . "\n"); 210 } elsif ($?) { 211 die("gunplot returned exit code " . ($? >> 8) . "\n"); 212 } 213} 214 215sub write_dat_file($) { 216 my $fn = shift; # File name 217 218 local *FILE; # File handle 219 my $datum; # Iterator 220 221 sysopen(FILE, $fn, O_RDWR|O_CREAT|O_TRUNC, 0640) 222 or die("$fn: open(): $!\n"); 223 foreach $datum (@COUNT) { 224 print(FILE strftime(&TIMEFMT, localtime($datum->[0])), 225 " ", $datum->[1], 226 " ", $COUNT[-1]->[1], 227 "\n"); 228 } 229 close(FILE); 230} 231 232sub graph_open_prs($$$$$) { 233 my $datfn = shift; # Data file name 234 my $fn = shift; # File name 235 my $start = shift; # Starting date 236 my $end = shift; # Ending date 237 my $title = shift; # Title 238 239 my $tickfmt; # Tick format 240 my $timefmt; # Time format 241 242 if ($end - $start > 86400 * 30) { 243 $tickfmt = "%Y-%m-%d"; 244 } else { 245 $tickfmt = "%m-%d"; 246 } 247 $start = strftime(&TIMEFMT, localtime($start)); 248 $end = strftime(&TIMEFMT, localtime($end)); 249 $timefmt = &TIMEFMT; 250 gnuplot(" 251set term png small color 252set xdata time 253set timefmt '$timefmt' 254set data style line 255set grid 256set output '$fn' 257set format x '$tickfmt' 258set xrange ['$start':'$end'] 259set yrange [0:*] 260set title '$title' 261plot '$datfn' using 1:2 title 'Open PRs' 262"); 263} 264 265MAIN:{ 266 my $now; # Current time 267 268 $| = 1; 269 $TTY = isatty(*STDOUT); 270 271 # Perl lacks strptime(), and its mktime() doesn't accept a 272 # timezone argument, so we set our local timezone to that of the 273 # FreeBSD cluster and use localtime() instead. 274 $ENV{'TZ'} = &GNATS_TZ; 275 tzset(); 276 277 if (@ARGV) { 278 foreach (@ARGV) { 279 scan_recurse(join('/', &GNATS_DIR, $_)); 280 } 281 } else { 282 scan_recurse(&GNATS_DIR); 283 } 284 if ($TTY) { 285 print("\r", scalar(keys(%PR)), " problem reports scanned\n"); 286 } 287 288 count_prs(); 289 write_dat_file(&DATFILE); 290 $now = time(); 291 graph_open_prs(&DATFILE, "week.png", $now - (86400 * 7) + 1, $now, 292 "Open FreeBSD problem reports (week view)"); 293 graph_open_prs(&DATFILE, "month.png", $now - (86400 * 30) + 1, $now, 294 "Open FreeBSD problem reports (month view)"); 295 graph_open_prs(&DATFILE, "year.png", $now - (86400 * 365) + 1, $now, 296 "Open FreeBSD problem reports (year view)"); 297 graph_open_prs(&DATFILE, "ever.png", $COUNT[1]->[0], $now, 298 "Open FreeBSD problem reports (project history)"); 299 graph_open_prs(&DATFILE, "drive.png", mktime(0, 0, 0, 29, 4, 101), $now, 300 "Open FreeBSD problem reports (drive progress)"); 301 #unlink(&DATFILE); 302} 303