Josh Tarchuk

I'm guessing © C. Stewart - cms@balius.com

#!/usr/local/bin/perl
#
# ============== pmdflog.pl =================
# A simple program for gathering statistics from iMS mail.log* files.
# Sample usage:
# % imslog.pl < InstanceRoot/log/imta/mail.log_yesterday > IMTA.stats
#
# You must have at least V5.005 of perl to run this script. Specifically,
# there must be support for the "use vars qw" feature.
#
# To run it this tool nightly, you might make a small script, say
# collect_stats:
#
# #!/bin/csh
# /usr/local/bin/imslog.pl < InstanceRoot/log/imta/mail.log_current >& \
# /usr/tmp/mail_stats.log
# mailx -s "mail_stats, `date`" root@mynode.com < /usr/tmp/mail_stats.log
#
# Then make a crontab entry for root to launch it nightly:
# 15 0 * * * /usr/local/bin/collect_stats > /dev/null 2>&1
#
# ----------------------------------------------------------------------
#
# iMS mail.log file codes:
#
# A -- Print request failed, FAX temp failure, page aborted,
# or IMAP connection failed
# C -- Connection closed
# D -- Message dequeue
# E -- Message enqueue
# F -- Mailserv command or page failed
# I -- ETRN received
# J -- Connection rejected
# M -- Maximum number of FAX delivery attempts exceeded
# O -- Connection opened
# Q -- Message requeue
# R -- Paging switch rejected message
# S -- Print request, mailserv operation, or page sent success
# X -- Connection rejected
# Y -- Connection failed
# Z -- Delivery and reqeue (currently treated the same as a Q)
# ----------------
# Global Variables:
#
# %bad_domains - count of Q records caused by domains
# %chan_enq - time a particular message was enqueued
# %conn_close - count of closed connecions per server
# %conn_close_source - source of connection closes
# %conn_failed - number of failed connections per server
# %conn_fail_source - source of connection failures
# %conn_open - count of opened connections per server
# %conn_open_source - source of connection opens
# %conn_reject - count of connection rejects per server
# %conn_reject_source - source of connection rejects
# %delay_bins_chan - counts of messages based on delays per channel
# %delay_bins_host - counts of messages based on delays per host
# %dequeues_by_date - count of dequeues by date
# %etrn_domains - the domains that have requested etrn support
# %failed_messages - count of Q records by channel
# %fax_destinations - count of successful faxes sent by source address by phone number
# %fax_senders - count of successful faxes sent by source address
# %giant_messages - count of big message senders by source
# %in_messages - count of messages enqueued by channel
# %in_size - size of messages enqueued by channel
# %into_chan - count of messages enqueued to channel
# %into_size - size of messages enqueued to channel
# %mailserv_users - domains using the mailserv channel
# %max_chan_delay - maximum delay time between enqueues and dequeues
# %max_host_delay - maximum delay time between enqs and deqs, by host
# %out_messages - count of messages dequeued by channel
# %out_messages_host - count of messages sent to a particular host
# %out_size - size of messages dequeued by channel
# %pager_successes - count of successful pages sent by channel
# %rectypes - count of each type of record we find, keyed by record type
# %rejected_messages - count of R records per channel
# %warning_cnt - count of W records per channel
# %retries_done - number of times a channel took n retries to deliver a msg
# %retry_num - index of letters (Z..A) to numbers (0..26)
# %size_bins - counts of messages based on size
# %total_host_delay - total delay time by host
#
# @all_dates - list of dates covered by this log file
# @bin_names - list of names used as keys into size_bins
# @delay_bin_names - list of names used as keys into delay_bins
# @dequeues_per_hour - count of dequeus in an hour
# @retry_bin_names - names of bins used to count retries
#
# $etrn_count - the number of etrn requests received
# $fax_failures - count of failed fax channel attempts
# $fax_max_failures - count of maximum delivery attempts exceeded
# $fax_successes - count of successful fax channel uses
# $last_date - last date processed
# $line_count - current line number we're on
# $mailserv_failures - count of failed mailserv uses
# $mailserv_successes - count of successful mailserv channel uses
# $pager_failures - count of failed pager channel uses
# $pager_successes - count of successful pager channel uses
# $printer_failures - count of failures on printer channels
# $printer_successes - count of successful printer channel uses
# $total_dequeues - total number of dequeue operations performed
# $total_dequeue_size - total size of messages dequeued
#
# -------
# Updates:
#
# 15 December 97 - Added support for Y records. Thanks to Chris
# Pappagianis for this one.
# 15 December 97 - Added channel delay time processing.
# 16 December 97 - Added host delay time processing.
# 17 December 97 - Fixed a couple of bugs: failed connections weren't
# getting counted correctly; remove leading space from
# dates with single-digit days. Also did some minor
# efficiency enhancements.
# 22 December 97 - Added handling for F records with mailserv channels
# and A records with IMAP servers
# 29 December 97 - Added message size bins
# 31 December 97 - Added dequeues per hour recording
# 7 January 98 - Redid delay time processing -- keep counts in bins
# and display those numbers rather than averages
# 9 January 98 - Fixed a bug in timeinsecs()
# 18 February 98 - Added big message source records
# 6 March 98 - Fixed some printing ugliness; changed the format of
# printed enqueue statistics
# 26 March 98 - Fixed a bug in the big message source records -- when
# messages had an empty envelope from address, the
# destination showed as the source
# 6 November 98 - Add support for logs that include pid's, bfc
# 9 November 98 - Add support for uucp channels, bfc
# 13 November 98 - Add support for 'J' field, bfc
# 17 November 98 - Allow x400 channel to use 'A' record, add processing for
# W records, bfc
# 22 December 98 - Added support for FAX channels, specifically A, S,
# and M record types. (whm)
# 24 December 98 - Restructured the parsing of input records by centralizing
# the parse logic. Added VMS conditional logic for gethost
# routines. Optionally support the generation of mail or
# connection information.
# 8-JAN-1999:whm - Strip trailing '>' or '"' from hosts in transit
# summary. Fixed a typo in the server summary.
# 2-JUN-1999:whm - Comment out the VMS conditionals now that the latest
# perl for VMS has socket support via DECC.
# 3-Mar-2000:jwa - Added some support for PMDF 6.0 log file changes.
# Specifically the A and/or S that can be appended to
# E, D, and Q records is now tallied.
# 3-Mar-2000:jwa - Added support for debugging. If you set the environment
# variable PMDFLOGPLDEBUG to 1, the $debug varible will
# be set.
use vars qw(%bad_domains
%chan_enq
%conn_close
%conn_close_source
%conn_failed
%conn_fail_source
%conn_open
%conn_open_source
%conn_reject
%conn_reject_source
%delay_bins_chan
%delay_bins_host
%dequeues_by_date
%etrn_domains
%failed_messages
%fax_senders
%fax_destinations
%giant_messages
%in_messages
%in_size
%into_chan
%into_size
%mailserv_users
%max_chan_delay
%max_host_delay
%out_messages
%out_messages_host
%out_size
%pager_successes
%rectypes
%rejected_messages
%secure_connections
%warning_cnt
%retries_done
%retry_num
%size_bins
%total_host_delay
@all_dates
@bin_names
@delay_bin_names
@dequeues_per_hour
@retry_bin_names
@token
$connection_record
$debug
$etrn_count
$fax_failures
$fax_max_failures
$fax_successes
$generate_connection_report
$generate_mail_report
$last_date
$line_count
$mailserv_failures
$mailserv_successes
$pager_failures
$pager_successes
$printer_failures
$printer_successes
$processing_conninfo
$processing_date
$processing_host
$processing_inchan
$processing_outchan
$processing_pid
$processing_rectype
$processing_rectype_fld_no
$processing_server
$processing_size
$processing_time
$rest
$total_dequeues
$total_dequeue_size
);

