chiark / gitweb /
wip rrd updater
[rrd-graphs.git] / newstailer
1 #!/usr/bin/perl -w
2
3 use strict qw(refs vars);
4 use POSIX;
5
6 use IO::Handle;
7 use IO::File;
8 use File::Tail;
9 use Parse::Syslog;
10 use RRDs;
11
12 our %dohosts;
13 our $outpfx= './';
14 our $sep= '_';
15 our $debug= 0;
16
17 open DEBUG, ">/dev/null" or die $!;
18
19 while (@ARGV && $ARGV[0] =~ m/^\-./) {
20     $_= shift @ARGV;
21     last if $_ eq '--';
22     while (m/^\-./) {
23         if (s/^-h//) { $dohosts{$_}=1; last; }
24         elsif (s/^-O//) { $outpfx=$_; last; }
25         elsif (s/^-s//) { $sep=$_; last; }
26         elsif (s/^-D/-/) { $debug++; }
27         else { die "bad option $_"; }
28     }
29 }
30
31 if (!keys %dohosts) {
32     my ($sysname, $nodename) = POSIX::uname();
33     die unless defined $nodename;
34     $dohosts{$nodename}= 1;
35 }
36
37 die unless @ARGV;
38 my $totail= pop @ARGV;
39
40 if ($debug) { open DEBUG, ">& STDERR" or die $!; }
41
42 our @fields_in= qw(seconds accepted refused rejected duplicate
43                    accepted_size duplicate_size);
44 our $tmpl_in= join ':',@fields_in;
45 our %details= (
46     'in' => {
47         Step => 60,
48         DstArguments => "7200:0:U",
49         Xff => 0.5,
50         Archives => [ [ 3600*4,           60 ],   # 4hr, 1min resolution
51                       [ 3600*25,         180 ],   # 25h, 3min resolution
52                       [ 86400*14*5,      3600 ],  # 14wks, 1hr resolution
53                       [ 86400*370*2, 3600*24 ] ], # 2yr+, 1day resolution
54     }
55 );
56
57 our ($time,$host,$peer,$conn,$stats);
58
59 sub path_rrd ($) {
60     my ($inout) = @_;
61     return "${outpfx}${host}${sep}${peer}_${inout}.rrd";
62 }
63
64 sub perhaps_create_rrd ($$) {
65     my ($inout, $fields) = @_;
66     my $path= path_rrd($inout);
67     return if stat $path;
68     $!==&ENOENT or die "$path $!";
69     my $details= $details{$inout};
70
71     my @sargs= ($path, '--start','now-1y', '--step',$details->{Step});
72     my @largs;
73     push @largs, "DS:$_:ABSOLUTE:$details->{DstArguments}" foreach @$fields;
74     foreach (@{ $details->{Archives} }) {
75         my ($whole,$reso) = @$_;
76         my $steps= $reso / $details->{Step};
77         my $rows= $whole / $reso;
78         push @largs, "RRA:AVERAGE:$details->{Xff}:$steps:$rows";
79     } 
80     print DEBUG join("            \\\n  ", "creating @sargs", @largs),"\n";
81     RRDs::create(@sargs,@largs);
82     my $err= RRDs::error;
83     die "$err [@sargs @largs]" if defined $err;
84 }
85
86 sub update_rrd ($$$) {
87     my ($inout,$tmpl,$vals) = @_;
88     my $path= path_rrd($inout);
89     my @args= ($path, '--template',$tmpl, join(':',$time,@$vals));
90     print DEBUG "update @args\n";
91     RRDs::update(@args);
92     my $err= RRDs::error;
93     die "$err [@args]" if defined $err;
94 }
95
96 our %in_conns;
97
98 sub inbound_connected () {
99     #print "$host $peer $conn START\n";
100     perhaps_create_rrd('in',\@fields_in);
101     $in_conns{$host,$peer,$conn} = [ (0) x @fields_in ];
102 }
103 sub inbound_closed () {
104     #print "$host $peer $conn STOP\n";
105     delete $in_conns{$host,$peer,$conn};
106 }
107 sub inbound_stats () {
108     $_= $stats.' ';
109     my %s;
110     s/(?<=[a-z]) (?=[a-z])/_/g;
111     my $hpc= $in_conns{$host,$peer,$conn};
112     if (!$hpc) {
113         perhaps_create_rrd('in',\@fields_in);
114         $in_conns{$host,$peer,$conn}= $hpc= [ (undef) x @fields_in ];
115     }
116     while (s/^([a-z_]+) (\d+)\s//) { $s{$1}= $2; }
117     my @v;
118     foreach my $f (@fields_in) {
119         my $this= $s{$f};
120         if (!defined $this) {
121             delete $hpc->[@v];
122             push @v, 'U';
123             next;
124         }
125         my $last= $hpc->[@v];
126         $hpc->[@v]= $this;
127         push @v, defined($last) ? $this - $last : 'U';
128     }
129     update_rrd('in',$tmpl_in,\@v);
130 }
131
132 sub outbound_stats () {
133     print "$host $peer OUT $stats\n";
134 }
135
136 sub run ($) {
137     my ($object) = @_;
138     my $parser= new Parse::Syslog $object, repeat=>0, arrayref=>1;
139     my $host_re= '[-.0-9a-z]+';
140     my $conn_re= '[1-9]\d{0,5}';
141     my ($process,$pid,$msg,$cc,$sl);
142     while ($sl= $parser->next) {
143         ($time,$host,$process,$pid,$msg) = @$sl;
144         next unless exists $dohosts{$host};
145         #print join("|", map { defined($_) ? $_ : "<undef>" } @$sl), "\n";
146         if ($process eq 'innd' && !defined $pid) {
147             if (($peer,$conn) = $msg =~ m/^($host_re) connected ($conn_re)$/) {
148                 inbound_connected()
149             } elsif (($peer,$conn,$cc,$stats) = $msg =~
150             m/^($host_re):($conn_re) (closed|checkpoint) (seconds .*)$/) {
151                 inbound_stats();
152                 inbound_closed() if $cc eq 'closed';
153             }
154         } elsif ($process eq 'innduct') {
155             if (($peer,$stats) = $msg =~
156                 m/^($host_re)\| (?:completed|processed) \S+ (read=.*)$/) {
157                 outbound_stats();
158             }
159         }
160     }
161 }
162
163 #seconds (\d+) accepted (\d+) refused (\d+) rejected (\d+) duplicate (\d+) accepted size (\d+) duplicate size (\d+) 
164
165         
166 foreach my $staticpath (@ARGV) {
167     if ($staticpath =~ m/\.gz$/) {
168         my $fh= new IO::Handle;
169         open $fh, '-|', 'gunzip', '--', $staticpath or die $!;
170         run($fh);
171         !$fh->error or die "$staticpath $!";
172         $!=0;$?=0; close $fh or die "$staticpath $! $?";
173     } else {
174         my $fh= new IO::File $staticpath, '<' or die $!;
175         run($staticpath);
176         !$fh->error or die "$staticpath $!";
177         close $fh or die "$staticpath $!";
178     }
179 }
180
181 exit 0 if $totail eq '';
182
183 my $tailer= new File::Tail name=>$totail,
184     interval=>60, adjustafter=>2, ignore_nonexistant=>1, tail=>-1
185     or die "$totail $!";
186
187 run($tailer);
188
189 die "huh?";