The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#############################################################################
# MRTG::Parse - v0.03                                                       #
#                                                                           # 
# This module parses and utilizes the logfiles produced by MRTG             #
# A full documentation is attached to this sourcecode in POD format.        #
#                                                                           #
# Copyright (C) 2005 Mario Fuerderer <mario@codehack.org>                   #
#                                                                           #
# This library is free software; you can redistribute it and/or             #
# modify it under the terms of the GNU Lesser General Public                #
# License as published by the Free Software Foundation; either              #
# version 2.1 of the License, or (at your option) any later version.        #
#                                                                           #
# This library is distributed in the hope that it will be useful,           #
# but WITHOUT ANY WARRANTY; without even the implied warranty of            #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU         #
# Lesser General Public License for more details.                           #
#                                                                           #
# You should have received a copy of the GNU Lesser General Public          #
# License along with this library; if not, write to the Free Software       #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA #
#                                                                           #
#                                    Mario Fuerderer <mariof@cpan.org)      #
#                                                                           #
#############################################################################

package MRTG::Parse;

use 5.006;
use strict;
use warnings;
use Time::Local;

require Exporter;

our @ISA = qw(Exporter);
our @EXPORT = qw(mrtg_parse);

our $VERSION = '0.03';


###########################################
# This subroutine receives the needed     #
# arguments and passes them to the main   #
# parser() subroutine.                    #
###########################################
sub mrtg_parse {
  my $logfile  = $_[0];
  my $period   = $_[1];
  my $unit     = $_[2];

  # Start the parser and save total incoming and outgoing bytes to @array_traffic_total
  my @array_traffic_total           = &parser($logfile, $period);
  # Convert the bytes into the right unit (you may call it "prefix multiplier").
  my @array_traffic_total_unit_in   = &traffic_output($array_traffic_total[0], $unit);
  my @array_traffic_total_unit_out  = &traffic_output($array_traffic_total[1], $unit);
  my @array_traffic_total_unit_sum  = &traffic_output($array_traffic_total[0] + $array_traffic_total[1], $unit);

  my @return;
  push(@return, join(' ', @array_traffic_total_unit_in));
  push(@return, join(' ', @array_traffic_total_unit_out));
  push(@return, join(' ', @array_traffic_total_unit_sum));

  return @return;
}


###########################################
# Our main subroutine, which parses the   #
# logfile with the values passed through  #
# mrtg_parse().                           # 
########################################### 
sub parser {                              
  my $logfile = $_[0];
  my $period  = $_[1];

  my $parse_type;
  my $start_date;
  my $end_date;
  my $start_epoch;
  my $end_epoch;
  my $weekday;
  my $month;
  my $day;
  my $time;
  my $year;

  # Decide whether we should parse for static time period...
  if ($period eq "day" || $period eq "month" || $period eq "year") {
    ($weekday, $month, $day, $time, $year) = split(/ +/, localtime(time()));
    $parse_type = "static";  
  # ...or an individual one.
  } elsif ($period =~ /\d{8}-\d{8}/) {
    ($start_date, $end_date) = split(/-/, $period);

    unless ($start_date - $end_date =~ /-/) {
      die("Error: time periods are not in the right order!\n");
    }
    
    # Calculate start and end date to seconds since the system epoch
    $start_date  =~ /(\d{4})(\d{2})(\d{2})/;
    my $start_year  = $1;
    my $start_month = $2;
    my $start_day   = $3;
    $start_epoch = timelocal("00", "00", "00", $start_day, $start_month-1, $start_year);

    $end_date    =~ /(\d{4})(\d{2})(\d{2})/;
    my $end_year    = $1;
    my $end_month   = $2;
    my $end_day     = $3;
    $end_epoch   = timelocal("00", "00", "00", $end_day, $end_month-1, $end_year);

    $parse_type = "individual";
  } else {
    die("Error: time period is not present or in an unkown format!\n");
  }

  open(LOG, "$logfile") or die "Error: Could not open MRTG-Logfile ($logfile)";

  # Set any counter to zero before we start parsing.
  my $traffic_total_in  = 0;
  my $traffic_total_out = 0;
  my $last_date         = 0;
  my $counter           = 0;

  while (my $line=<LOG>) {
    # We want to ignore the first line, because it's just the sum of the rest.
    unless ($counter == 0) {
      my $date;
      my @column = split(/\s+/, $line);
      # Build the match-pattern
      my $pattern; 
      if ($parse_type eq "static") {
        $date = scalar localtime($column[0]);
        if ($period eq "day") {
          $pattern = ".+ $month.+$day .+$year";
        } elsif($period eq "month") {
          $pattern = ".+$month.+$year";
        } elsif($period eq "year") {
          $pattern = ".+$year";
        }
       # Check if the current line matches out pattern.
       if ($date =~ /^$pattern$/) {
         my $traffic_in  = $column[1];
         my $traffic_out = $column[2];
    
         my $time_range;
 
         unless ($last_date == 0) { 
           # Calculate the time difference between the current and the previously processd line.
           $time_range = $last_date - $column[0];
         } else {
           # Set time range to zero if it's the first line.
           $time_range = 0;
         } 
        
         # Multiply the bytes with the time range.
         $traffic_total_in  += $traffic_in  * $time_range;
         $traffic_total_out += $traffic_out * $time_range;
        
         # Set the $last_date variable to the new value.
         $last_date  = $column[0];
       }
      } elsif ($parse_type eq "individual") {
        
	$date = $column[0];

        if (($date > $start_epoch) && ($date < $end_epoch)) {
	  my $traffic_in  = $column[1];
	  my $traffic_out = $column[2];

	  my $time_range;

	  unless ($last_date == 0) {
	    $time_range = $last_date - $column[0];
          } else {
	    $time_range = 0;
	  }

          $traffic_total_in  += $traffic_in  * $time_range;
	  $traffic_total_out += $traffic_out * $time_range;

	  $last_date  = $column[0];
	}
	
      }
    }
    $counter++;
  }

  my @array;
  push(@array, $traffic_total_in);
  push(@array, $traffic_total_out);
  return @array;

  close LOG;
}