use Time::Local;
use strict;

sub process_enqueue {
my ($local_part, $end, $host, $source_host, $source_local, $tmp);
my ($id);

$secure_connections{"Auth"} += 1 if ($processing_rectype =~ /A/);
$secure_connections{"Secure"} += 1 if ($processing_rectype =~ /S/);

if (($rest =~ /^\s/) || ($rest =~ /^<>/)) {
$source_host = "Empty envelope from address";
($host, $end) = split / /o, $rest, 2;
}
else {
if ($processing_outchan eq 'l') {
$host = 'local delivery';
($source_host, $end) = split / /o, $rest, 2;
}
else {
($source_local, $end) = split /@/o, $rest, 2;
($source_host, $tmp) = split / /o, $end, 2;
if ($processing_outchan =~ /uucp/) {
# A uucp_* channel, will be in the form host!user
($host,$local_part,$tmp) = split /\s+|\!/o,$tmp,3;
$host = lc($host);
$source_host = $host.'!'.$local_part;
}
else {
($local_part, $end) = split /@/o, $tmp, 2;
($host, $tmp) = split / /o, $end, 2;
$host = lc($host);
$source_host = $source_local.'@'.$source_host;
}
}
}

$end =~ /(<[^>]+>)/o;
$id = $1;

$in_messages{$processing_inchan}++;
$in_size{$processing_inchan} += $processing_size;
$into_chan{$processing_outchan}++;
$into_size{$processing_outchan} += $processing_size;

if ($processing_size > 1000) {
$giant_messages{$source_host}++;
}

$chan_enq{$processing_outchan}{$id}
= timeinsecs($processing_date, $processing_time)
if (not exists $chan_enq{$processing_outchan}{$id});
}

sub process_dequeue {
my ($local_part, $end, $host, $id, $deqtime, $delta, $tmp);
my ($hours, $minutes, $seconds, $bin, $num_retries, $found_file);

$secure_connections{"Auth"} += 1 if ($processing_rectype =~ /A/);
$secure_connections{"Secure"} += 1 if ($processing_rectype =~ /S/);

# Increment the message count for the hour

($hours, $minutes, $seconds) = split /:/o, $processing_time;
$dequeues_per_hour[$hours]++;

# Split the record into two parts: before and after the @ sign that
# should be in the destination address

if ($processing_outchan !~ /uucp/) {
($local_part, $end) = split /@/o, $_, 2;
}

# Now grab the host name from the destination address

if ($processing_outchan eq 'l') {
$host = 'local delivery';
}
elsif ($processing_outchan =~ /uucp/) {
($local_part,$end) = split /\!/o, $_, 2;
$local_part =~ /(.*)([\s]+)([\S]+)/;
$host = $3;
$host = lc($host);
}
else {
($host, $tmp) = split / /o, $end, 2;
($local_part, $end) = split /@/o, $tmp, 2;
($host, $tmp) = split / /o, $end, 2;
$host = lc($host);
}
$host =~ s/>$//;
$host =~ s/\"$//;

# Look for the message ID, enclosed in angle brackets
$end =~ /(<[^>]+>)/o;
$id = $1;

$out_messages{$processing_outchan}++;
$out_messages_host{$host}++;
$out_size{$processing_outchan} += $processing_size;
$total_dequeues++;
$total_dequeue_size += $processing_size;
if ($processing_date ne $last_date) {
$last_date = $processing_date;
push @all_dates, $last_date;
}
$dequeues_by_date{$processing_date}++;

# Increment the proper bin counter

if ($processing_size < 2) {
$size_bins{'Less than 2K'}++;
}
elsif ($processing_size < 10) {
$size_bins{'2K - 10K'}++;
}
elsif ($processing_size < 100) {
$size_bins{'10K - 100K'}++;
}
elsif ($processing_size < 1000) {
$size_bins{'100K - 1M'}++;
}
else {
if ($processing_size < 10000) {
$size_bins{'1M - 10M'}++;
}
else {
$size_bins{'Over 10M'}++;
}
}

# See if we have an enqueue time for this message. If so, compute
# the delay time ($delta) and increment the proper bin for the channel
# and the host. Also keep track of the maximum delay time per
# channel and host.

if (exists $chan_enq{$processing_outchan}{$id}) {
$deqtime = timeinsecs($processing_date, $processing_time);
$delta = $deqtime - $chan_enq{$processing_outchan}{$id};

# Initialize the bin counters to zero if this is a newly seen
# channel or host

if (not exists $delay_bins_chan{$processing_outchan}) {
foreach $bin (@delay_bin_names) {
$delay_bins_chan{$processing_outchan}{$bin} = 0;
}
}
if (not exists $delay_bins_host{$host}) {
foreach $bin (@delay_bin_names) {
$delay_bins_host{$host}{$bin} = 0;
}
}

if ($delta < 2) {
$delay_bins_chan{$processing_outchan}{'<2 sec'}++;
$delay_bins_host{$host}{'<2 sec'}++;
}
elsif ($delta < 10) {
$delay_bins_chan{$processing_outchan}{'<10 sec'}++;
$delay_bins_host{$host}{'<10 sec'}++;
}
elsif ($delta < 60) {
$delay_bins_chan{$processing_outchan}{'<1 min'}++;
$delay_bins_host{$host}{'<1 min'}++;
}
elsif ($delta < 300) {
$delay_bins_chan{$processing_outchan}{'<5 mins'}++;
$delay_bins_host{$host}{'<5 mins'}++;
}
elsif ($delta < 3600) {
$delay_bins_chan{$processing_outchan}{'<1 hour'}++;
$delay_bins_host{$host}{'<1 hour'}++;
}
else {
$delay_bins_chan{$processing_outchan}{'>1 hour'}++;
$delay_bins_host{$host}{'>1 hour'}++;
}

if ((not (exists $max_chan_delay{$processing_outchan})) or
($delta > $max_chan_delay{$processing_outchan})) {
$max_chan_delay{$processing_outchan} = $delta;
}

if ((not (exists $max_host_delay{$host})) or
($delta > $max_host_delay{$host})) {
$max_host_delay{$host} = $delta;
}

# We keep a total delay time for hosts because we display the top
# 20 hosts by total delay time

$total_host_delay{$host} += $delta;
}

# If file name logging is on, then we can track messages by number
# of retries before delivery

# Look for a VMS-style file name first

$found_file = 0;
if ($_ =~ /PMDF_QUEUE:\[.*\]([A-Z])([A-Z])/) {
$num_retries = $retry_num{$1}*26 + $retry_num{$2};
$found_file = 1;
}
elsif ($_ =~ /imta\/queue\/.*\/([A-Z][A-Z])/) {
$num_retries = $retry_num{$1}*26 + $retry_num{$2};
$found_file = 1;
}
if ($found_file) {
if (not exists $retries_done{$processing_outchan}) {
foreach $bin (@retry_bin_names) {
$retries_done{$processing_outchan}{$bin} = 0;
}
}

if ($num_retries == 0) {
$retries_done{$processing_outchan}{'1st time'}++;
}
elsif ($num_retries == 1) {
$retries_done{$processing_outchan}{'2nd time'}++;
}
elsif ($num_retries < 5) {
$retries_done{$processing_outchan}{'<5 times'}++;
}
else
{
$retries_done{$processing_outchan}{'>5 times'}++;
}
}
}

