/Main_Page

::You must have ninja focus to complete your mission::NinjaFocus::

Spamd stats

Views:


This script will collect stats from the spamd logs. It's based on another script called exim-stats and has only been modified in a minor way. The script will output values that can be collected by cacti. I'll leave it to you to create your own data and graph templates. I collect the data over snmp. An snmpd on the same host as the spamd, has an extend directive, that simply cats out the stats file when an certain OID is queried.

[kieranw@nologo bin]$ cat spamd_stats.pl 
#! /usr/bin/perl
#
# spamd stats grabber for NET-SNMP
#
# Based on exim stats grabber for NET-SNMP, which was based on a script 
# by Matthew Newton and Copyright (c) University of Leicester, 2005
#
# Exim stats Slightly modified for Timico (http://timico.net) by 
# Ian P. Christian. More information about Ian P. Christian's version 
# can be found at 
# http://pookey.co.uk/wordpress/archives/70-cacti-and-exim-a-mostly-complete-guide-part-1
#
# The spamd version of the script was made at Queen Mary College, Apr 2010
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the Free
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.

use strict;


# Options to set up here.
#
# $conf is where to store the current state file.
# $statistics is the file that contains the current data.
# $maillog is the latest spamd main log.
# $mainlogold is the last rotated out main log.
# $archive is a directory. If it exists then old data is written
#          there, too.

my $conf = "/var/lib/spamd/stats/conf";
my $statsfile = "/var/lib/spamd/stats/statistics";
my $mainlog = "/var/log/spamd";
my $mainlogold = "/var/log/spamd.1.bz2";
#my $archive = "/usr/local/exim-snmp-stats/archive";
my $archive = "";


# The regular expressions will need tweaking below, especially the
# IP addresses that determine local or remote. This should be
# moved to a configuration file some day.


# read conf file of when last run and where we got to

my %stats = ();

# set some defaults. If the current-state file does not exist, lets
# start searching through the current mainlog from the beginning.
# Maybe in this case we should really just set $seek to be the last
# position in the file so that the beginning stats are not large?
my $seek = 0;
my $inode = inode_number($mainlog);
my $cantseek = 0;

if (-r $conf) {
  open CONF, "< $conf";
  while (<CONF>) {
    chomp;
    s/^\s*//;
    s/\s*$//;
    next if /^$/;
    if (/^inode=(\d+)$/) {
      $inode = $1;
    }
    if (/^seek=(.*)$/) {
      $seek = $1;
    }
  }
  close CONF;
}
$stats{"connections"} = 0;
$stats{"spam"} = 0;
$stats{"clean"} = 0;


# see if we can seek to current position in the mainlog. If not, then
# it has most likely been rotated
open LOG, "< $mainlog" or die "cannot open exim log file $mainlog!";
if (!seek(LOG, $seek, 0)) {
  $cantseek = 1;
}
close LOG;

if ($inode != inode_number($mainlog) or
    $cantseek or
    $inode == inode_number($mainlogold) or
    $seek > (stat($mainlog))[7] ) {
  # we have changed to a new log file; read the previous mainlog first
  if ($mainlogold =~ /\.bz2$/) {
      my $oldumask = umask;
      umask 0077;
      `bunzip2 -ck $mainlogold > /tmp/spamd_stats.$$`;
      read_log("/tmp/spamd_stats.$$", $seek, \%stats);
      unlink("/tmp/spamd_stats.$$");
      umask $oldumask;
  } else {
      read_log($mainlogold, $seek, \%stats);
  }
  $inode = inode_number($mainlog);
  $seek = 0;
}

$seek = read_log($mainlog, $seek, \%stats);

write_stats($statsfile, \%stats);

# create config file
open CONF, "> $conf";
print CONF "inode=$inode\n";
print CONF "seek=$seek\n";
close CONF;

if (defined $archive and $archive ne "" and -d $archive) {
  write_stats($archive."/".now_time(), \%stats);
}

exit;

sub now_time
{
  my @n = gmtime();
  my ($y, $m, $d) = ($n[5]+1900, $n[4]+1, $n[3]);
  my ($hr, $mn, $sc) = ($n[2], $n[1], $n[0]);
  $m = "0$m" if ($m < 10);
  $d = "0$d" if ($d < 10);
  $hr = "0$hr" if ($hr < 10);
  $mn = "0$mn" if ($mn < 10);
  $sc = "0$sc" if ($sc < 10);
  return "$y-$m-$d-$hr-$mn-$sc";
}

sub inode_number
{
  my $file = shift;
  my ($dummy, $inode) = stat($file);
  return $inode;
}

sub read_log
{
  my ($file, $seek, $stats) = @_;
  my $prevline = undef;
  my $line;
  my ($prevpos, $pos);
  local *LOG;

  open LOG, "< $file" or die "cannot open spamd log file $file!";
  if (!seek(LOG, $seek, 0)) {
    close LOG;
    return $seek;
  }
  while ($line = <LOG>) {
    if (defined $prevline) {
      if ($line =~ /spamd: connection from/) {
        $$stats{"connections"}++;
      } elsif ($line =~ /spamd: identified spam/) {
        $$stats{"spam"}++;
      } elsif ($line =~ /spamd: clean message/) {
        $$stats{"clean"}++;
      }
    }
    $prevline = $line;
    $prevpos = $pos;
    $pos = tell LOG;
  }
  close LOG;

  $prevpos = $seek unless defined $prevpos;

  return $prevpos;
}

sub write_stats
{
  my ($file, $stats) = @_;
  local *STATS;

  open STATS, "> $file";
  print STATS <<EOF;
$$stats{"connections"}
$$stats{"spam"}
$$stats{"clean"}
EOF
  close STATS;
}


Main Menu

Personal tools

Toolbox