133 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
		
		
			
		
	
	
			133 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
| 
								 | 
							
								#!/usr/bin/perl
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use strict;
							 | 
						||
| 
								 | 
							
								use RRDs;
							 | 
						||
| 
								 | 
							
								use IO::Socket::INET;
							 | 
						||
| 
								 | 
							
								use Getopt::Long;
							 | 
						||
| 
								 | 
							
								use Danga::Daemon;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# parse out command line options
							 | 
						||
| 
								 | 
							
								my ($server, $port, $rrd_path);
							 | 
						||
| 
								 | 
							
								my $res = GetOptions(
							 | 
						||
| 
								 | 
							
								    "server=s"   => \$server,
							 | 
						||
| 
								 | 
							
								    "port=i"     => \$port,
							 | 
						||
| 
								 | 
							
								    "rrd-path=s" => \$rrd_path,
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								die "Unable to parse options.\n" unless $res;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# make sure we have a minimum set of options
							 | 
						||
| 
								 | 
							
								die "Please specify a path to access the RRD files in --rrd-path.\n"
							 | 
						||
| 
								 | 
							
								    unless $rrd_path;
							 | 
						||
| 
								 | 
							
								die "RRD path $rrd_path does not exist or is not a directory.\n"
							 | 
						||
| 
								 | 
							
								    unless -e $rrd_path && -d $rrd_path;
							 | 
						||
| 
								 | 
							
								die "Requires --server (and a --port if it's not 9107).\n"
							 | 
						||
| 
								 | 
							
								    unless $server;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# setup more internal variables
							 | 
						||
| 
								 | 
							
								$port ||= 9107;
							 | 
						||
| 
								 | 
							
								my $spud_server = "$server:$port";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# connect to local spud server
							 | 
						||
| 
								 | 
							
								my $sock = IO::Socket::INET->new(PeerAddr => $spud_server)
							 | 
						||
| 
								 | 
							
								    or die "Can't connect to server: $!\n";
							 | 
						||
| 
								 | 
							
								print $sock "sub *\r\n";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# data storage
							 | 
						||
| 
								 | 
							
								my $shutdown = 0;
							 | 
						||
| 
								 | 
							
								my $lastpurge = time();
							 | 
						||
| 
								 | 
							
								my (%data, %exists);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# now we want to daemonize
							 | 
						||
| 
								 | 
							
								Danga::Daemon::daemonize(
							 | 
						||
| 
								 | 
							
								    \&worker,
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        interval => 1,
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# process updates as they come in
							 | 
						||
| 
								 | 
							
								sub worker {
							 | 
						||
| 
								 | 
							
								    # try to reconnect?
							 | 
						||
| 
								 | 
							
								    debug("Worker start...");
							 | 
						||
| 
								 | 
							
								    unless ($sock) {
							 | 
						||
| 
								 | 
							
								        $sock = IO::Socket::INET->new(PeerAddr => $spud_server)
							 | 
						||
| 
								 | 
							
								            or return;
							 | 
						||
| 
								 | 
							
								        print $sock "sub *\r\n";
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # loop and process what they're giving us
							 | 
						||
| 
								 | 
							
								    debug("Beginning main loop...");
							 | 
						||
| 
								 | 
							
								    while (<$sock>) { 
							 | 
						||
| 
								 | 
							
								        my $line = $_;
							 | 
						||
| 
								 | 
							
								        $line =~ s/[\r\n]+$//;
							 | 
						||
| 
								 | 
							
								        next unless $line =~ /^set\s+(\d+)\s+(\S+)\s+(\d+)$/i;
							 | 
						||
| 
								 | 
							
								        my ($what, $time, $dpoint) = ($2, $1, $3);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # fix up the colons in $what to be underscores :/
							 | 
						||
| 
								 | 
							
								        $what =~ s/:/_/g;
							 | 
						||
| 
								 | 
							
								        push @{$data{$what} ||= []}, [ $time, $dpoint ];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # and if necessary, do a purge
							 | 
						||
| 
								 | 
							
								        if ($Danga::Daemon::stop || ($lastpurge + 60 < time())) {
							 | 
						||
| 
								 | 
							
								            do_purge();
							 | 
						||
| 
								 | 
							
								            $lastpurge = time();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # done if we were told to shutdown
							 | 
						||
| 
								 | 
							
								        return if $Danga::Daemon::stop;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # got undef from $sock... so undef $sock itself
							 | 
						||
| 
								 | 
							
								    debug("Oops, lost connection to SPUD server...");
							 | 
						||
| 
								 | 
							
								    $sock = undef;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								sub do_purge {
							 | 
						||
| 
								 | 
							
								    debug("** Beginning purge...");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # dump to file
							 | 
						||
| 
								 | 
							
								    my ($keys, $count);
							 | 
						||
| 
								 | 
							
								    foreach my $what (keys %data) {
							 | 
						||
| 
								 | 
							
								        # update this particular file
							 | 
						||
| 
								 | 
							
								        $keys++;
							 | 
						||
| 
								 | 
							
								        my $fn = "$rrd_path/$what";
							 | 
						||
| 
								 | 
							
								        unless ($exists{$fn}) {
							 | 
						||
| 
								 | 
							
								            unless (-e $fn) {
							 | 
						||
| 
								 | 
							
								                RRDs::create($fn,
							 | 
						||
| 
								 | 
							
								                    "--start", "-120",            # up to two minutes ago... should be enough
							 | 
						||
| 
								 | 
							
								                    "--step", "5",                # data is expected to be 5 seconds apart
							 | 
						||
| 
								 | 
							
								                    "DS:val:GAUGE:10:U:U",        # all data in 'val', 10 seconds before UNKNOWN
							 | 
						||
| 
								 | 
							
								                    'RRA:AVERAGE:0.5:1:1440',     # past 2 hours of data
							 | 
						||
| 
								 | 
							
								                    'RRA:AVERAGE:0.5:60:288',     # 5 minute averages for the day
							 | 
						||
| 
								 | 
							
								                    'RRA:AVERAGE:0.5:360:336',    # 30 minute averages for a week
							 | 
						||
| 
								 | 
							
								                    'RRA:AVERAGE:0.5:720:720',    # 1 hour averages for 30 days
							 | 
						||
| 
								 | 
							
								                    'RRA:AVERAGE:0.5:17280:365',  # 1 day averages for a year
							 | 
						||
| 
								 | 
							
								                    'RRA:AVERAGE:0.5:120960:520', # 1 week averages for 10 years
							 | 
						||
| 
								 | 
							
								                );
							 | 
						||
| 
								 | 
							
								                if (my $err = RRDs::error) {
							 | 
						||
| 
								 | 
							
								                    die "Error creating RRD file: $err\n";
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            $exists{$fn} = 1;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # now pipe out the updates
							 | 
						||
| 
								 | 
							
								        my @updates;
							 | 
						||
| 
								 | 
							
								        foreach my $row (@{$data{$what}}) {
							 | 
						||
| 
								 | 
							
								            $count++;
							 | 
						||
| 
								 | 
							
								            push @updates, "$row->[0]:$row->[1]";
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        RRDs::update($fn, @updates);
							 | 
						||
| 
								 | 
							
								        #debug("\t$what: ", scalar(@updates), " written");
							 | 
						||
| 
								 | 
							
								        if (my $err = RRDs::error) {
							 | 
						||
| 
								 | 
							
								            warn "WARNING: Error updating $fn: $err\n";
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    debug("\tupdates processed: $count");
							 | 
						||
| 
								 | 
							
								    debug("\tfiles touched: $keys");
							 | 
						||
| 
								 | 
							
								    debug("\tratio: %.2f updates per file\n", ($count / $keys));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # and now that we're done with that old data ...
							 | 
						||
| 
								 | 
							
								    %data = ();
							 | 
						||
| 
								 | 
							
								}
							 |