sub process_requeue {
my ($from_address, $to_address, $other, $mbx, $domain);

if ($rest =~ /^\s/) {
$other = $rest;
$other =~ s/^ //;
$from_address = "";
} elsif ($rest =~ /^<>/) {
$other = $rest;
$other =~ s/^<>//;
$from_address = "";
} else {
($mbx,$other) = split /@/o, $rest, 2;
($domain, $other) = split / /o, $other, 2;
$from_address = $mbx . "@" .$domain;
}

# Check for header to, otherwise use envelope to
if ($other =~ /^rfc822;/) {
$other =~ s/^rfc822;//;
($mbx, $other) = split /@/o, $other, 2;
($domain, $other) = split / /o, $other, 2;
$to_address = $mbx . "@" . $domain;
} else {
# Envelope to might not have a domain
($to_address, $other) = split / /o, $other, 2;
if ($to_address =~ /@/o ) {
($mbx, $domain) = split /@/o, $to_address;
} else {
$domain = $to_address;
}
}

$domain =~ s/>$//;
$domain =~ s/\"$//;

$failed_messages{$processing_inchan}++;
$bad_domains{lc($domain)}++;
}

sub process_success {
my ($address, $other, $user, $domain);
my ($tag);

($address, $other) = split / /o, $rest, 2;

# S records are only defined for mailserv, printer, FAX,
# and pager channels
# If we find something else, make a note of it for future extension

if ($processing_inchan =~ /^mailserv/io) {
$mailserv_successes++;
($user, $domain) = split /@/o, $address;
$mailserv_users{lc($domain)}++;
}
elsif ($processing_inchan =~ /^printer/io) {
$printer_successes++;
}
elsif ($processing_inchan =~ /^pager/io) {
$pager_successes{$processing_inchan}++;
}
elsif ($processing_inchan =~ /^g3/io) {
($tag, $other) = split / /o, $other, 2;
$other =~ m/^([\d| |-]+)/o;
$fax_destinations{$address}{$1}++;
$fax_senders{$address}++;
$fax_successes++;
}
else {
print "Unexpected channel $processing_inchan in S record at line $line_count\n";
}
}

sub process_failure {
# F records can be generated by the printer, pager, or mailserv channel

# We only record the number of failures, no more.

if ($processing_inchan =~ /^printer/io) {
$printer_failures++;
}
elsif ($processing_inchan =~ /^pager/io) {
$pager_failures++;
}
elsif ($processing_inchan =~ /^mailserv/io) {
$mailserv_failures++;
}
else {
print "** Found unexpected channel $processing_inchan in F record\n";
}
}

sub process_abort {
# A records can be generated by the printer channel, the pager channel,
# the fax channel, an IMAP server, or an X400 channel. If it's an IMAP
# server, we'll call the connection fail routine. Otherwise this routine
# is currently just like process_failure; they are separate in case
# additional data can be extracted from the different records

if ($processing_inchan =~ /^printer/io) {
$printer_failures++;
}
elsif ($processing_inchan =~ /^pager/io) {
$pager_failures++;
}
elsif ($processing_inchan =~ /^g3/io) {
$fax_failures++;
}
elsif ($processing_inchan =~ /^IMAP/io) {
&process_connfail;
}
elsif ($processing_inchan =~ /POP3/io) {
# successful login, ignore for now, bfc ; this was fixed in a later
# version of POP, but let's make allowances..
;
}
elsif ($processing_inchan =~ /x400/io) {
# ignore for now, probably just an aborted connection
;
}
else {
print "Unexpected channel $processing_inchan in A record at line $line_count\n";
}
}