###########################################
# This is just to get the right unit...   #
###########################################
sub traffic_output {
  my $bytes        = $_[0];
  my $desired_unit = $_[1];
  
  my @array;
  my $unit;
  my $total_count;

  my @unit_array = qw(B KB MB GB TB);
  my $temp_unit  = $bytes;
  my $counter    = 0;
  my $check      = "false";

  # Run through the @unit_array
  foreach my $unit_entry (@unit_array) {
    # We don't need to divide for the first entry, because it's already in byte
    unless ($counter == 0) {
      $temp_unit = ($temp_unit / 1024);
      unless ($desired_unit) {
        # If our value is lower than 1, we should stop here in order to retain an adequate unit.
	if ($temp_unit < 1) {
          last;
        }
      }
    }

    $total_count  = $temp_unit;
    $unit         = $unit_entry;

    if ($desired_unit) {
      if ($desired_unit eq $unit_entry) {
        $check = "true";
        last;
      }
    }

    $counter++;
  }
  
  push(@array, $total_count);
  push(@array, $unit);
  if ($desired_unit && $check eq "true") {
    return @array;
  } elsif (defined($desired_unit) && $check ne "true") {
    die("Erorr: $desired_unit is a non valid unit!\n");
  } else {
    return @array;
  }
}


__END__

=head1 MRTG::Parse

MRTG::Parse - Perl extension for parsing and utilizing the logfiles
generated by the famous MRTG Tool.


=head1 SYNOPSIS

  use strict;
  use MRTG::Parse;
  
  my $mrtg_logfile = "/var/www/htdocs/mrtg/eth0.log";
  my $period       = "day";
  my $desired_unit = "GB"; 

  my ($traffic_incoming, $traffic_outgoing, $traffic_sum) = mrtg_parse($mrtg_logfile, $period, $desired_unit);

  print "Incoming Traffic:   $traffic_incoming\n";
  print "Outgoing Traffic:   $traffic_outgoing\n";
  print "= Sum               $traffic_sum\n";
  

=head1 DESCRIPTION

This perl extension enables its users to parse and utilize the logfiles that
are generated by the famous MRTG (Multi Router Traffic Grapher) tool.

mrtg_parse() takes three argument:
        
	1st:  filename of the mrtg logfile
	2nd:  time period to genereate the output for
	      valid values are:   
	                         - individual time periods like: 20040821-20050130 (ISO 8601)
	                         - static values:                day, month, year
	3rd:  the desired unit (optional)
	      valid values are: 
	                         - B, KB, MB, GB, TB 
				 - if missing mrtg_parse will chose an adequate one for you

mrtg_parse() returns three values:

        1st:  Incoming traffic
	2nd:  Outgoing traffic
	3rd:  Sum of incoming and outgoing


=head2 EXPORT

mrtg_parse()


=head1 SEE ALSO

http://people.ee.ethz.ch/~oetiker/webtools/mrtg/ - MRTG Homepage

http://people.ee.ethz.ch/~oetiker/webtools/mrtg/mrtg-logfile.html - Description of the MRTG Logfile Format


=head1 BUGS

Please report any bugs or feature requests directly to mariof@cpan.org. Thanks!


=head1 AUTHOR

Mario Fuerderer, E<lt>mariof@cpan.orgE<gt>


=head1 COPYRIGHT AND LICENSE

Copyright (C) 2005 by Mario Fuerderer

This library is free software; you can redistribute it and/or             
modify it under the terms of the GNU Lesser General Public                
License as published by the Free Software Foundation; either             
version 2.1 of the License, or (at your option) any later version.      
                                                                       
This library is distributed in the hope that it will be useful,           
but WITHOUT ANY WARRANTY; without even the implied warranty of            
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU         
Lesser General Public License for more details.                           
                                                                          
You should have received a copy of the GNU Lesser General Public          
License along with this library; if not, write to the Free Software       
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 


=cut