#!/usr/bin/perl -w # filechangeserver - watches a file and reports howmany lines or bytes have been added # released under GPL by Paul Mansfield November 2011. # no warranty provided, no tin will be supplied labelled with a promise # to do anything, no animals should be harmed in the use of this script, # your mileage may vary, and your karma may increase. use strict; use Fcntl; use IO::Select; use IO::Socket; ############################################################################# # constants my $pidFile = '/var/run/filechangeserver.pid'; ############################################################################# # command line options # error checking? we don't need no stinkeeng error checking my $dbgLevel = 0; my $listenPort; my $tailfile; while (($#ARGV >= 0) && ($ARGV[0] eq '-d')) { ++$dbgLevel; shift; } if ( $#ARGV < 1 ) { print STDERR "Usage:\nfilechangeserver [-d] port file\n\n"; exit } $listenPort = $ARGV[0]; $tailfile = $ARGV[1]; ############################################################################# sub deletePidFile { if (!unlink "$pidFile") { print STDERR "Error, failed to delete $pidFile\n"; } } ############################################################################# sub writePidFile { open(PIDFILE, ">$pidFile") or die "Error, failed to open $pidFile for writing"; print PIDFILE $$; close PIDFILE; } ############################################################################# sub openTailFile { # use sysopen for the tail file to get nonblocking read return sysopen(TF, "$tailfile", O_RDONLY|O_NONBLOCK); } ############################################################################# ############################################################################# # main program #$SIG{HUP} = 'catchSignalHup'; # could fail in modules $SIG{HUP} = \&catchSignalHup; # best strategy $SIG{INT} = \&catchSignalInt; # best strategy openTailFile() || die "Error, failed to open $tailfile for reading"; # use sysopen for the tail file to get nonblocking read sysopen(TF, "$tailfile", O_RDONLY|O_NONBLOCK) or die "Error, failed to open $tailfile for reading"; # create a listening socket on the user specified port my $sock = new IO::Socket::INET ( LocalPort => $listenPort , Proto => 'tcp' , Listen => 1 , Reuse => 1 ); die "could not create socket: $!\n" unless $sock; # get the current file stat my ($tf_cur_dev,$tf_cur_ino,$tf_cur_mode,$tf_cur_nlink,$tf_cur_uid,$tf_cur_gid,$tf_cur_rdev,$tf_cur_size, $tf_cur_atime,$tf_cur_mtime,$tf_cur_ctime,$tf_cur_blksize,$tf_cur_blocks) = stat(TF); seek(TF, 0, 2); # seek to end of file my $oldTime = time(); my $read_set = new IO::Select(); # create handle set for reading $read_set->add($sock); # add the main socket to the set ############################################################################# # program loop writePidFile(); my $requestType = ''; my $newSock; # needs to be global so can close it on SIGINT my $httpHost = 'ERROR'; while (1) # forever { print STDERR "beginning of program infinite loop\n" if ($dbgLevel > 1); if (! ($newSock = $sock->accept()) ) # ignore a HUP { next(); } print STDERR "accepted socket connection\n" if ($dbgLevel > 1); my $newTime = time(); # take the sample immediately # don't buffer, flush immediately on writing $newSock->autoflush(); # don't block when reading from socket my $sockFlags = fcntl($newSock, F_GETFL, 0); fcntl(TF, F_SETFL, $sockFlags | O_NONBLOCK); # read the request, ignore all except the GET line which we want for the URI while(<$newSock>) # && !$blankLineFlag) { chomp; chop if (substr($_, length($_) - 1, 1) eq "\r"); print STDERR ">>$_<<\n" if ($dbgLevel > 0); # extract the host if ($_ =~ /^Host: (.*)/) { $httpHost = $1; } # extract the URI elsif ($_ =~ /^GET (.*) HTTP.*/) { #print STDERR ">>$_<<\n" if ($dbgLevel > 1); if ($1 eq "/bytes") { $requestType = "bytes"; } elsif ($1 eq "/ajaxpage") { $requestType = "ajaxpage"; } elsif ($1 eq "/lines") { $requestType = "lines"; } elsif ($1 eq "/linespersec") { $requestType = "linespersec"; } } elsif ($_ eq "") # request terminates with a blank line { #print STDERR "<< exit >>\n" if ($dbgLevel > 1); last; } } # print response headers (mostly fake) - they don't actually matter print $newSock "HTTP/1.0 200 OK\r\n"; print $newSock "Date: Tue, 08 Jun 2010 13:46:38 GMT\r\n"; print $newSock "Expires: Tue, 08 Jun 2010 13:46:38 GMT\r\n"; print $newSock "Last-Modified: Tue, 08 Jun 2010 13:46:38 GMT\r\n"; print $newSock "Cache-Control: private, must-revalidate, max-age=0\r\n"; if ($requestType eq 'ajaxpage') { print $newSock "Content-type: text/html\r\n\r\n"; } else { print $newSock "Content-type: text/plain\r\n\r\n"; } # get files's current state from the file handle my ($tf_new_dev,$tf_new_ino,$tf_new_mode,$tf_new_nlink,$tf_new_uid,$tf_new_gid,$tf_new_rdev,$tf_new_size, $tf_new_atime,$tf_new_mtime,$tf_new_ctime,$tf_new_blksize,$tf_new_blocks) = stat(TF); # check if file has grown my $countNewLines = 0; # scan for new lines to effectively seek to end of file if ($tf_new_size - $tf_cur_size > 0) { while () { ++$countNewLines; #print $newSock $_; #print STDERR "Growth $countNewLines: $_" if ($dbgLevel > 1); } } if ($requestType eq 'lines') { print $newSock $countNewLines . "\r\n"; } elsif ($requestType eq 'linespersec') { my $deltaTime = ($newTime - $oldTime); $deltaTime = 1 if ($deltaTime == 0); print $newSock ( (int (10 * $countNewLines / $deltaTime) / 10.0) ) . "\r\n"; } elsif ($requestType eq 'bytes') { print $newSock ($tf_new_size - $tf_cur_size) . "\r\n"; } elsif ($requestType eq 'ajaxpage') { writeAjaxPage(); } else { print $newSock "file has grown by " . ($tf_new_size - $tf_cur_size) . " bytes and $countNewLines lines\r\n"; } close($newSock); ($tf_cur_dev,$tf_cur_ino,$tf_cur_mode,$tf_cur_nlink,$tf_cur_uid,$tf_cur_gid,$tf_cur_rdev,$tf_cur_size, $tf_cur_atime,$tf_cur_mtime,$tf_cur_ctime,$tf_cur_blksize,$tf_cur_blocks) = ($tf_new_dev,$tf_new_ino,$tf_new_mode,$tf_new_nlink,$tf_new_uid,$tf_new_gid,$tf_new_rdev,$tf_new_size, $tf_new_atime,$tf_new_mtime,$tf_new_ctime,$tf_new_blksize,$tf_new_blocks); $oldTime = $newTime; } ############################################################################## sub catchSignalInt { my $sigName = shift; $SIG{INT} = \&catchSignalInt; # refresh signal handler #close($newSock); # would need to check it was open close($sock); close(TF); deletePidFile(); die("filechangeserver killed by sigint"); } ############################################################################# sub catchSignalHup { my $sigName = shift; print STDERR "\nHUP\n"; close(TF); openTailFile() || die "Error, failed to open $tailfile for reading"; # jump to the end of the current version of the file my ($tf_new_dev,$tf_new_ino,$tf_new_mode,$tf_new_nlink,$tf_new_uid,$tf_new_gid,$tf_new_rdev,$tf_new_size, $tf_new_atime,$tf_new_mtime,$tf_new_ctime,$tf_new_blksize,$tf_new_blocks) = stat(TF); ($tf_cur_dev,$tf_cur_ino,$tf_cur_mode,$tf_cur_nlink,$tf_cur_uid,$tf_cur_gid,$tf_cur_rdev,$tf_cur_size, $tf_cur_atime,$tf_cur_mtime,$tf_cur_ctime,$tf_cur_blksize,$tf_cur_blocks) = ($tf_new_dev,$tf_new_ino,$tf_new_mode,$tf_new_nlink,$tf_new_uid,$tf_new_gid,$tf_new_rdev,$tf_new_size, $tf_new_atime,$tf_new_mtime,$tf_new_ctime,$tf_new_blksize,$tf_new_blocks); my $newTime = time(); $oldTime = $newTime; #$SIG{HUP} = 'catchSignalHup'; # could fail in modules $SIG{HUP} = \&catchSignalHup; # best strategy } ############################################################################## sub writeAjaxPage { print $newSock < Log Rate
value goes here
EOF } # end of filechangeserver.pl