sub process_max_fax {
# M records are generated when the FAX channel finally gives up
# trying to deliver faxen.

my ($address, $other);

($address, $other) = split / /o, $rest, 2;

if ($processing_inchan =~ /^g3/io) {
$fax_max_failures++;
}
else {
print "Unexpected channel $processing_inchan in M record at line $line_count\n";
}
}

sub process_etrn {
my ($sourceip, $tmp, $domain);

$processing_conninfo =~
/(^TCP\|)(\d+\.\d+\.\d+\.\d+\|)(\d+\|)(\d+\.\d+\.\d+\.\d+)(\|.)/o;
$sourceip = $4;
($tmp, $domain) = split / /o, $rest, 2;
$etrn_count++;
$etrn_domains{$sourceip}{$domain}++;
}

sub process_reject {
$rejected_messages{$processing_inchan}++;
}

sub process_warning {
$warning_cnt{$processing_inchan}++;
}

sub process_open {
my ($sourceip);

$processing_conninfo =~
/(^TCP\|)(\d+\.\d+\.\d+\.\d+\|)(\d+\|)(\d+\.\d+\.\d+\.\d+)(\|.)/o;
$sourceip = $4;
$conn_open{$processing_server}++;
$conn_open_source{$processing_server}{$sourceip}++;
}

sub process_close {
my ($sourceip);

$processing_conninfo =~
/(^TCP\|)(\d+\.\d+\.\d+\.\d+\|)(\d+\|)(\d+\.\d+\.\d+\.\d+)(\|.)/o;
$sourceip = $4;
$conn_close{$processing_server}++;
$conn_close_source{$processing_server}{$sourceip}++;
}

sub process_connreject {
my ($sourceip);

$processing_conninfo =~
/(^TCP\|)(\d+\.\d+\.\d+\.\d+\|)(\d+\|)(\d+\.\d+\.\d+\.\d+)(\|.)/o;
$sourceip = $4;
$conn_reject{$processing_server}++;
$conn_reject_source{$processing_server}{$sourceip}++;
}

sub process_connfail {
my ($sourceip);

$processing_conninfo =~
/(^TCP\|)(\d+\.\d+\.\d+\.\d+\|)(\d+\|)(\d+\.\d+\.\d+\.\d+)(\|.)/o;
$sourceip = $4;
$conn_failed{$processing_server}++;
$conn_fail_source{$processing_server}{$sourceip}++;
}
#
# MAIN PROGRAM BEGINS
#
# Note: if this process_table gets additions, you must also edit the
# RECORD TYPE CHECK if statement further below.
#
my %process_table = (
'E' => \&process_enqueue,
'D' => \&process_dequeue,
'Q' => \&process_requeue,
'Z' => \&process_requeue,
'S' => \&process_success,
'F' => \&process_failure,
'A' => \&process_abort,
'R' => \&process_reject,
'O' => \&process_open,
'C' => \&process_close,
'X' => \&process_connreject,
'Y' => \&process_connfail,
'J' => \&process_reject,
'W' => \&process_warning,
'M' => \&process_max_fax,
'I' => \&process_etrn,
);

# List of valid records follows. To add support for new or unsupported
# record types, just add the type to the list and add a processing
# routine to %process_table. Also change the pattern below where we
# check for the record type we found. The fact that you have to change
# 3 things might indicate that this could be done better...

my @valid_records = ('E','D','Q','Z','S','F','A','R','O','C','X','Y','J','W','M','I');
my %valid_records;
my ($date_printed, $rectype, $last_record, $i);

$debug = $ENV{PMDFLOGPLDEBUG};
print "Debugging is on\n" if $debug;
print ">>> imslog.pl v: 3-MAR-2000\n";

# Initialize the size bins

%size_bins = (
'Less than 2K' => 0,
'2K - 10K' => 0,
'10K - 100K' => 0,
'100K - 1M' => 0,
'1M - 10M' => 0,
'Over 10M' => 0,
);
@bin_names = ('Less than 2K',
'2K - 10K',
'10K - 100K',
'100K - 1M',
'1M - 10M',
'Over 10M',);

@delay_bin_names = ('<2 sec',
'<10 sec',
'<1 min',
'<5 mins',
'<1 hour',
'>1 hour');

@retry_bin_names = ('1st time',
'2nd time',
'<5 times',
'>5 times');

# Construct a hash to use when computing number of retries

$i=25;
for ('A'..'Z') {
$retry_num{$_} = $i--;
}


# Construct a hash of valid record types. This speeds up record type
# validation.
for (@valid_records) {
$valid_records{$_}++;
}

# Keep track of information by day. Even "single day" logs often have
# remnants of the previous day in them.

$last_date = '1-JAN-1900';
$generate_mail_report = 0;
$generate_connection_report = 0;

log_read:
while (<>) {

# Single-digit days have a space prepended. Take it off.
s/^ //;

$line_count++;

# One reasonably consistent feature of the log is that the date and time
# seem to always be the first two fields.
($processing_date, $processing_time, $rest) = split / /o,$_,3;
if (!$date_printed) {
print "\n Log started on $processing_date at $processing_time\n";
$date_printed = 1;
}

# RECORD TYPE CHECK
# We'll assume that a single upper-case character in the set of things
# we regard as valid record types occuring between two spaces is a real
# record type identifier

# PMDF v6.0 change -- record types are no longer always a single
# character. E, D, and Q records can be followed by A (authenticated)
# and/or S (secure)

if (/(\s+)([EDQZSFAROCXYJWMI]|[EDQ][AS]+)(\s+)/o) {
$processing_rectype = $2;
} else {
print "Found an unsupported record type in line $line_count\n";
print "$_\n" if $debug;
next log_read;
}

# Now that we know what the record type is find out what field
# number it is. This is required since we do not know if we have
# to deal with a host and/or a pid. Note we start at 2 because
# we have already pulled off the date and time.
$processing_rectype_fld_no = 2;
find_rectype:
while (1==1) {
$processing_rectype_fld_no++;
($token[$processing_rectype_fld_no],$rest) = split /\s+/io,$rest,2;
if ($token[$processing_rectype_fld_no] eq $processing_rectype)
{last find_rectype;}
if ($processing_rectype_fld_no > 7) {
print "Unknown record type at line $line_count\n";
next log_read;
}
}

$connection_record = 0;
if ($processing_rectype =~ "D") {
# dequeue records have one less field
$processing_inchan = "";
dequeue_field_case: {

if ($processing_rectype_fld_no == 6) {
# both a host name and a pid are present
$processing_host = $token[3];
$processing_pid = $token[4];
$processing_outchan = $token[5];
last dequeue_field_case;}

if ($processing_rectype_fld_no == 4) {
# neither host or pid are present
$processing_host = "";
$processing_pid = "";
$processing_outchan = $token[3];
last dequeue_field_case;}

if ($token[3] =~ /^([a-f0-9]*\.[a-f0-9])/io) {
# looks like a pid, so that is what we guess
$processing_host = "";
$processing_pid = $token[3];
$processing_outchan = $token[4];
last dequeue_field_case;}

# doesn't look like a pid, must be a host
$processing_host = $token[3];
$processing_pid = "";
$processing_outchan = $token[4];
}
} elsif (($processing_rectype eq "Q")
|| ($processing_rectype eq "Z")
|| ($processing_rectype eq "J")
|| ($processing_rectype eq "R")
|| ($processing_rectype eq "W")) {

# delivery attempts have one less field as well
$processing_outchan = "";
dequeue_field_case: {

if ($processing_rectype_fld_no == 6) {
# both a host name and a pid are present
$processing_host = $token[3];
$processing_pid = $token[4];
$processing_inchan = $token[5];
last dequeue_field_case;}

if ($processing_rectype_fld_no == 4) {
# neither host or pid are present
$processing_host = "";
$processing_pid = "";
$processing_inchan = $token[3];
last dequeue_field_case;}

if ($token[3] =~ /^([a-f0-9]*\.[a-f0-9])/io) {
# looks like a pid, so that is what we guess
$processing_host = "";
$processing_pid = $token[3];
$processing_inchan = $token[4];
last dequeue_field_case;}

# doesn't look like a pid, must be a host
$processing_host = $token[3];
$processing_pid = "";
$processing_inchan = $token[4];
}

} else {
$connection_record = 1 if (($processing_rectype eq "O")
|| ($processing_rectype eq "C")
|| ($processing_rectype eq "I")
|| ($processing_rectype eq "X")
|| ($processing_rectype eq "Y"));
field_case: {

if ($processing_rectype_fld_no == 7) {
# both a host name and a pid are present
$processing_host = $token[3];
$processing_pid = $token[4];
if ($connection_record) {
$processing_server = $token[5];
$processing_inchan = "";
$processing_outchan = "";
} else {
$processing_server = "";
$processing_inchan = $token[5];
$processing_outchan = $token[6];
}
last field_case;}

if ($processing_rectype_fld_no == 5) {
# neither host or pid are present
$processing_host = "";
$processing_pid = "";
if ($connection_record) {
$processing_server = $token[3];
$processing_inchan = "";
$processing_outchan = "";
} else {
$processing_server = "";
$processing_inchan = $token[3];
$processing_outchan = $token[4];
}
last field_case;}

if ($token[3] =~ /^([a-f0-9]*\.[a-f0-9])/io) {
# looks like a pid, so that is what we guess
$processing_host = "";
$processing_pid = $token[3];
if ($connection_record) {
$processing_server = $token[4];
$processing_inchan = "";
$processing_outchan = "";
} else {
$processing_server = "";
$processing_inchan = $token[4];
$processing_outchan = $token[5];
}
last field_case;}

# doesn't look like a pid, must be a host
$processing_host = $token[3];
$processing_pid = "";
if ($connection_record) {
$processing_server = $token[4];
$processing_inchan = "";
$processing_outchan = "";
} else {
$processing_server = "";
$processing_inchan = $token[4];
$processing_outchan = $token[5];
}
}
}

if ($connection_record) {
# Get rid of the annoying NULL in IMAP records. Note that this was
# a bug in the logging software, but it does occur in some versions
# of PMDF
$rest =~ s/\0/ /g;
$processing_server =~ s/\0/ /g;
($processing_conninfo, $rest) = split /\s/io,$rest,2;
$processing_size = "";
$generate_connection_report = 1;
} else {
$processing_server = "";
$processing_conninfo = "";
($processing_size, $rest) = split /\s/io,$rest,2;
if (!($processing_inchan =~ /^IMAP/io) &
!($processing_inchan =~ /POP3/io) &
! ($processing_inchan =~ /x400/io)) {
$generate_mail_report = 1;
}
}

# Bump the record type counter (we don't do anything we this, but
# we keep it anyway). Then call the appropriate routine to deal with
# this record.

$rectypes{$processing_rectype}++;

# The hash test is just paranoia; it could be removed. Since we
# checked for a valid record type above, we shouldn't get here unless
# we have a handling routine for this record. Still...

# PMDF v6.0 change -- the substr() fxn is used in case we have a
# D or E record with an A and/or S appended.

if ($valid_records{substr ($processing_rectype, 0, 1)}) {
&{$process_table{substr ($processing_rectype, 0, 1)}};
}
else {
print "There's no processing routine for $processing_rectype records\n";
}
$last_record = $_;
}

$last_record =~ s/^\s+//;
($processing_date, $processing_time, $rest) = split / /o, $last_record;
print " Log ended on $processing_date at $processing_time\n\n";

if ($generate_mail_report) {print_mail_report();}
if ($generate_connection_report) {print_connection_report();}
#
# MAIN PROGRAM ENDS
#

# All the report printing stuff can probably be done better with formats,
# but I read through the format part in the camel book and came away
# baffled. So converting this over to using formats is left as an
# exercise for the reader.

sub print_mail_report {

my ($key, $channel, $avg_delay, $avg_delay_string, $max_delay_string,
$i, $host, $server, $source, $hostname, $msg_total, $header_printed,
$first, $sender, $number, $tmp, $last);
my (@all_servers);
my (%printed);

print "\n\n Enqueue Statistics\n\n";
printf "%15s %8s %14s %8s %14s\n", "Channel","Messages"," ",
"Messages", " ";
printf "%15s %8s %14s %8s %14s\n", " Name ","Q'd from","Size (Kbytes)",
"Q'd to ", "Size (Kbytes)";
printf "%15s %8s %14s %8s %14s\n", '-'x7, '-'x8, '-'x13, '-'x8, '-'x13;

foreach $key (sort keys %in_messages) {
printf "%15s %8d %14d %8d %14d\n",
$key, $in_messages{$key}, $in_size{$key},
($into_chan{$key} ? $into_chan{$key} : 0),
($into_size{$key} ? $into_size{$key} : 0);
$printed{$key} = ' ';
}

# print "\n\n Messages Enqueued to:\n\n";
# printf "%15s %8s %14s\n", "Channel","Messages","Size (Kbytes)";
# printf "%15s %8s %14s\n", '-'x7, '-'x8, '-'x13;
foreach $key (sort keys %into_chan) {
if (!$printed{$key}) {
printf "%15s %8d %14d %8d %14d\n",
$key, 0, 0, $into_chan{$key}, $into_size{$key};
}
}

print "\n\n Dequeue statistics:\n\n";
printf "%15s %8s %14s\n", "Channel","Messages","Size (Kbytes)";
printf "%15s %8s %14s\n", '-'x7, '-'x8, '-'x13;
foreach $key (sort keys %out_messages) {
printf "%15s %8d %14d\n", $key, $out_messages{$key}, $out_size{$key};
}

print "\n\n Secure connections:\n\n";
print "$secure_connections{Auth} connections were authenticated\n";
print "$secure_connections{Secure} connections were secure\n";

print "\n\n Trouble statistics:\n\n";
printf "%15s %13s\n", "Channel", "Retry Entries";
printf "%15s %13s\n", '-'x7, '-'x13;
foreach $key (sort keys %failed_messages) {
printf "%15s %13d\n", $key, $failed_messages{$key};
}

print "\n Top 10 requeues by domain/user:\n\n";
printf " %11s %35s\n", 'Domain/User', 'Num Retries';
printf " %11s %35s\n", '-'x11, '-'x11;
$i = 1;
foreach $key (sort bynum keys %bad_domains) {
printf " %-35.35s %d\n", $key, $bad_domains{$key};
last if $i++ == 10;
}

if (keys %rejected_messages) {
print "\n\n Rejected Messages \n\n";
printf "%15s %14s\n", "Channel", "Reject Entries";
printf "%15s %14s\n", '-'x7, '-'x14;
foreach $key (sort keys %rejected_messages) {
printf "%15s %14d\n", $key, $rejected_messages{$key};
}
}

if (keys %warning_cnt) {
print "\n\n Warnings \n\n";
printf "%15s %14s\n", "Channel", "Warnings";
printf "%15s %14s\n", '-'x7, '-'x14;
foreach $key (sort keys %warning_cnt) {
printf "%15s %14d\n", $key, $warning_cnt{$key};
}
}

if ($printer_successes || $mailserv_successes ||
$printer_failures || $mailserv_failures) {
print "\n\n Mailserv, Pager, and Printer Channels:\n\n";

print " There were $printer_successes successful uses of the printer channel\n"
if $printer_successes;

print " There were $printer_failures attempts to use the printer channel that failed\n"
if $printer_failures;

print " There were $pager_successes successful uses of pager channels\n"
if $pager_successes;

print " There were $pager_failures attempts to use pager channels that failed\n"
if $pager_failures;

if ($mailserv_successes) {
print " There were $mailserv_successes successful uses of the mailserv channel\n";
printf " Users were from %d different domains. Top 10 domains follow.\n\n", scalar keys %mailserv_users;
printf " %6s %40s\n", 'Domain', 'Num Accesses';
printf " %6s %40s\n", '-'x6, '-'x12;
$i=1;
foreach $key (sort byuse keys %mailserv_users) {
printf " %-35.35s %d\n", $key, $mailserv_users{$key};
last if $i++==10;
}
}
print "\nThere were $mailserv_failures attempts to use the mailserv channel that failed\n"
if $mailserv_failures;
}

if ($fax_successes || $fax_failures || $fax_max_failures) {
print "\n FAX Summary\n\n";
}
if ($fax_failures > 0) {
printf "%4s %4s %-60s\n",
" ", $fax_failures, "delivery attempts by FAX channels failed.";
}
if ($fax_max_failures > 0) {
printf "%4s %4s %-60s\n",
" ", $fax_max_failures, "FAXes exceeded the maximum number of delivery attemps.";
}
if ($fax_successes > 0) {
printf "%4s %4s %-60s\n",
" ", $fax_successes, "FAXes were successfully delivered.";

# Uncomment the following if you want more fax details
# print "\n";
# printf "%7s %32s %6s %-24s\n",
# " ", "Source", "Count", "Destination";
# printf "%7s %32s %6s %-24s\n",
# " ", "-"x32, "-"x6 , "-"x24;
# foreach $sender (sort keys %fax_destinations) {
# $last = " ";
# foreach $number (keys %{$fax_destinations{$sender}}) {
# $tmp = $sender;
# if ($sender eq $last) {$tmp = " ";}
# printf "%7s %32s %6s %-24s\n",
# " ", $sender, $fax_destinations{$sender}{$number}, $number;
# $last = $sender;
# }
# }

# comment the following if you are printing more fax details
$i=1;
print "\n";
printf "%7s %s\n\n", " ", "Top 10 FAX users where:";
printf "%7s %32s %6s\n",
" ", "Source", "Count";
printf "%7s %32s %6s\n",
" ", "-"x32, "-"x6;
foreach $key (sort byfaxsource keys %fax_senders) {
printf "%7s %32s %6s\n",
" ", $key, $fax_senders{$key};
last if $i++ == 10;
}

}

print "\n\n Messaging Summary \n\n";
print " $total_dequeues messages were dequeued. Total size was $total_dequeue_size Kbytes\n";

print "\n Message sizes:\n\n";
foreach $key (@bin_names) {
printf " %12.12s: %d\n", $key, $size_bins{$key};
}

$i=1;
foreach $key (sort bycount keys %giant_messages) {
if ($i == 1) {
print "\n Top 10 sources of large messages (>1M)\n\n";
printf " %6s %57s\n", 'Originator', 'Num Enqueues';
printf " %6s %57s\n", '-'x10, '-'x12;
}
printf " %-55.55s %d\n", $key, $giant_messages{$key};
last if $i++ == 10;
}

print "\n\n Message load by channel (dequeues):\n\n";
printf "%15s %8s\n", "Channel","% of dequeues";
printf "%15s %8s\n", '-'x7, '-'x13;
foreach $key (sort {$out_messages{$b} <=> $out_messages{$a}}
keys %out_messages) {
printf "%15s %5.2f%%\n", $key, ($out_messages{$key}/$total_dequeues)*100.00;
}
printf "\n\n Message load by day (dequeues):\n\n";
printf "%15s %8s\n", "Date ","# of dequeues";
printf "%15s %8s\n", '---- ', '-'x13;
while ($tmp = pop @all_dates) {
printf "%15s %8d\n", $tmp, $dequeues_by_date{$tmp};
}

print "\n\n Message load by hour (dequeues):\n\n";
printf "%15s %8s %15s %8s\n",
"Hour ","# of dequeues","Hour ","# of dequeus";
printf "%15s %8s %15s %8s\n",
'---- ','-------------','---- ','------------';
for ($i=0; $i<12; $i++) {
printf "%10.2d %11d %17.2d %11d\n", $i, $dequeues_per_hour[$i],
$i+12, $dequeues_per_hour[$i+12];
}

printf "\n\n Message delays (time from enqueue to dequeue):\n\n";
printf " %14.14s %7.7s %7.7s %7.7s %7.7s %7.7s %7.7s %7.7s\n",
"Channel ","<2 sec ","<10 sec","<1 min ","<5 mins","<1 hour",">1 hour","Max ";
printf " %14.14s %7.7s %7.7s %7.7s %7.7s %7.7s %7.7s %7.7s\n",
'-'x14,'-'x7,'-'x7,'-'x7,'-'x7,'-'x7,'-'x7,'-'x7;
foreach $channel (sort keys %delay_bins_chan) {
printf " %14.14s", $channel;

# We print out a percentage, so total up the number of messages first
# then compute the percentage of the total in a second loop

$msg_total = 0;
foreach $key (@delay_bin_names) {
$msg_total += $delay_bins_chan{$channel}{$key};
}
foreach $key (@delay_bin_names) {
printf " %5.1f%% ", ($delay_bins_chan{$channel}{$key}/$msg_total)*100;
}
if ($max_chan_delay{$channel} > 3600) {
$max_delay_string = sprintf "%2.2f hours", $max_chan_delay{$channel}/3600;
}
elsif ($max_chan_delay{$channel} > 60) {
$max_delay_string = sprintf "%2.2f mins", $max_chan_delay{$channel}/60;
}
else {
$max_delay_string = sprintf "%d secs", $max_chan_delay{$channel};
}
print " $max_delay_string\n";
}

printf "\n\n Message transit times\n Time from enqueue to dequeue, top 20 hosts by total transit time):\n\n";
printf " %14.14s %7.7s %7.7s %7.7s %7.7s %7.7s %7.7s %7.7s\n",
"Host ","<2 sec ","<10 sec","<1 min ","<5 mins","<1 hour",">1 hour","Max ";
printf " %14.14s %7.7s %7.7s %7.7s %7.7s %7.7s %7.7s %7.7s\n",
'-'x14,'-'x7,'-'x7,'-'x7,'-'x7,'-'x7,'-'x7,'-'x7;
$i = 1;
foreach $host (sort bydelay keys %total_host_delay) {
last if $i++ == 20;
printf " %s\n %14.14s", $host, ' 'x14;
$msg_total = 0;
foreach $key (@delay_bin_names) {
$msg_total += $delay_bins_host{$host}{$key};
}
foreach $key (@delay_bin_names) {
printf " %5.1f%% ", ($delay_bins_host{$host}{$key}/$msg_total)*100;
}
if ($max_host_delay{$host} > 3600) {
$max_delay_string = sprintf "%2.2f hours", $max_host_delay{$host}/3600;
}
elsif ($max_host_delay{$host} > 60) {
$max_delay_string = sprintf "%2.2f mins", $max_host_delay{$host}/60;
}
else {
$max_delay_string = sprintf "%d secs", $max_host_delay{$host};
}
print " $max_delay_string\n";
}

print "\n\n Retry counts\n\n";
printf " %14.14s %9.9s %9.9s %9.9s %9.9s\n",
"Channel ","1st time ","2nd time ","<5 times ",">5 times ";
printf " %14.14s%9.9s %9.9s %9.9s %9.9s\n",
'-'x14,'-'x8,'-'x8,'-'x8,'-'x8;
foreach $channel (sort keys %retries_done) {
printf " %14.14s", $channel;
foreach $key (@retry_bin_names) {
printf "%9d ", $retries_done{$channel}{$key};
}
print "\n";
}

}

sub print_connection_report {

my ($key, $channel, $avg_delay, $avg_delay_string, $max_delay_string,
$i, $host, $server, $source, $hostname, $msg_total, $header_printed,
$first, $sender, $number, $tmp, $last);
my (@all_servers);
my (%printed);

print "\n\n >> Server Summary <<\n\n";

print "\n\n Connections:\n\n";
printf " %-15.15s %-6.6s %-6.6s %-8.8s %-6.6s\n", "Server", "Opened",
"Closed", "Rejected", "Failed";
printf " %-15.15s %-6.6s %-6.6s %-8.8s %-6.6s\n",
'-'x6, '-'x6, '-'x6, '-'x8, '-'x6;
foreach $key (keys %conn_open) {
next if grep /$key/, @all_servers;
push @all_servers, $key;
}
foreach $key (keys %conn_close) {
next if grep /$key/, @all_servers; # had to do this to grep
# a '+', may miss other keys?
push @all_servers, $key;
}
foreach $key (keys %conn_reject) {
next if grep /$key/, @all_servers;
push @all_servers, $key;
}
foreach $key (keys %conn_failed) {
next if grep /$key/, @all_servers;
push @all_servers, $key;
}

foreach $server (@all_servers) {
printf " %-15.15s %6d %6d %8d %6d\n", $server,
(exists $conn_open{$server}) ? $conn_open{$server} : 0,
(exists $conn_close{$server}) ? $conn_close{$server} : 0,
(exists $conn_reject{$server}) ? $conn_reject{$server} : 0,
(exists $conn_failed{$server}) ? $conn_failed{$server} : 0;
}

if ($etrn_count) {
print "\n ERTN request sources:\n\n";
printf " %6s %-17s %-48s\n",
"Count", " Source IP", "Requested Domain";
printf " %6s %-17s %-48s\n",
'-'x6, '-'x17, "-"x48;
foreach $source (sort keys %etrn_domains) {
foreach $tmp (sort keys %{$etrn_domains{$source}}) {
printf " %6s %-17s %-48s\n",
$etrn_domains{$source}{$tmp}, "[".$source."]", $tmp;
}
}
}

$first = 1;
foreach $server (sort keys %conn_open_source) {
print "\n Connection open sources:\n" if $first;
$first = 0;
print "\n $server\n";
printf " %s\n", '-' x length($server);
# if ($^O eq "VMS") {
# foreach $source (sort keys %{$conn_open_source{$server}}) {
# print " [$source]: \t$conn_open_source{$server}{$source}\n";
# }
# } else {
foreach $source (sort keys %{$conn_open_source{$server}}) {
$hostname = gethostbyip ($source);
printf (" %17.17s %-32s %7s\n", "[".$source."]", $hostname,
$conn_open_source{$server}{$source});
# }
}
}

$first = 1;
foreach $server (sort keys %conn_close_source) {
print "\n\n Connection close sources:\n" if $first;
$first = 0;
print "\n $server\n";
printf " %s\n", '-' x length($server);
# if ($^O eq "VMS") {
# foreach $source (sort keys %{$conn_close_source{$server}}) {
# print " [$source]: \t$conn_close_source{$server}{$source}\n";
# }
# } else {
foreach $source (sort keys %{$conn_close_source{$server}}) {
$hostname = gethostbyip ($source);
printf (" %17.17s %-32s %7s\n", "[".$source."]", $hostname,
$conn_close_source{$server}{$source});
# }
}
}

$first = 1;
foreach $server (sort keys %conn_reject_source) {
print "\n Connection reject sources:\n" if $first;
$first = 0;
print "\n $server\n";
printf " %s\n", '-' x length($server);
# if ($^O eq "VMS") {
# foreach $source (sort keys %{$conn_reject_source{$server}}) {
# print " [$source]: \t$conn_reject_source{$server}{$source}\n";
# }
# } else {
foreach $source (sort keys %{$conn_reject_source{$server}}) {
$hostname = gethostbyip ($source);
printf (" %17.17s %-32s %7s\n", "[".$source."]", $hostname,
$conn_reject_source{$server}{$source});
# }
}
}

$first = 1;
foreach $server (sort keys %conn_fail_source) {
print "\n Connection fail sources:\n" if $first;
$first = 0;
print "\n $server\n";
printf " %s\n", '-' x length($server);
# if ($^O eq "VMS") {
# foreach $source (sort keys %{$conn_fail_source{$server}}) {
# print " [$source]: \t$conn_fail_source{$server}{$source}\n";
# }
# } else {
foreach $source (sort keys %{$conn_fail_source{$server}}) {
$hostname = gethostbyip ($source);
printf (" %17.17s %-32s %7s\n", "[".$source."]", $hostname,
$conn_fail_source{$server}{$source});
# }
}
}
}

# Utility routines follow

# timeinsecs takes a date and time as found in the log file (nn-MMM-yyyy)
# and (hh:mm:ss) and computes a seconds since 1970 value

sub timeinsecs {
my $date = shift;
my $time = shift;
my ($hours, $mins, $secs, $day, $mon, $year, $monthnum, $computed_time);

my %monthnums = ("JAN" => 0,
"FEB" => 1,
"MAR" => 2,
"APR" => 3,
"MAY" => 4,
"JUN" => 5,
"JUL" => 6,
"AUG" => 7,
"SEP" => 8,
"OCT" => 9,
"NOV" => 10,
"DEC" => 11);

($hours, $mins, $secs) = split /:/, $processing_time;
# truncate any fractional seconds
$secs =~ s/\..*//;
($day, $mon, $year) = split /-/, $processing_date;
$monthnum = $monthnums{$mon};

if ($day < 1 or $day > 31) {
print "Found bogus day $day at line $line_count\n";
$day = 1;
}
$computed_time = timelocal($secs, $mins, $hours, $day, $monthnum, $year);

return $computed_time;
}

# gethostbyip takes a dot-separated IP address (10.20.30.40) as an
# argument and returns the associated host name

sub gethostbyip {
my $ipaddr = shift;
my ($a, $b, $c, $d, $hostname);

($a, $b, $c, $d) = split /\./, $ipaddr;
$hostname = gethostbyaddr (pack ("c4", $a,$b,$c,$d), 2);
if ($hostname) {
return $hostname;
} else {
return "Unknown";
}
}

# A couple of comparison routines to use in sorts:

sub bynum {
$bad_domains{$b} <=> $bad_domains{$a}
or
$a cmp $b;
}
sub byuse {
$mailserv_users{$b} <=> $mailserv_users{$a}
or
$a cmp $b;
}
sub bydelay {
$total_host_delay{$b} <=> $total_host_delay{$a}
or
$a cmp $b;
}
sub bycount {
$giant_messages{$b} <=> $giant_messages{$a}
or
$a cmp $b;
}
sub byfaxsource {
$fax_senders{$b} <=> $fax_senders{$a}
or
$a cmp $b;
}




Blackcomb.ca My little corner of the web

Google:
submit

Home
Josh
Work
Linux

"The secret to creativity is knowing how to hide your sources."
A. Einstein

NEWS:
New site


No endorsement or approval of any third parties or their advice, opinions, information, products or services is expressed or implied by any information on this Site or by any hyperlinks to or from any third party websites or pages. ©2004 Josh Tarchuk  |  Blackcomb.ca