180
181
# 2004-02-20 V1.31 Andrea Balzi
181
182
# Only show the Local Sender/Destination links if the tables exist.
184
# 2004-07-05 V1.32 Steve Campbell
185
# Fix '-merge -h0' divide by zero error.
187
# 2004-07-15 V1.33 Steve Campbell
188
# Documentation update - I've converted the subroutine
189
# documentation from POD to comments.
191
# 2004-12-10 V1.34 Steve Campbell
192
# Eximstats can now parse syslog lines as well as mainlog lines.
194
# 2004-12-20 V1.35 Wouter Verhelst
195
# Pie charts by volume were actually generated by count. Fixed.
197
# 2005-02-07 V1.36 Gregor Herrmann / Steve Campbell
198
# Added average sizes to HTML Top tables.
200
# 2005-04-26 V1.37 Frank Heydlauf
201
# Added -xls and the ability to specify output files.
203
# 2005-04-29 V1.38 Steve Campbell
204
# Use FileHandles for outputing results.
205
# Allow any combination of xls, txt, and html output.
206
# Fixed display of large numbers with -nvr option
207
# Fixed merging of reports with empty tables.
209
# 2005-05-27 V1.39 Steve Campbell
210
# Added the -include_original_destination flag
211
# Removed tabs and trailing whitespace.
213
# 2005-06-03 V1.40 Steve Campbell
214
# Whilst parsing the mainlog(s), store information about
215
# the messages in a hash of arrays rather than using
216
# individual hashes. This is a bit cleaner and results in
217
# dramatic memory savings, albeit at a slight CPU cost.
219
# 2005-06-15 V1.41 Steve Campbell
220
# Added the -show_rt<list> flag.
221
# Added the -show_dt<list> flag.
223
# 2005-06-24 V1.42 Steve Campbell
224
# Added Histograms for user specified patterns.
226
# 2005-06-30 V1.43 Steve Campbell
227
# Bug fix for V1.42 with -h0 specified. Spotted by Chris Lear.
230
# For documentation on the logfile format, see
231
# http://www.exim.org/exim-html-4.50/doc/html/spec_48.html#IX2793
187
eximstats - generates statistics from Exim mainlog files.
235
eximstats - generates statistics from Exim mainlog or syslog files.
191
eximstats [Options] mainlog1 mainlog2 ... > report.txt
239
eximstats [Output] [Options] mainlog1 mainlog2 ...
192
240
eximstats -merge [Options] report.1.txt report.2.txt ... > weekly_report.txt
248
Output the results in plain text to STDOUT.
250
=item B<-txt>=I<filename>
252
Output the results in plain text. Filename '-' for STDOUT is accepted.
256
Output the results in HTML to STDOUT.
258
=item B<-html>=I<filename>
260
Output the results in HTML. Filename '-' for STDOUT is accepted.
264
Output the results in Excel compatible Format to STDOUT.
265
Requires the Spreadsheet::WriteExcel CPAN module.
267
=item B<-xls>=I<filename>
269
Output the results in Excel compatible format. Filename '-' for STDOUT is accepted.
436
555
use vars qw($show_errors $show_relay $show_transport $transport_pattern);
437
556
use vars qw($topcount $local_league_table $include_remote_users);
438
557
use vars qw($hist_opt $hist_interval $hist_number $volume_rounding);
439
use vars qw($relay_pattern @queue_times $html @user_patterns @user_descriptions);
558
use vars qw($relay_pattern @queue_times @user_patterns @user_descriptions);
559
use vars qw(@rcpt_times @delivery_times);
560
use vars qw($include_original_destination);
561
use vars qw($txt_fh $htm_fh $xls_fh);
441
563
use vars qw(%do_sender); #Do sender by Host, Domain, Email, and/or Edomain tables.
442
564
use vars qw($charts $chartrel $chartdir $charts_option_specified);
443
use vars qw($merge_reports); #Merge old reports ?
565
use vars qw($merge_reports); #Merge old reports ?
445
567
# The following are modified in the parse() routine, and
446
568
# referred to in the print_*() routines.
447
use vars qw($queue_more_than $delayed_count $relayed_unshown $begin $end);
569
use vars qw($delayed_count $relayed_unshown $begin $end);
570
use vars qw(%messages $message_aref);
448
571
use vars qw(%received_count %received_data %received_data_gigs);
449
572
use vars qw(%delivered_count %delivered_data %delivered_data_gigs);
450
573
use vars qw(%received_count_user %received_data_user %received_data_gigs_user);
451
574
use vars qw(%delivered_count_user %delivered_data_user %delivered_data_gigs_user);
452
575
use vars qw(%transported_count %transported_data %transported_data_gigs);
453
use vars qw(%remote_delivered %relayed %delayed %had_error %errors_count);
454
use vars qw(@queue_bin @remote_queue_bin @received_interval_count @delivered_interval_count);
455
use vars qw(@user_pattern_totals);
576
use vars qw(%relayed %errors_count $message_errors);
577
use vars qw(@qt_all_bin @qt_remote_bin);
578
use vars qw($qt_all_overflow $qt_remote_overflow);
579
use vars qw(@dt_all_bin @dt_remote_bin %rcpt_times_bin);
580
use vars qw($dt_all_overflow $dt_remote_overflow %rcpt_times_overflow);
581
use vars qw(@received_interval_count @delivered_interval_count);
582
use vars qw(@user_pattern_totals @user_pattern_interval_count);
457
584
use vars qw(%report_totals);
587
use vars qw($SIZE $FROM_HOST $FROM_ADDRESS $ARRIVAL_TIME $REMOTE_DELIVERED $PROTOCOL);
588
use vars qw($DELAYED $HAD_ERROR);
593
$REMOTE_DELIVERED = 4;
464
602
##################################################
467
=head2 volume_rounded();
469
$rounded_volume = volume_rounded($bytes,$gigabytes);
471
Given a data size in bytes, round it to KB, MB, or GB
474
Eg 12000 => 12KB, 15000000 => 14GB, etc.
476
Note: I've experimented with Math::BigInt and it results in a 33%
477
performance degredation as opposed to storing numbers split into
604
#######################################################################
605
# get_filehandle($file,\%output_files);
606
# Return a filehandle writing to $file.
608
# If %output_files is defined, check that $output_files{$file}
609
# doesn't exist and die if it does, or set it if it doesn't.
610
#######################################################################
612
my($file,$output_files_href) = @_;
614
$file = '-' if ($file eq '');
616
if (defined $output_files_href) {
617
die "You can only output to '$file' once! Use -h for help.\n" if exists $output_files_href->{$file};
618
$output_files_href->{$file} = 1;
626
unlink $file or die "Failed to rm $file: $!";
629
my $fh = new IO::File $file, O_WRONLY|O_CREAT|O_EXCL;
630
die "new IO::File $file failed: $!" unless (defined $fh);
635
#######################################################################
638
# $rounded_volume = volume_rounded($bytes,$gigabytes);
640
# Given a data size in bytes, round it to KB, MB, or GB
643
# Eg 12000 => 12KB, 15000000 => 14GB, etc.
645
# Note: I've experimented with Math::BigInt and it results in a 33%
646
# performance degredation as opposed to storing numbers split into
647
# bytes and gigabytes.
648
#######################################################################
482
649
sub volume_rounded {
484
651
$x = 0 unless $x;
516
683
# We don't want any rounding to be done.
517
$rounded = sprintf("%4d", ($g * $gig) + $x);
684
# and we don't need broken formated output which on one hand avoids numbers from
685
# being interpreted as string by Spreadsheed Calculators, on the other hand
686
# breaks if more than 4 digits! -> flexible length instead of fixed length
687
# Format the return value at the output routine! -fh
688
#$rounded = sprintf("%d", ($g * $gig) + $x);
690
$rounded = sprintf("%.0f", ($g * $gig) + $x);
526
un_round($rounded_volume,\$bytes,\$gigabytes);
528
Given a volume in KB, MB or GB, as generated by volume_rounded(),
529
do the reverse transformation and convert it back into Bytes and Gigabytes.
530
These are added to the $bytes and $gigabytes parameters.
532
Given a data size in bytes, round it to KB, MB, or GB
535
EG: 500 => (500,0), 14GB => (0,14), etc.
697
#######################################################################
700
# un_round($rounded_volume,\$bytes,\$gigabytes);
702
# Given a volume in KB, MB or GB, as generated by volume_rounded(),
703
# do the reverse transformation and convert it back into Bytes and Gigabytes.
704
# These are added to the $bytes and $gigabytes parameters.
706
# Given a data size in bytes, round it to KB, MB, or GB
709
# EG: 500 => (500,0), 14GB => (0,14), etc.
710
#######################################################################
540
712
my($rounded,$bytes_sref,$gigabytes_sref) = @_;
644
$time = seconds($timestamp);
646
Given a time-of-day timestamp, convert it into a time() value using
647
POSIX::mktime. We expect the timestamp to be of the form
648
"$year-$mon-$day $hour:$min:$sec", with month going from 1 to 12,
649
and the year to be absolute (we do the necessary conversions). The
650
timestamp may be followed with an offset from UTC like "+$hh$mm"; if the
651
offset is not present, and we have not been told that the log is in UTC
652
(with the -utc option), then we adjust the time by the current local
653
time offset so that it can be compared with the time recorded in message
656
To improve performance, we only use mktime on the date ($year-$mon-$day),
657
and only calculate it if the date is different to the previous time we
658
came here. We then add on seconds for the '$hour:$min:$sec'.
660
We also store the results of the last conversion done, and only
661
recalculate if the date is different.
663
We used to have the '-cache' flag which would store the results of the
664
mktime() call. However, the current way of just using mktime() on the
812
#######################################################################
815
# $time = seconds($timestamp);
817
# Given a time-of-day timestamp, convert it into a time() value using
818
# POSIX::mktime. We expect the timestamp to be of the form
819
# "$year-$mon-$day $hour:$min:$sec", with month going from 1 to 12,
820
# and the year to be absolute (we do the necessary conversions). The
821
# timestamp may be followed with an offset from UTC like "+$hh$mm"; if the
822
# offset is not present, and we have not been told that the log is in UTC
823
# (with the -utc option), then we adjust the time by the current local
824
# time offset so that it can be compared with the time recorded in message
827
# To improve performance, we only use mktime on the date ($year-$mon-$day),
828
# and only calculate it if the date is different to the previous time we
829
# came here. We then add on seconds for the '$hour:$min:$sec'.
831
# We also store the results of the last conversion done, and only
832
# recalculate if the date is different.
834
# We used to have the '-cache' flag which would store the results of the
835
# mktime() call. However, the current way of just using mktime() on the
836
# date obsoletes this.
837
#######################################################################
670
839
my($timestamp) = @_;
728
=head2 calculate_localtime_offset();
730
$localtime_offset = calculate_localtime_offset();
732
Calculate the the localtime offset from gmtime in seconds.
734
$localtime = time() + $localtime_offset.
736
These are the same semantics as ISO 8601 and RFC 2822 timezone offsets.
737
(West is negative, East is positive.)
894
#######################################################################
897
# $seconds = wdhms_seconds($string);
899
# Convert a string in a week/day/hour/minute/second format (eg 4h10s)
901
#######################################################################
903
if ($_[0] =~ /^(?:(\d+)w)?(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?/) {
904
return((($1||0) * $WEEK) + (($2||0) * $DAY) + (($3||0) * $HOUR) + (($4||0) * $MINUTE) + ($5||0));
909
#######################################################################
912
# $queued = queue_time($completed_tod, $arrival_time, $id);
914
# Given the completed time of day and either the arrival time
915
# (preferred), or the message ID, calculate how long the message has
918
#######################################################################
920
my($completed_tod, $arrival_time, $id) = @_;
922
# Note: id_seconds() benchmarks as 42% slower than seconds()
923
# and computing the time accounts for a significant portion of
925
if (defined $arrival_time) {
926
return(seconds($completed_tod) - seconds($arrival_time));
929
return(seconds($completed_tod) - id_seconds($id));
934
#######################################################################
935
# calculate_localtime_offset();
937
# $localtime_offset = calculate_localtime_offset();
939
# Calculate the the localtime offset from gmtime in seconds.
941
# $localtime = time() + $localtime_offset.
943
# These are the same semantics as ISO 8601 and RFC 2822 timezone offsets.
944
# (West is negative, East is positive.)
945
#######################################################################
741
947
# $localtime = gmtime() + $localtime_offset. OLD COMMENT
742
948
# This subroutine commented out as it's not currently in use.
764
=head2 print_queue_times();
766
$time = print_queue_times($message_type,\@queue_times,$queue_more_than);
768
Given the type of messages being output, the array of message queue times,
769
and the number of messages which exceeded the queue times, print out
774
sub print_queue_times {
971
#######################################################################
972
# print_duration_table();
974
# print_duration_table($title, $message_type, \@times, \@values, $overflow);
976
# Print a table showing how long a particular step took for
977
# the messages. The parameters are:
978
# $title Eg "Time spent on the queue"
979
# $message_type Eg "Remote"
980
# \@times The maximum time a message took for it to increment
981
# the corresponding @values counter.
982
# \@values An array of message counters.
983
# $overflow The number of messages which exceeded the maximum
985
#######################################################################
986
sub print_duration_table {
776
my($string,$array,$queue_more_than) = @_;
988
my($title, $message_type, $times_aref, $values_aref, $overflow) = @_;
777
989
my(@chartdatanames);
778
990
my(@chartdatavals);
780
992
my $printed_one = 0;
781
993
my $cumulative_percent = 0;
782
#$queue_unknown += keys %arrival_time;
784
my $queue_total = $queue_more_than;
785
for ($i = 0; $i <= $#queue_times; $i++) { $queue_total += $$array[$i] }
787
my $temp = "Time spent on the queue: $string";
791
print "<hr><a name=\"$string time\"></a><h2>$temp</h2>\n";
792
print "<table border=0 width=\"100%\">\n";
794
print "<table border=1>\n";
795
print "<tr><th>Time</th><th>Messages</th><th>Percentage</th><th>Cumulative Percentage</th>\n";
796
$format = "<tr><td align=\"right\">%s %s</td><td align=\"right\">%d</td><td align=\"right\">%5.1f%%</td><td align=\"right\">%5.1f%%</td>\n";
995
my $queue_total = $overflow;
996
map {$queue_total += $_} @$values_aref;
998
my $temp = "$title: $message_type";
1001
my $txt_format = "%5s %4s %6d %5.1f%% %5.1f%%\n";
1002
my $htm_format = "<tr><td align=\"right\">%s %s</td><td align=\"right\">%d</td><td align=\"right\">%5.1f%%</td><td align=\"right\">%5.1f%%</td>\n";
1005
printf $txt_fh ("%s\n%s\n\n", $temp, "-" x length($temp)) if $txt_fh;
1007
print $htm_fh "<hr><a name=\"$title $message_type\"></a><h2>$temp</h2>\n";
1008
print $htm_fh "<table border=0 width=\"100%\">\n";
1009
print $htm_fh "<tr><td>\n";
1010
print $htm_fh "<table border=1>\n";
1011
print $htm_fh "<tr><th>Time</th><th>Messages</th><th>Percentage</th><th>Cumulative Percentage</th>\n";
800
printf("%s\n%s\n\n", $temp, "-" x length($temp));
801
$format = "%5s %4s %6d %5.1f%% %5.1f%%\n";
1016
$ws_global->write($row++, $col, "$title: ".$message_type, $f_header2);
1017
my @content=("Time", "Messages", "Percentage", "Cumulative Percentage");
1018
&set_worksheet_line($ws_global, $row++, 1, \@content, $f_headertab);
804
for ($i = 0; $i <= $#queue_times; $i++) {
1022
for ($i = 0; $i <= $#$times_aref; ++$i) {
1023
if ($$values_aref[$i] > 0)
807
my $percent = ($$array[$i] * 100)/$queue_total;
1025
my $percent = ($values_aref->[$i] * 100)/$queue_total;
808
1026
$cumulative_percent += $percent;
810
$printed_one? " " : "Under",
811
format_time($queue_times[$i]),
812
$$array[$i], $percent, $cumulative_percent);
813
if (!defined($queue_times[$i])) {
1028
my @content=($printed_one? " " : "Under",
1029
format_time($times_aref->[$i]),
1030
$values_aref->[$i], $percent, $cumulative_percent);
1033
printf $htm_fh ($htm_format, @content);
1034
if (!defined($values_aref->[$i])) {
1035
print $htm_fh "Not defined";
1039
printf $txt_fh ($txt_format, @content);
1040
if (!defined($times_aref->[$i])) {
1041
print $txt_fh "Not defined";
1047
&set_worksheet_line($ws_global, $row, 0, [@content[0,1,2]], $f_default);
1048
&set_worksheet_line($ws_global, $row++, 3, [$content[3]/100,$content[4]/100], $f_percent);
1050
if (!defined($times_aref->[$i])) {
1052
$ws_global->write($row++, $col, "Not defined" );
816
1056
push(@chartdatanames,
817
($printed_one? "" : "Under") . format_time($queue_times[$i]));
818
push(@chartdatavals, $$array[$i]);
1057
($printed_one? "" : "Under") . format_time($times_aref->[$i]));
1058
push(@chartdatavals, $$values_aref[$i]);
819
1059
$printed_one = 1;
823
if ($queue_more_than > 0) {
824
my $percent = ($queue_more_than * 100)/$queue_total;
1063
if ($overflow && $overflow > 0) {
1064
my $percent = ($overflow * 100)/$queue_total;
825
1065
$cumulative_percent += $percent;
828
format_time($queue_times[$#queue_times]),
829
$queue_more_than, $percent, $cumulative_percent);
1067
my @content = ("Over ", format_time($times_aref->[-1]),
1068
$overflow, $percent, $cumulative_percent);
1070
printf $txt_fh ($txt_format, @content) if $txt_fh;
1071
printf $htm_fh ($htm_format, @content) if $htm_fh;
1074
&set_worksheet_line($ws_global, $row, 0, [@content[0,1,2]], $f_default);
1075
&set_worksheet_line($ws_global, $row++, 3, [$content[3]/100,$content[4]/100], $f_percent);
831
push(@chartdatanames, "Over " . format_time($queue_times[$#queue_times]));
832
push(@chartdatavals, $queue_more_than);
1080
push(@chartdatanames, "Over " . format_time($times_aref->[-1]));
1081
push(@chartdatavals, $overflow);
834
1083
#printf("Unknown %6d\n", $queue_unknown) if $queue_unknown > 0;
1085
print $htm_fh "</table>\n";
1086
print $htm_fh "</td><td>\n";
839
if ($HAVE_GD_Graph_pie && $charts) {
1088
if ($HAVE_GD_Graph_pie && $charts && ($#chartdatavals > 0)) {
841
1090
\@chartdatanames,
844
1093
my $graph = GD::Graph::pie->new(200, 200);
847
if ($string =~ /all/) { $pngname = "queue_all.png"; $title = "Queue (all)"; }
848
if ($string =~ /remote/) { $pngname = "queue_rem.png"; $title = "Queue (remote)"; }
1094
my $pngname = "$title-$message_type.png";
1095
$pngname =~ s/[^\w\-\.]/_/;
1097
my $graph_title = "$title ($message_type)";
1098
$graph->set(title => $graph_title) if (length($graph_title) < 21);
852
1100
my $gd = $graph->plot(\@data) or warn($graph->error);
854
open(IMG, ">$chartdir/$pngname") or die $!;
1102
open(IMG, ">$chartdir/$pngname") or die "Could not write $chartdir/$pngname: $!\n";
856
1104
print IMG $gd->png;
858
print "<img src=\"$chartrel/$pngname\">";
1106
print $htm_fh "<img src=\"$chartrel/$pngname\">";
861
print "</td></tr></table>\n";
868
=head2 print_histogram();
870
print_histogram('Deliverieds|Messages received',@interval_count);
872
Print a histogram of the messages delivered/received per time slot
1109
print $htm_fh "</td></tr></table>\n";
1116
print $txt_fh "\n" if $txt_fh;
1117
print $htm_fh "\n" if $htm_fh;
1122
#######################################################################
1123
# print_histogram();
1125
# print_histogram('Deliveries|Messages received|$pattern', $unit, @interval_count);
1127
# Print a histogram of the messages delivered/received per time slot
1128
# (hour by default).
1129
#######################################################################
877
1130
sub print_histogram {
879
my(@interval_count) = @_;
1131
my($text, $unit, @interval_count) = @_;
880
1132
my(@chartdatanames);
881
1133
my(@chartdatavals);
1136
if (!$run_hist) # save first row of print_histogram for xls output
883
1145
for ($i = 0; $i < $hist_number; $i++)
884
1146
{ $maxd = $interval_count[$i] if $interval_count[$i] > $maxd; }
886
1148
my $scale = int(($maxd + 25)/50);
887
1149
$scale = 1 if $scale == 0;
890
if ($text eq "Deliveries")
892
$type = ($scale == 1)? "delivery" : "deliveries";
896
$type = ($scale == 1)? "message" : "messages";
899
my($title) = sprintf("$text per %s (each dot is $scale $type)",
900
($hist_interval == 60)? "hour" :
901
($hist_interval == 1)? "minute" : "$hist_interval minutes");
904
print "<hr><a name=\"$text\"></a><h2>$title</h2>\n";
905
print "<table border=0 width=\"100%\">\n";
906
print "<tr><td><pre>\n";
909
printf("%s\n%s\n\n", $title, "-" x length($title));
1152
if ($unit !~ s/y$/ies/) {
1157
# make and output title
1158
my $title = sprintf("$text per %s",
1159
($hist_interval == 60)? "hour" :
1160
($hist_interval == 1)? "minute" : "$hist_interval minutes");
1162
my $txt_htm_title = $title . " (each dot is $scale $unit)";
1164
printf $txt_fh ("%s\n%s\n\n", $txt_htm_title, "-" x length($txt_htm_title)) if $txt_fh;
1167
print $htm_fh "<hr><a name=\"$text\"></a><h2>$txt_htm_title</h2>\n";
1168
print $htm_fh "<table border=0 width=\"100%\">\n";
1169
print $htm_fh "<tr><td><pre>\n";
1174
$title =~ s/Messages/Msg/ ;
1175
$ws_global->write($row++, $col_hist+1, $title, $f_headertab);
913
1180
my $minutes = 0;
914
1181
for ($i = 0; $i < $hist_number; $i++)
916
1183
my $c = $interval_count[$i];
918
1185
# If the interval is an hour (the maximum) print the starting and
999
1299
my(@chartdatavals) = ();
1000
1300
my $chartotherval = 0;
1004
print "<hr><a name=\"$text count\"></a><h2>$temp</h2>\n";
1005
print "<table border=0 width=\"100%\">\n";
1007
print "<table border=1>\n";
1008
print "<tr><th>Messages</th><th>Bytes</th><th>\u$text</th>\n";
1303
my $txt_format = "%7d %10s %s\n";
1306
printf $txt_fh ("%s\n%s\n\n", $temp, "-" x length($temp)) if $txt_fh;
1308
print $htm_fh "<hr><a name=\"$text count\"></a><h2>$temp</h2>\n";
1309
print $htm_fh "<table border=0 width=\"100%\">\n";
1310
print $htm_fh "<tr><td>\n";
1311
print $htm_fh "<table border=1>\n";
1312
print $htm_fh "<tr><th>Messages</th><th>Bytes</th><th>Average</th><th>\u$text</th>\n";
1010
1314
# Align non-local addresses to the right (so all the .com's line up).
1011
1315
# Local addresses are aligned on the left as they are userids.
1012
1316
my $align = ($text !~ /local/i) ? 'right' : 'left';
1013
$format = "<tr><td align=\"right\">%d</td><td align=\"right\">%s</td><td align=\"$align\" nowrap>%s</td>\n";
1016
printf("%s\n%s\n\n", $temp, "-" x length($temp));
1017
$format = "%7d %10s %s\n";
1317
$htm_format = "<tr><td align=\"right\">%d</td><td align=\"right\">%s</td><td align=\"right\">%s</td><td align=\"$align\" nowrap>%s</td>\n";
1321
$ws_top50->write($row_league_table++, 0, $temp, $f_header2);
1322
&set_worksheet_line($ws_top50, $row_league_table++, 0, ["Messages", "Bytes", "Average", $text], $f_headertab );
1327
my($key,$htmlkey,$rounded_volume,$rounded_average,$count,$data,$gigs);
1021
1328
foreach $key (top_n_sort($topcount,$m_count,$m_data_gigs,$m_data)) {
1330
# When displaying the average figures, we calculate the average of
1331
# the rounded data, as the user would calculate it. This reduces
1332
# the accuracy slightly, but we have to do it this way otherwise
1333
# when using -merge to convert results from text to HTML and
1334
# vice-versa discrepencies would occur.
1335
$rounded_volume = volume_rounded($$m_data{$key},$$m_data_gigs{$key});
1337
un_round($rounded_volume,\$data,\$gigs);
1338
$count = $$m_count{$key};
1339
$rounded_average = volume_rounded($data/$count,$gigs/$count);
1340
my @content=( $count, $rounded_volume, $rounded_average);
1343
# any reason not to include rounded_average in txt-output? -fh
1344
printf $txt_fh ($txt_format, $count, $rounded_volume, $key) if $txt_fh;
1023
1347
$htmlkey = $key;
1024
1348
$htmlkey =~ s/>/\>\;/g;
1025
1349
$htmlkey =~ s/</\<\;/g;
1026
printf($format, $$m_count{$key}, volume_rounded($$m_data{$key},$$m_data_gigs{$key}), $htmlkey);
1029
printf($format, $$m_count{$key}, volume_rounded($$m_data{$key},$$m_data_gigs{$key}), $key);
1350
printf $htm_fh ($htm_format, @content, $htmlkey);
1354
&set_worksheet_line($ws_top50, $row_league_table++, 0, [@content, $key], $f_default);
1031
1357
if (scalar @chartdatanames < $ntopchart)
1033
1359
push(@chartdatanames, $key);
1034
1360
push(@chartdatavals, $$m_count{$key});
1038
1364
$chartotherval += $$m_count{$key};
1041
1368
push(@chartdatanames, "Other");
1042
1369
push(@chartdatavals, $chartotherval);
1047
print "</td><td>\n";
1048
if ($HAVE_GD_Graph_pie && $charts)
1371
print $txt_fh "\n" if $txt_fh;
1374
print $htm_fh "</table>\n";
1375
print $htm_fh "</td><td>\n";
1376
if ($HAVE_GD_Graph_pie && $charts && ($#chartdatavals > 0))
1050
1378
# calculate the graph
1063
1391
my $temp = $text;
1064
1392
$temp =~ s/ /_/g;
1065
open(IMG, ">$chartdir/${temp}_count.png") or die $!;
1393
open(IMG, ">$chartdir/${temp}_count.png") or die "Could not write $chartdir/${temp}_count.png: $!\n";
1067
1395
print IMG $gd->png;
1069
print "<img src=\"$chartrel/${temp}_count.png\">";
1397
print $htm_fh "<img src=\"$chartrel/${temp}_count.png\">";
1072
print "</td><td>\n";
1073
print "</td></tr></table>\n";
1400
print $htm_fh "</td><td>\n";
1401
print $htm_fh "</td></tr></table>\n\n";
1405
$row_league_table++;
1077
1411
$temp = "Top $name by volume";
1079
print "<hr><a name=\"$text volume\"></a><h2>$temp</h2>\n";
1080
print "<table border=0 width=\"100%\">\n";
1082
print "<table border=1>\n";
1083
print "<tr><th>Messages</th><th>Bytes</th><th>\u$text</th>\n";
1413
printf $txt_fh ("%s\n%s\n\n", $temp, "-" x length($temp)) if $txt_fh;
1415
print $htm_fh "<hr><a name=\"$text volume\"></a><h2>$temp</h2>\n";
1416
print $htm_fh "<table border=0 width=\"100%\">\n";
1417
print $htm_fh "<tr><td>\n";
1418
print $htm_fh "<table border=1>\n";
1419
print $htm_fh "<tr><th>Messages</th><th>Bytes</th><th>Average</th><th>\u$text</th>\n";
1086
printf("%s\n%s\n\n", $temp, "-" x length($temp));
1423
$ws_top50->write($row_league_table++, 0, $temp, $f_header2);
1424
&set_worksheet_line($ws_top50, $row_league_table++, 0, ["Messages", "Bytes", "Average", $text], $f_headertab);
1089
1427
@chartdatanames = ();
1090
1428
@chartdatavals = ();
1091
1429
$chartotherval = 0;
1092
1431
foreach $key (top_n_sort($topcount,$m_data_gigs,$m_data,$m_count)) {
1432
# The largest volume will be the first (top of the list).
1433
# If it has at least 1 gig, then just use gigabytes to avoid
1434
# risking an integer overflow when generating the pie charts.
1435
if ($$m_data_gigs{$key}) {
1439
$rounded_volume = volume_rounded($$m_data{$key},$$m_data_gigs{$key});
1441
un_round($rounded_volume,\$data,\$gigs);
1442
$count = $$m_count{$key};
1443
$rounded_average = volume_rounded($data/$count,$gigs/$count);
1444
my @content=($count, $rounded_volume, $rounded_average );
1447
# any reasons for not including rounded_average in the txt-version?? -fh
1448
printf $txt_fh ($txt_format, $count, $rounded_volume, $key) if $txt_fh;
1094
1450
$htmlkey = $key;
1095
1451
$htmlkey =~ s/>/\>\;/g;
1096
1452
$htmlkey =~ s/</\<\;/g;
1097
printf($format, $$m_count{$key}, volume_rounded($$m_data{$key},$$m_data_gigs{$key}), $htmlkey);
1453
printf $htm_fh ($htm_format, @content, $htmlkey);
1457
&set_worksheet_line($ws_top50, $row_league_table++, 0, [@content, $key], $f_default);
1461
if (scalar @chartdatanames < $ntopchart) {
1463
if ($$m_data_gigs{$key}) {
1464
push(@chartdatanames, $key);
1465
push(@chartdatavals, $$m_data_gigs{$key});
1469
push(@chartdatanames, $key);
1470
push(@chartdatavals, $$m_data{$key});
1100
printf($format, $$m_count{$key}, volume_rounded($$m_data{$key},$$m_data_gigs{$key}), $key);
1103
if (scalar @chartdatanames < $ntopchart)
1105
push(@chartdatanames, $key);
1106
push(@chartdatavals, $$m_count{$key});
1110
$chartotherval += $$m_count{$key};
1474
$chartotherval += ($use_gig) ? $$m_data_gigs{$key} : $$m_data{$key};
1113
1477
push(@chartdatanames, "Other");
1114
1478
push(@chartdatavals, $chartotherval);
1118
print "</td><td>\n";
1119
if ($HAVE_GD_Graph_pie && $charts) {
1480
print $txt_fh "\n" if $txt_fh;
1482
print $htm_fh "</table>\n";
1483
print $htm_fh "</td><td>\n";
1484
if ($HAVE_GD_Graph_pie && $charts && ($#chartdatavals > 0)) {
1120
1485
# calculate the graph
1122
1487
\@chartdatanames,
1125
1490
my $graph = GD::Graph::pie->new(300, 300);
1127
1492
x_label => 'Name',
1128
y_label => 'Volume',
1493
y_label => 'Volume' ,
1129
1494
title => 'By Volume',
1131
1496
my $gd = $graph->plot(\@data) or warn($graph->error);
1134
1499
$temp =~ s/ /_/g;
1135
open(IMG, ">$chartdir/${temp}_volume.png") or die $!;
1500
open(IMG, ">$chartdir/${temp}_volume.png") or die "Could not write $chartdir/${temp}_volume.png: $!\n";
1137
1502
print IMG $gd->png;
1139
print "<img src=\"$chartrel/${temp}_volume.png\">";
1504
print $htm_fh "<img src=\"$chartrel/${temp}_volume.png\">";
1142
print "</td><td>\n";
1143
print "</td></tr></table>\n";
1150
=head2 top_n_sort();
1152
@sorted_keys = top_n_sort($n,$href1,$href2,$href3);
1154
Given a hash which has numerical values, return the sorted $n keys which
1155
point to the top values. The second and third hashes are used as
1156
tiebreakers. They all must have the same keys.
1158
The idea behind this routine is that when you only want to see the
1159
top n members of a set, rather than sorting the entire set and then
1160
plucking off the top n, sort through the stack as you go, discarding
1161
any member which is lower than your current n'th highest member.
1163
This proves to be an order of magnitude faster for large hashes.
1164
On 200,000 lines of mainlog it benchmarked 9 times faster.
1165
On 700,000 lines of mainlog it benchmarked 13.8 times faster.
1167
We assume the values are > 0.
1507
print $htm_fh "</td><td>\n";
1508
print $htm_fh "</td></tr></table>\n\n";
1512
++$row_league_table;
1517
#######################################################################
1520
# @sorted_keys = top_n_sort($n,$href1,$href2,$href3);
1522
# Given a hash which has numerical values, return the sorted $n keys which
1523
# point to the top values. The second and third hashes are used as
1524
# tiebreakers. They all must have the same keys.
1526
# The idea behind this routine is that when you only want to see the
1527
# top n members of a set, rather than sorting the entire set and then
1528
# plucking off the top n, sort through the stack as you go, discarding
1529
# any member which is lower than your current n'th highest member.
1531
# This proves to be an order of magnitude faster for large hashes.
1532
# On 200,000 lines of mainlog it benchmarked 9 times faster.
1533
# On 700,000 lines of mainlog it benchmarked 13.8 times faster.
1535
# We assume the values are > 0.
1536
#######################################################################
1171
1537
sub top_n_sort {
1172
1538
my($n,$href1,$href2,$href3) = @_;
1288
Display usage instructions and exit.
1649
#######################################################################
1654
# Display usage instructions and exit.
1655
#######################################################################
1293
1657
print << "EoText";
1295
1659
eximstats Version $VERSION
1297
Usage: eximstats [Options] mainlog1 mainlog2 ... > report.txt
1298
eximstats -html [Options] mainlog1 mainlog2 ... > report.html
1299
eximstats -merge [Options] report.1.txt report.2.txt ... > weekly_rep.txt
1300
eximstats -merge -html [Options] report.1.html ... > weekly_rep.html
1302
Parses exim mainlog files and generates a statistical analysis of
1303
the messages processed. Valid options are:
1662
eximstats [Output] [Options] mainlog1 mainlog2 ...
1663
eximstats -merge -html [Options] report.1.html ... > weekly_rep.html
1666
eximstats -html=eximstats.html mainlog1 mainlog2 ...
1667
eximstats mainlog1 mainlog2 ... > report.txt
1669
Parses exim mainlog or syslog files and generates a statistical analysis
1670
of the messages processed.
1672
Valid output types are:
1673
-txt[=<file>] plain text (default unless no other type is specified)
1676
With no type and file given, defaults to -txt and STDOUT.
1305
1679
-h<number> histogram divisions per hour. The default is 1, and
1306
1680
0 suppresses histograms. Other valid values are:
1307
2, 3, 5, 10, 15, 20, 30 or 60.
1681
2, 3, 5, 10, 15, 20, 30 or 60.
1308
1682
-ne don't display error information
1309
1683
-nr don't display relaying information
1310
1684
-nr/pattern/ don't display relaying information that matches
1311
1685
-nt don't display transport information
1312
1686
-nt/pattern/ don't display transport information that matches
1313
-nvr don't do volume rounding. Display in bytes, not KB/MB/GB.
1314
-q<list> list of times for queuing information
1315
single 0 item suppresses
1687
-nvr don't do volume rounding. Display in bytes, not KB/MB/GB.
1316
1688
-t<number> display top <number> sources/destinations
1317
1689
default is 50, 0 suppresses top listing
1318
1690
-tnl omit local sources/destinations in top listing
1319
1691
-t_remote_users show top user sources/destinations from non-local domains
1321
-byhost show results by sending host (default unless bydomain or
1692
-q<list> list of times for queuing information. -q0 suppresses.
1693
-show_rt<list> Show the receipt times for all the messages.
1694
-show_dt<list> Show the delivery times for all the messages.
1695
<list> is an optional list of times in seconds.
1698
-include_original_destination show both the final and original
1699
destinations in the results rather than just the final ones.
1701
-byhost show results by sending host (default unless bydomain or
1322
1702
byemail is specified)
1323
-bydomain show results by sending domain.
1324
-byemail show results by sender's email address
1325
-byedomain show results by sender's email domain
1703
-bydomain show results by sending domain.
1704
-byemail show results by sender's email address
1705
-byedomain show results by sender's email domain
1327
1707
-pattern "Description" /pattern/
1328
1708
Count lines matching specified patterns and show them in
1329
the results. It can be specified multiple times. Eg:
1330
-pattern 'Refused connections' '/refused connection/'
1709
the results. It can be specified multiple times. Eg:
1710
-pattern 'Refused connections' '/refused connection/'
1332
1712
-merge merge previously generated reports into a new report
1334
-html output the results in HTML
1335
-charts Create charts (this requires the GD::Graph modules)
1714
-charts Create charts (this requires the GD::Graph modules).
1715
Only valid with -html.
1336
1716
-chartdir <dir> Create the charts' png files in the directory <dir>
1337
1717
-chartrel <dir> Specify the relative directory for the "img src=" tags
1338
1718
from where to include the charts in the html file
1339
-chartdir and -chartrel default to '.'
1719
-chartdir and -chartrel default to '.'
1341
-d Debug mode - dump the eval'ed parser onto STDERR.
1721
-d Debug mode - dump the eval'ed parser onto STDERR.
1350
=head2 generate_parser();
1352
$parser = generate_parser();
1354
This subroutine generates the parsing routine which will be
1355
used to parse the mainlog. We take the base operation, and remove bits not in use.
1356
This improves performance depending on what bits you take out or add.
1358
I've tested using study(), but this does not improve performance.
1360
We store our parsing routing in a variable, and process it looking for #IFDEF (Expression)
1361
or #IFNDEF (Expression) statements and corresponding #ENDIF (Expression) statements. If
1362
the expression evaluates to true, then it is included/excluded accordingly.
1730
#######################################################################
1731
# generate_parser();
1733
# $parser = generate_parser();
1735
# This subroutine generates the parsing routine which will be
1736
# used to parse the mainlog. We take the base operation, and remove bits not in use.
1737
# This improves performance depending on what bits you take out or add.
1739
# I've tested using study(), but this does not improve performance.
1741
# We store our parsing routing in a variable, and process it looking for #IFDEF (Expression)
1742
# or #IFNDEF (Expression) statements and corresponding #ENDIF (Expression) statements. If
1743
# the expression evaluates to true, then it is included/excluded accordingly.
1744
#######################################################################
1366
1745
sub generate_parser {
1368
1747
my($ip,$host,$email,$edomain,$domain,$thissize,$size,$old,$new);
1369
1748
my($tod,$m_hour,$m_min,$id,$flag);
1749
my($seconds,$queued,$rcpt_time);
1370
1750
while (<$fh>) {
1752
# Convert syslog lines to mainlog format.
1754
next unless s/^.*? exim\\b.*?: //;
1371
1757
next if length($_) < 38;
1374
# next unless /^(\\d{4}\\-\\d\\d-\\d\\d\\s(\\d\\d):(\\d\\d):\\d\\d)/;
1375
1758
next unless /^(\\d{4}\\-\\d\\d-\\d\\d\\s(\\d\\d):(\\d\\d):\\d\\d( [-+]\\d\\d\\d\\d)?)/o;
1377
1760
($tod,$m_hour,$m_min) = ($1,$2,$3);
1457
1867
if ($flag eq "<=") {
1458
1868
$thissize = (/\\sS=(\\d+)( |$)/) ? $1 : 0;
1459
$size{$id} = $thissize;
1869
$message_aref->[$SIZE] = $thissize;
1870
$message_aref->[$PROTOCOL] = (/ P=(\S+)/) ? $1 : undef;
1461
1872
#IFDEF ($show_relay)
1462
1873
if ($host ne "local") {
1463
# Save incoming information in case it becomes interesting
1464
# later, when delivery lines are read.
1465
my($from) = /^(\\S+)/;
1466
$from_host{$id} = "$host$ip";
1467
$from_address{$id} = $from;
1874
# Save incoming information in case it becomes interesting
1875
# later, when delivery lines are read.
1876
my($from) = /^(\\S+)/;
1877
$message_aref->[$FROM_HOST] = "$host$ip";
1878
$message_aref->[$FROM_ADDRESS] = $from;
1469
1880
#ENDIF ($show_relay)
1471
1882
#IFDEF ($local_league_table || $include_remote_users)
1475
#IFDEF ($local_league_table && $include_remote_users)
1476
{ #Store both local and remote users.
1477
#ENDIF ($local_league_table && $include_remote_users)
1479
#IFDEF ($local_league_table && ! $include_remote_users)
1480
if ($host eq "local") { #Store local users only.
1481
#ENDIF ($local_league_table && ! $include_remote_users)
1483
#IFDEF ($include_remote_users && ! $local_league_table)
1484
if ($host ne "local") { #Store remote users only.
1485
#ENDIF ($include_remote_users && ! $local_league_table)
1487
$received_count_user{$user}++;
1488
add_volume(\\$received_data_user{$user},\\$received_data_gigs_user{$user},$thissize);
1886
#IFDEF ($local_league_table && $include_remote_users)
1887
{ #Store both local and remote users.
1888
#ENDIF ($local_league_table && $include_remote_users)
1890
#IFDEF ($local_league_table && ! $include_remote_users)
1891
if ($host eq "local") { #Store local users only.
1892
#ENDIF ($local_league_table && ! $include_remote_users)
1894
#IFDEF ($include_remote_users && ! $local_league_table)
1895
if ($host ne "local") { #Store remote users only.
1896
#ENDIF ($include_remote_users && ! $local_league_table)
1898
++$received_count_user{$user};
1899
add_volume(\\$received_data_user{$user},\\$received_data_gigs_user{$user},$thissize);
1491
1902
#ENDIF ($local_league_table || $include_remote_users)
1493
1904
#IFDEF ($do_sender{Host})
1494
$received_count{Host}{$host}++;
1495
add_volume(\\$received_data{Host}{$host},\\$received_data_gigs{Host}{$host},$thissize);
1905
++$received_count{Host}{$host};
1906
add_volume(\\$received_data{Host}{$host},\\$received_data_gigs{Host}{$host},$thissize);
1496
1907
#ENDIF ($do_sender{Host})
1498
1909
#IFDEF ($do_sender{Domain})
1500
$received_count{Domain}{$domain}++;
1501
add_volume(\\$received_data{Domain}{$domain},\\$received_data_gigs{Domain}{$domain},$thissize);
1911
++$received_count{Domain}{$domain};
1912
add_volume(\\$received_data{Domain}{$domain},\\$received_data_gigs{Domain}{$domain},$thissize);
1503
1914
#ENDIF ($do_sender{Domain})
1505
1916
#IFDEF ($do_sender{Email})
1506
$received_count{Email}{$email}++;
1507
add_volume(\\$received_data{Email}{$email},\\$received_data_gigs{Email}{$email},$thissize);
1917
++$received_count{Email}{$email};
1918
add_volume(\\$received_data{Email}{$email},\\$received_data_gigs{Email}{$email},$thissize);
1508
1919
#ENDIF ($do_sender{Email})
1510
1921
#IFDEF ($do_sender{Edomain})
1511
$received_count{Edomain}{$edomain}++;
1512
add_volume(\\$received_data{Edomain}{$edomain},\\$received_data_gigs{Edomain}{$edomain},$thissize);
1922
++$received_count{Edomain}{$edomain};
1923
add_volume(\\$received_data{Edomain}{$edomain},\\$received_data_gigs{Edomain}{$edomain},$thissize);
1513
1924
#ENDIF ($do_sender{Edomain})
1515
$total_received_count++;
1926
++$total_received_count;
1516
1927
add_volume(\\$total_received_data,\\$total_received_data_gigs,$thissize);
1518
#IFDEF ($#queue_times >= 0)
1519
$arrival_time{$id} = $tod;
1520
#ENDIF ($#queue_times >= 0)
1929
#IFDEF ($#queue_times >= 0 || $#rcpt_times >= 0)
1930
$message_aref->[$ARRIVAL_TIME] = $tod;
1931
#ENDIF ($#queue_times >= 0 || $#rcpt_times >= 0)
1522
1933
#IFDEF ($hist_opt > 0)
1523
$received_interval_count[($m_hour*60 + $m_min)/$hist_interval]++;
1934
$received_interval_count[($m_hour*60 + $m_min)/$hist_interval]++;
1524
1935
#ENDIF ($hist_opt > 0)
1527
1938
elsif ($flag eq "=>") {
1528
$size = $size{$id} || 0;
1939
$size = $message_aref->[$SIZE] || 0;
1529
1940
if ($host ne "local") {
1530
$remote_delivered{$id} = 1;
1941
$message_aref->[$REMOTE_DELIVERED] = 1;
1533
1944
#IFDEF ($show_relay)
1565
1976
#IFDEF ($local_league_table || $include_remote_users)
1566
#IFDEF ($local_league_table && $include_remote_users)
1567
{ #Store both local and remote users.
1568
#ENDIF ($local_league_table && $include_remote_users)
1570
#IFDEF ($local_league_table && ! $include_remote_users)
1571
if ($host eq "local") { #Store local users only.
1572
#ENDIF ($local_league_table && ! $include_remote_users)
1574
#IFDEF ($include_remote_users && ! $local_league_table)
1575
if ($host ne "local") { #Store remote users only.
1576
#ENDIF ($include_remote_users && ! $local_league_table)
1578
if (my($user) = split((/\\s</)? " <" : " ", $_)) {
1579
if ($user =~ /^[\\/|]/) {
1580
my($parent) = $_ =~ /(<[^@]+@?[^>]*>)/;
1581
$user = "$user $parent" if defined $parent;
1583
$delivered_count_user{$user}++;
1584
add_volume(\\$delivered_data_user{$user},\\$delivered_data_gigs_user{$user},$size);
1977
#IFDEF ($local_league_table && $include_remote_users)
1978
{ #Store both local and remote users.
1979
#ENDIF ($local_league_table && $include_remote_users)
1981
#IFDEF ($local_league_table && ! $include_remote_users)
1982
if ($host eq "local") { #Store local users only.
1983
#ENDIF ($local_league_table && ! $include_remote_users)
1985
#IFDEF ($include_remote_users && ! $local_league_table)
1986
if ($host ne "local") { #Store remote users only.
1987
#ENDIF ($include_remote_users && ! $local_league_table)
1989
if (my($user) = split((/\\s</)? " <" : " ", $_)) {
1990
#IFDEF ($include_original_destination)
1992
#ENDIF ($include_original_destination)
1993
#IFNDEF ($include_original_destination)
1994
if ($user =~ /^[\\/|]/) {
1995
#ENDIF ($include_original_destination)
1996
my($parent) = $_ =~ /(<[^@]+@?[^>]*>)/;
1997
$user = "$user $parent" if defined $parent;
1999
++$delivered_count_user{$user};
2000
add_volume(\\$delivered_data_user{$user},\\$delivered_data_gigs_user{$user},$size);
1587
2003
#ENDIF ($local_league_table || $include_remote_users)
1589
2005
#IFDEF ($do_sender{Host})
1590
$delivered_count{Host}{$host}++;
1591
add_volume(\\$delivered_data{Host}{$host},\\$delivered_data_gigs{Host}{$host},$size);
2006
$delivered_count{Host}{$host}++;
2007
add_volume(\\$delivered_data{Host}{$host},\\$delivered_data_gigs{Host}{$host},$size);
1592
2008
#ENDIF ($do_sender{Host})
1593
2009
#IFDEF ($do_sender{Domain})
1595
$delivered_count{Domain}{$domain}++;
1596
add_volume(\\$delivered_data{Domain}{$domain},\\$delivered_data_gigs{Domain}{$domain},$size);
2011
++$delivered_count{Domain}{$domain};
2012
add_volume(\\$delivered_data{Domain}{$domain},\\$delivered_data_gigs{Domain}{$domain},$size);
1598
2014
#ENDIF ($do_sender{Domain})
1599
2015
#IFDEF ($do_sender{Email})
1600
$delivered_count{Email}{$email}++;
1601
add_volume(\\$delivered_data{Email}{$email},\\$delivered_data_gigs{Email}{$email},$size);
2016
++$delivered_count{Email}{$email};
2017
add_volume(\\$delivered_data{Email}{$email},\\$delivered_data_gigs{Email}{$email},$size);
1602
2018
#ENDIF ($do_sender{Email})
1603
2019
#IFDEF ($do_sender{Edomain})
1604
$delivered_count{Edomain}{$edomain}++;
1605
add_volume(\\$delivered_data{Edomain}{$edomain},\\$delivered_data_gigs{Edomain}{$edomain},$size);
2020
++$delivered_count{Edomain}{$edomain};
2021
add_volume(\\$delivered_data{Edomain}{$edomain},\\$delivered_data_gigs{Edomain}{$edomain},$size);
1606
2022
#ENDIF ($do_sender{Edomain})
1608
$total_delivered_count++;
2024
++$total_delivered_count;
1609
2025
add_volume(\\$total_delivered_data,\\$total_delivered_data_gigs,$size);
1611
2027
#IFDEF ($show_transport)
1612
2028
my $transport = (/\\sT=(\\S+)/) ? $1 : ":blackhole:";
1613
$transported_count{$transport}++;
2029
++$transported_count{$transport};
1614
2030
add_volume(\\$transported_data{$transport},\\$transported_data_gigs{$transport},$size);
1615
2031
#ENDIF ($show_transport)
1637
2075
elsif ($flag eq "Co") {
1639
2077
#IFDEF ($#queue_times >= 0)
1640
#Note: id_seconds() benchmarks as 42% slower than seconds() and computing
1641
#the time accounts for a significant portion of the run time.
1643
if (defined $arrival_time{$id}) {
1644
$queued = seconds($tod) - seconds($arrival_time{$id});
1645
delete($arrival_time{$id});
1648
$queued = seconds($tod) - id_seconds($id);
2078
$queued = queue_time($tod, $message_aref->[$ARRIVAL_TIME], $id);
1651
2080
for ($i = 0; $i <= $#queue_times; $i++) {
1652
2081
if ($queued < $queue_times[$i]) {
1654
$remote_queue_bin[$i]++ if $remote_delivered{$id};
2083
++$qt_remote_bin[$i] if $message_aref->[$REMOTE_DELIVERED];
1658
$queue_more_than++ if $i > $#queue_times;
2087
if ($i > $#queue_times) {
2089
++$qt_remote_overflow if $message_aref->[$REMOTE_DELIVERED];
1659
2091
#ENDIF ($#queue_times >= 0)
1661
#IFDEF ($show_relay)
1662
delete($from_host{$id});
1663
delete($from_address{$id});
1664
#ENDIF ($show_relay)
2093
#IFDEF ($#rcpt_times >= 0)
2095
$seconds = wdhms_seconds($1);
2096
#Calculate $queued if not previously calculated above.
2097
#IFNDEF ($#queue_times >= 0)
2098
$queued = queue_time($tod, $message_aref->[$ARRIVAL_TIME], $id);
2099
#ENDIF ($#queue_times >= 0)
2100
$rcpt_time = $seconds - $queued;
2103
if (defined $message_aref->[$PROTOCOL]) {
2104
$protocol = $message_aref->[$PROTOCOL];
2106
# Create the bin if its not already defined.
2107
unless (exists $rcpt_times_bin{$protocol}) {
2108
initialise_rcpt_times($protocol);
2113
for ($i = 0; $i <= $#rcpt_times; ++$i) {
2114
if ($rcpt_time < $rcpt_times[$i]) {
2115
++$rcpt_times_bin{all}[$i];
2116
++$rcpt_times_bin{$protocol}[$i] if defined $protocol;
2121
if ($i > $#rcpt_times) {
2122
++$rcpt_times_overflow{all};
2123
++$rcpt_times_overflow{$protocol} if defined $protocol;
2126
#ENDIF ($#rcpt_times >= 0)
2128
delete($messages{$id});
1719
=head2 print_header();
1723
Print our headers and contents.
2187
#######################################################################
2192
# Print our headers and contents.
2193
#######################################################################
1727
2194
sub print_header {
1729
2197
my $title = "Exim statistics from $begin to $end";
1732
print html_header($title);
1734
print "<li><a href=\"#grandtotal\">Grand total summary</a>\n";
1735
print "<li><a href=\"#patterns\">User Specified Patterns</a>\n" if @user_patterns;
1736
print "<li><a href=\"#transport\">Deliveries by Transport</a>\n" if $show_transport;
2199
print $txt_fh "\n$title\n" if $txt_fh;
2201
print $htm_fh html_header($title);
2202
print $htm_fh "<ul>\n";
2203
print $htm_fh "<li><a href=\"#grandtotal\">Grand total summary</a>\n";
2204
print $htm_fh "<li><a href=\"#patterns\">User Specified Patterns</a>\n" if @user_patterns;
2205
print $htm_fh "<li><a href=\"#transport\">Deliveries by Transport</a>\n" if $show_transport;
1737
2206
if ($hist_opt) {
1738
print "<li><a href=\"#Messages received\">Messages received per hour</a>\n";
1739
print "<li><a href=\"#Deliveries\">Deliveries per hour</a>\n";
2207
print $htm_fh "<li><a href=\"#Messages received\">Messages received per hour</a>\n";
2208
print $htm_fh "<li><a href=\"#Deliveries\">Deliveries per hour</a>\n";
1741
2211
if ($#queue_times >= 0) {
1742
print "<li><a href=\"#all messages time\">Time spent on the queue: all messages</a>\n";
1743
print "<li><a href=\"#messages with at least one remote delivery time\">Time spent on the queue: messages with at least one remote delivery</a>\n";
1745
print "<li><a href=\"#Relayed messages\">Relayed messages</a>\n" if $show_relay;
2212
print $htm_fh "<li><a href=\"#Time spent on the queue all messages\">Time spent on the queue: all messages</a>\n";
2213
print $htm_fh "<li><a href=\"#Time spent on the queue messages with at least one remote delivery\">Time spent on the queue: messages with at least one remote delivery</a>\n";
2216
if ($#delivery_times >= 0) {
2217
print $htm_fh "<li><a href=\"#Delivery times all messages\">Delivery times: all messages</a>\n";
2218
print $htm_fh "<li><a href=\"#Delivery times messages with at least one remote delivery\">Delivery times: messages with at least one remote delivery</a>\n";
2221
if ($#rcpt_times >= 0) {
2222
print $htm_fh "<li><a href=\"#Receipt times all messages\">Receipt times</a>\n";
2225
print $htm_fh "<li><a href=\"#Relayed messages\">Relayed messages</a>\n" if $show_relay;
1746
2226
if ($topcount) {
1747
2227
foreach ('Host','Domain','Email','Edomain') {
1748
next unless $do_sender{$_};
1749
print "<li><a href=\"#sending \l$_ count\">Top $topcount sending \l${_}s by message count</a>\n";
1750
print "<li><a href=\"#sending \l$_ volume\">Top $topcount sending \l${_}s by volume</a>\n";
2228
next unless $do_sender{$_};
2229
print $htm_fh "<li><a href=\"#sending \l$_ count\">Top $topcount sending \l${_}s by message count</a>\n";
2230
print $htm_fh "<li><a href=\"#sending \l$_ volume\">Top $topcount sending \l${_}s by volume</a>\n";
1752
2232
if ($local_league_table || $include_remote_users) {
1753
print "<li><a href=\"#local sender count\">Top $topcount local senders by message count</a>\n";
1754
print "<li><a href=\"#local sender volume\">Top $topcount local senders by volume</a>\n";
2233
print $htm_fh "<li><a href=\"#local sender count\">Top $topcount local senders by message count</a>\n";
2234
print $htm_fh "<li><a href=\"#local sender volume\">Top $topcount local senders by volume</a>\n";
1756
2236
foreach ('Host','Domain','Email','Edomain') {
1757
next unless $do_sender{$_};
1758
print "<li><a href=\"#\l$_ destination count\">Top $topcount \l$_ destinations by message count</a>\n";
1759
print "<li><a href=\"#\l$_ destination volume\">Top $topcount \l$_ destinations by volume</a>\n";
2237
next unless $do_sender{$_};
2238
print $htm_fh "<li><a href=\"#\l$_ destination count\">Top $topcount \l$_ destinations by message count</a>\n";
2239
print $htm_fh "<li><a href=\"#\l$_ destination volume\">Top $topcount \l$_ destinations by volume</a>\n";
1761
2241
if ($local_league_table || $include_remote_users) {
1762
print "<li><a href=\"#local destination count\">Top $topcount local destinations by message count</a>\n";
1763
print "<li><a href=\"#local destination volume\">Top $topcount local destinations by volume</a>\n";
2242
print $htm_fh "<li><a href=\"#local destination count\">Top $topcount local destinations by message count</a>\n";
2243
print $htm_fh "<li><a href=\"#local destination volume\">Top $topcount local destinations by volume</a>\n";
1766
print "<li><a href=\"#errors\">List of errors</a>\n" if %errors_count;
1767
print "</ul>\n<hr>\n";
2246
print $htm_fh "<li><a href=\"#errors\">List of errors</a>\n" if %errors_count;
2247
print $htm_fh "</ul>\n<hr>\n";
2251
$ws_global->write($row++, $col+0, "Exim Statistics", $f_header1);
2252
&set_worksheet_line($ws_global, $row, $col, ["from:", $begin, "to:", $end], $f_default);
1776
=head2 print_grandtotals();
1778
print_grandtotals();
1780
Print the grand totals.
2258
#######################################################################
2259
# print_grandtotals();
2261
# print_grandtotals();
2263
# Print the grand totals.
2264
#######################################################################
1784
2265
sub print_grandtotals {
1786
2267
# Get the sender by headings and results. This is complicated as we can have
1787
2268
# different numbers of columns.
1788
2269
my($sender_txt_header,$sender_html_header,$sender_txt_format,$sender_html_format);
1789
2270
my(@received_totals,@delivered_totals);
2271
my($row_tablehead, $row_max);
1790
2273
foreach ('Host','Domain','Email','Edomain') {
1791
2274
next unless $do_sender{$_};
1792
2275
if ($merge_reports) {
1803
2286
$sender_txt_format .= " " x ($COLUMN_WIDTHS - 5) . "%6d";
1806
my($format1,$format2);
1809
<a name="grandtotal"></a>
1810
<h2>Grand total summary</h2>
1812
<tr><th>TOTAL</th><th>Volume</th><th>Messages</th>$sender_html_header<th colspan=2>At least one addr<br>Delayed</th><th colspan=2>At least one addr<br>Failed</th>
2289
my $txt_format1 = " %-16s %9s %6d $sender_txt_format";
2290
my $txt_format2 = " %6d %4.1f%% %6d %4.1f%%",
2291
my $htm_format1 = "<tr><td>%s</td><td align=\"right\">%s</td>$sender_html_format<td align=\"right\">%d</td>";
2292
my $htm_format2 = "<td align=\"right\">%d</td><td align=\"right\">%4.1f%%</td><td align=\"right\">%d</td><td align=\"right\">%4.1f%%</td>";
1815
$format1 = "<tr><td>%s</td><td align=\"right\">%s</td>$sender_html_format<td align=\"right\">%d</td>";
1816
$format2 = "<td align=\"right\">%d</td><td align=\"right\">%4.1f%%</td><td align=\"right\">%d</td><td align=\"right\">%4.1f%%</td>";
1819
2295
my $sender_spaces = " " x length($sender_txt_header);
1824
$sender_spaces At least one address
1825
TOTAL Volume Messages $sender_txt_header Delayed Failed
1827
$format1 = " %-16s %9s %6d $sender_txt_format";
1828
$format2 = " %6d %4.1f%% %6d %4.1f%%",
2297
print $txt_fh "Grand total summary\n";
2298
print $txt_fh "-------------------\n";
2299
print $txt_fh " $sender_spaces At least one address\n";
2300
print $txt_fh " TOTAL Volume Messages $sender_txt_header Delayed Failed\n";
2303
print $htm_fh "<a name=\"grandtotal\"></a>\n";
2304
print $htm_fh "<h2>Grand total summary</h2>\n";
2305
print $htm_fh "<table border=1>\n";
2306
print $htm_fh "<tr><th>TOTAL</th><th>Volume</th><th>Messages</th>$sender_html_header<th colspan=2>At least one addr<br>Delayed</th><th colspan=2>At least one addr<br>Failed</th>\n";
2310
$ws_global->write($row++, $col, "Grand total summary", $f_header2);
2312
$row_tablehead = $row+1; # header-row of TOTALS table
2314
&set_worksheet_line($ws_global, $row_tablehead, 0, ['Received', 'Delivered', 'TOTAL'], $f_headertab);
2320
"At least one address Delayed (Total)",
2321
"At least one address Delayed (Percent)",
2322
"At least one address Failed (Total)",
2323
"At least one address Failed (Percent)"
2326
for (my $i=0; $i < scalar(@content); $i++)
2328
$ws_global->write($row_tablehead+$i+1, 2, $content[$i], $f_default);
2331
$row_max = $row_tablehead+scalar(@content)+2; # continue from this row
1831
2336
my($volume,$failed_count);
1832
2337
if ($merge_reports) {
1857
2381
$volume = volume_rounded($total_delivered_data, $total_delivered_data_gigs);
1859
printf("$format1\n\n",'Delivered',$volume,$total_delivered_count,@delivered_totals);
1860
print "</table>\n" if $html;
2384
my @content=($volume, $total_delivered_count, @delivered_totals);
2385
printf $txt_fh ("$txt_format1\n\n", 'Delivered', @content) if $txt_fh;
2386
printf $htm_fh ("$htm_format1\n\n", 'Delivered', @content) if $htm_fh;
2387
printf $htm_fh "</table>\n" if $htm_fh;
2391
$row = $row_tablehead+1;
2392
for (my $i=0; $i < scalar(@content); $i++)
2394
$ws_global->write($row+$i, 1, $content[$i], $f_default);
1864
=head2 print_user_patterns()
1866
print_user_patterns();
1868
Print the counts of user specified patterns.
2401
#######################################################################
2402
# print_user_patterns()
2404
# print_user_patterns();
2406
# Print the counts of user specified patterns.
2407
#######################################################################
1872
2408
sub print_user_patterns {
1876
print "<hr><a name=\"patterns\"></a><h2>User Specified Patterns</h2>\n";
1877
print "<table border=0 width=\"100%\">\n";
1879
print "<table border=1>\n";
1880
print "<tr><th> </th><th>Total</th>\n";
1881
$format1 = "<tr><td>%s</td><td align=\"right\">%d</td>";
1884
print "User Specified Patterns\n";
1885
print "-----------------------";
1887
$format1 = " %-18s %6d";
2409
my $txt_format1 = " %-18s %6d";
2410
my $htm_format1 = "<tr><td>%s</td><td align=\"right\">%d</td>";
2413
print $txt_fh "User Specified Patterns\n";
2414
print $txt_fh "-----------------------";
2415
print $txt_fh "\n Total\n";
2418
print $htm_fh "<hr><a name=\"patterns\"></a><h2>User Specified Patterns</h2>\n";
2419
print $htm_fh "<table border=0 width=\"100%\">\n";
2420
print $htm_fh "<tr><td>\n";
2421
print $htm_fh "<table border=1>\n";
2422
print $htm_fh "<tr><th> </th><th>Total</th>\n";
2425
$ws_global->write($row++, $col, "User Specified Patterns", $f_header2);
2426
&set_worksheet_line($ws_global, $row++, 1, ["Total"], $f_headertab);
1891
2431
if ($merge_reports) {
1892
2432
# We are getting our data from previous reports.
1893
2433
foreach $key (@user_descriptions) {
1894
2434
my $count = get_report_total($report_totals{patterns}{$key},'Total');
1895
printf("$format1\n",$key,$count);
2435
printf $txt_fh ("$txt_format1\n",$key,$count) if $txt_fh;
2436
printf $htm_fh ("$htm_format1\n",$key,$count) if $htm_fh;
2439
&set_worksheet_line($ws_global, $row++, 0, [$key,$count], $f_default);
1899
2444
# We are getting our data from mainlog files.
1900
2445
my $user_pattern_index = 0;
1901
2446
foreach $key (@user_descriptions) {
1902
printf("$format1\n",$key,$user_pattern_totals[$user_pattern_index]);
1903
$user_pattern_index++;
2447
printf $txt_fh ("$txt_format1\n",$key,$user_pattern_totals[$user_pattern_index]) if $txt_fh;
2448
printf $htm_fh ("$htm_format1\n",$key,$user_pattern_totals[$user_pattern_index]) if $htm_fh;
2451
&set_worksheet_line($ws_global, $row++, 0, [$key,$user_pattern_totals[$user_pattern_index]]);
2453
$user_pattern_index++;
2456
print $txt_fh "\n" if $txt_fh;
2457
print $htm_fh "</table>\n\n" if $htm_fh;
2463
if ($hist_opt > 0) {
2464
my $user_pattern_index = 0;
2465
foreach $key (@user_descriptions) {
2466
print_histogram($key, 'occurence', @{$user_pattern_interval_count[$user_pattern_index]});
2467
$user_pattern_index++;
1913
=head2 print_transport();
1917
Print totals by transport.
2473
#######################################################################
2474
# print_transport();
2476
# print_transport();
2478
# Print totals by transport.
2479
#######################################################################
1921
2480
sub print_transport {
1923
2481
my(@chartdatanames);
1924
2482
my(@chartdatavals_count);
1925
2483
my(@chartdatavals_vol);
1926
no integer; #Lose this for charting the data.
1929
print "<hr><a name=\"transport\"></a><h2>Deliveries by Transport</h2>\n";
1930
print "<table border=0 width=\"100%\">\n";
1932
print "<table border=1>\n";
1933
print "<tr><th> </th><th>Volume</th><th>Messages</th>\n";
1934
$format1 = "<tr><td>%s</td><td align=\"right\">%s</td><td align=\"right\">%d</td>";
1937
print "Deliveries by transport\n";
1938
print "-----------------------";
1939
print "\n Volume Messages\n";
1940
$format1 = " %-18s %6s %6d";
2484
no integer; #Lose this for charting the data.
2486
my $txt_format1 = " %-18s %6s %6d";
2487
my $htm_format1 = "<tr><td>%s</td><td align=\"right\">%s</td><td align=\"right\">%d</td>";
2490
print $txt_fh "Deliveries by transport\n";
2491
print $txt_fh "-----------------------";
2492
print $txt_fh "\n Volume Messages\n";
2495
print $htm_fh "<hr><a name=\"transport\"></a><h2>Deliveries by Transport</h2>\n";
2496
print $htm_fh "<table border=0 width=\"100%\">\n";
2497
print $htm_fh "<tr><td>\n";
2498
print $htm_fh "<table border=1>\n";
2499
print $htm_fh "<tr><th> </th><th>Volume</th><th>Messages</th>\n";
2502
$ws_global->write($row++, $col, "Deliveries by transport", $f_header2);
2503
&set_worksheet_line($ws_global, $row++, 1, ["Volume", "Messages"], $f_headertab);
1945
2508
# We are getting our data from previous reports.
1946
2509
foreach $key (sort keys %{$report_totals{transport}}) {
1947
2510
my $count = get_report_total($report_totals{transport}{$key},'Messages');
1948
printf("$format1\n",$key,
1949
volume_rounded($report_totals{transport}{$key}{Volume},$report_totals{transport}{$key}{'Volume-gigs'}),
2511
my @content=($key, volume_rounded($report_totals{transport}{$key}{Volume},
2512
$report_totals{transport}{$key}{'Volume-gigs'}), $count);
1951
2513
push(@chartdatanames, $key);
1952
2514
push(@chartdatavals_count, $count);
1953
2515
push(@chartdatavals_vol, $report_totals{transport}{$key}{'Volume-gigs'}*$gig + $report_totals{transport}{$key}{Volume} );
2516
printf $txt_fh ("$txt_format1\n", @content) if $txt_fh;
2517
printf $htm_fh ("$htm_format1\n", @content) if $htm_fh;
2519
&set_worksheet_line($ws_global, $row++, 0, \@content, $f_default);
1957
2524
# We are getting our data from mainlog files.
1958
2525
foreach $key (sort keys %transported_data) {
1959
printf("$format1\n",$key,
1960
volume_rounded($transported_data{$key},$transported_data_gigs{$key}),
1961
$transported_count{$key});
2526
my @content=($key, volume_rounded($transported_data{$key},$transported_data_gigs{$key}),
2527
$transported_count{$key});
1962
2528
push(@chartdatanames, $key);
1963
2529
push(@chartdatavals_count, $transported_count{$key});
1964
2530
push(@chartdatavals_vol, $transported_data_gigs{$key}*$gig + $transported_data{$key});
2531
printf $txt_fh ("$txt_format1\n", @content) if $txt_fh;
2532
printf $htm_fh ("$htm_format1\n", @content) if $htm_fh;
2534
&set_worksheet_line($ws_global, $row++, 0, \@content);
1969
print "</td><td>\n";
1970
if ($HAVE_GD_Graph_pie && $charts)
2538
print $txt_fh "\n" if $txt_fh;
2540
print $htm_fh "</table>\n";
2541
print $htm_fh "</td><td>\n";
2542
if ($HAVE_GD_Graph_pie && $charts && ($#chartdatavals_count > 0))
1972
2544
# calculate the graph
2003
2575
my $gd = $graph->plot(\@data) or warn($graph->error);
2005
open(IMG, ">$chartdir/transports_vol.png") or die $!;
2009
print "<img src=\"$chartrel/transports_vol.png\">";
2577
open(IMG, ">$chartdir/transports_vol.png") or die "Could not write $chartdir/transports_count.png: $!\n";
2581
print $htm_fh "<img src=\"$chartrel/transports_vol.png\">";
2012
print "</td></tr></table>\n";
2584
print $htm_fh "</td></tr></table>\n\n";
2019
=head2 print_relay();
2023
Print our totals by relay.
2594
#######################################################################
2599
# Print our totals by relay.
2600
#######################################################################
2027
2601
sub print_relay {
2602
my $row_print_relay=1;
2028
2603
my $temp = "Relayed messages";
2029
print "<hr><a name=\"$temp\"></a><h2>$temp</h2>\n" if $html;
2604
print $htm_fh "<hr><a name=\"$temp\"></a><h2>$temp</h2>\n" if $htm_fh;
2030
2605
if (scalar(keys %relayed) > 0 || $relayed_unshown > 0) {
2032
2607
my $spacing = "";
2036
print "<table border=1>\n";
2037
print "<tr><th>Count</th><th>From</th><th>To</th>\n";
2038
$format = "<tr><td align=\"right\">%d</td><td>%s</td><td>%s</td>\n";
2041
printf("%s\n%s\n\n", $temp, "-" x length($temp));
2042
$format = "%7d %s\n => %s\n";
2608
my $txt_format = "%7d %s\n => %s\n";
2609
my $htm_format = "<tr><td align=\"right\">%d</td><td>%s</td><td>%s</td>\n";
2611
printf $txt_fh ("%s\n%s\n\n", $temp, "-" x length($temp)) if $txt_fh;
2613
print $htm_fh "<table border=1>\n";
2614
print $htm_fh "<tr><th>Count</th><th>From</th><th>To</th>\n";
2617
$ws_relayed->write($row_print_relay++, $col, $temp, $f_header2);
2618
&set_worksheet_line($ws_relayed, $row_print_relay++, 0, ["Count", "From", "To"], $f_headertab);
2046
2623
foreach $key (sort keys %relayed) {
2048
2625
$shown += $count;
2049
2626
$key =~ s/[HA]=//g;
2050
2627
my($one,$two) = split(/=> /, $key);
2051
printf($format, $count, $one, $two);
2628
my @content=($count, $one, $two);
2629
printf $txt_fh ($txt_format, @content) if $txt_fh;
2630
printf $htm_fh ($htm_format, @content) if $htm_fh;
2633
&set_worksheet_line($ws_relayed, $row_print_relay++, 0, \@content);
2052
2635
$spacing = "\n";
2054
print "</table>\n<p>\n" if $html;
2055
print "${spacing}Total: $shown (plus $relayed_unshown unshown)\n";
2638
print $htm_fh "</table>\n<p>\n" if $htm_fh;
2639
print $txt_fh "${spacing}Total: $shown (plus $relayed_unshown unshown)\n\n" if $txt_fh;
2640
print $htm_fh "${spacing}Total: $shown (plus $relayed_unshown unshown)\n\n" if $htm_fh;
2643
&set_worksheet_line($ws_relayed, $row_print_relay++, 0, [$shown, "Sum of shown" ]);
2644
&set_worksheet_line($ws_relayed, $row_print_relay++, 0, [$relayed_unshown, "unshown"]);
2058
print "No relayed messages\n";
2059
print "-------------------\n" unless $html;
2649
print $txt_fh "No relayed messages\n-------------------\n\n" if $txt_fh;
2650
print $htm_fh "No relayed messages\n\n" if $htm_fh;
2066
=head2 print_errors();
2070
Print our errors. In HTML, we display them as a list rather than a table -
2071
Netscape doesn't like large tables!
2660
#######################################################################
2665
# Print our errors. In HTML, we display them as a list rather than a table -
2666
# Netscape doesn't like large tables!
2667
#######################################################################
2075
2668
sub print_errors {
2076
2669
my $total_errors = 0;
2078
2672
if (scalar(keys %errors_count) != 0) {
2079
2673
my $temp = "List of errors";
2082
print "<hr><a name=\"errors\"></a><h2>$temp</h2>\n";
2083
print "<ul><li><b>Count - Error</b>\n";
2084
$format = "<li>%d - %s\n";
2087
printf("%s\n%s\n\n", $temp, "-" x length($temp));
2674
my $htm_format = "<li>%d - %s\n";
2676
printf $txt_fh ("%s\n%s\n\n", $temp, "-" x length($temp)) if $txt_fh;
2678
print $htm_fh "<hr><a name=\"errors\"></a><h2>$temp</h2>\n";
2679
print $htm_fh "<ul><li><b>Count - Error</b>\n";
2683
$ws_errors->write($row++, 0, $temp, $f_header2);
2684
&set_worksheet_line($ws_errors, $row++, 0, ["Count", "Error"], $f_headertab);
2091
2689
foreach $key (sort keys %errors_count) {
2092
2690
my $text = $key;
2094
$text =~ s/\s\s+/ /g; #Convert multiple spaces to a single space.
2692
$text =~ s/\s\s+/ /g; #Convert multiple spaces to a single space.
2095
2693
$total_errors += $errors_count{$key};
2696
printf $txt_fh ("%5d ", $errors_count{$key});
2697
my $text_remaining = $text;
2698
while (length($text_remaining) > 65) {
2699
my($first,$rest) = $text_remaining =~ /(.{50}\S*)\s+(.+)/;
2701
printf $txt_fh ("%s\n\t ", $first);
2702
$text_remaining = $rest;
2704
printf $txt_fh ("%s\n\n", $text_remaining);
2098
2709
#Translate HTML tag characters. Sergey Sholokh.
2099
2710
$text =~ s/\</\<\;/g;
2100
2711
$text =~ s/\>/\>\;/g;
2102
printf($format,$errors_count{$key},$text);
2713
printf $htm_fh ($htm_format,$errors_count{$key},$text);
2105
printf("%5d ", $errors_count{$key});
2106
while (length($text) > 65) {
2107
my($first,$rest) = $text =~ /(.{50}\S*)\s+(.+)/;
2109
printf("%s\n ", $first);
2112
printf("%s\n\n", $text);
2717
&set_worksheet_line($ws_errors, $row++, 0, [$errors_count{$key},$text]);
2115
print "</ul>\n<p>\n" if $html;
2117
2721
$temp = "Errors encountered: $total_errors";
2119
print "-" x length($temp),"\n" unless $html;
2724
print $txt_fh $temp, "\n";
2725
print $txt_fh "-" x length($temp),"\n";
2728
print $htm_fh "</ul>\n<p>\n";
2729
print $htm_fh $temp, "\n";
2733
&set_worksheet_line($ws_errors, $row++, 0, [$total_errors, "Sum of Errors encountered"]);
2125
=head2 parse_old_eximstat_reports();
2127
parse_old_eximstat_reports($fh);
2129
Parse old eximstat output so we can merge daily stats to weekly stats and weekly to monthly etc.
2131
To test that the merging still works after changes, do something like the following.
2132
All the diffs should produce no output.
2134
options='-bydomain -byemail -byhost -byedomain'
2135
options="$options -pattern 'Completed Messages' /Completed/"
2136
options="$options -pattern 'Received Messages' /<=/"
2138
./eximstats $options mainlog > mainlog.txt
2139
./eximstats $options -merge mainlog.txt > mainlog.2.txt
2140
diff mainlog.txt mainlog.2.txt
2142
./eximstats $options -html mainlog > mainlog.html
2143
./eximstats $options -merge -html mainlog.txt > mainlog.2.html
2144
diff mainlog.html mainlog.2.html
2146
./eximstats $options -merge mainlog.html > mainlog.3.txt
2147
diff mainlog.txt mainlog.3.txt
2149
./eximstats $options -merge -html mainlog.html > mainlog.3.html
2150
diff mainlog.html mainlog.3.html
2152
./eximstats $options -nvr mainlog > mainlog.nvr.txt
2153
./eximstats $options -merge mainlog.nvr.txt > mainlog.4.txt
2154
diff mainlog.txt mainlog.4.txt
2156
# double_mainlog.txt should have twice the values that mainlog.txt has.
2157
./eximstats $options mainlog mainlog > double_mainlog.txt
2740
#######################################################################
2741
# parse_old_eximstat_reports();
2743
# parse_old_eximstat_reports($fh);
2745
# Parse old eximstat output so we can merge daily stats to weekly stats and weekly to monthly etc.
2747
# To test that the merging still works after changes, do something like the following.
2748
# All the diffs should produce no output.
2750
# options='-bydomain -byemail -byhost -byedomain'
2751
# options="$options -show_rt1,2,4 -show_dt 1,2,4"
2752
# options="$options -pattern 'Completed Messages' /Completed/"
2753
# options="$options -pattern 'Received Messages' /<=/"
2755
# ./eximstats $options mainlog > mainlog.txt
2756
# ./eximstats $options -merge mainlog.txt > mainlog.2.txt
2757
# diff mainlog.txt mainlog.2.txt
2759
# ./eximstats $options -html mainlog > mainlog.html
2760
# ./eximstats $options -merge -html mainlog.txt > mainlog.2.html
2761
# diff mainlog.html mainlog.2.html
2763
# ./eximstats $options -merge mainlog.html > mainlog.3.txt
2764
# diff mainlog.txt mainlog.3.txt
2766
# ./eximstats $options -merge -html mainlog.html > mainlog.3.html
2767
# diff mainlog.html mainlog.3.html
2769
# ./eximstats $options -nvr mainlog > mainlog.nvr.txt
2770
# ./eximstats $options -merge mainlog.nvr.txt > mainlog.4.txt
2771
# diff mainlog.txt mainlog.4.txt
2773
# # double_mainlog.txt should have twice the values that mainlog.txt has.
2774
# ./eximstats $options mainlog mainlog > double_mainlog.txt
2775
#######################################################################
2161
2776
sub parse_old_eximstat_reports {
2164
2779
my(%league_table_value_entered, %league_table_value_was_zero, %table_order);
2781
my(%user_pattern_index);
2782
my $user_pattern_index = 0;
2783
map {$user_pattern_index{$_} = $user_pattern_index++} @user_descriptions;
2784
my $user_pattern_keys = join('|', @user_descriptions);
2166
2786
while (<$fh>) {
2787
PARSE_OLD_REPORT_LINE:
2167
2788
if (/Exim statistics from ([\d\-]+ [\d:]+(\s+[\+\-]\d+)?) to ([\d\-]+ [\d:]+(\s+[\+\-]\d+)?)/) {
2168
2789
$begin = $1 if ($1 lt $begin);
2169
2790
$end = $3 if ($3 gt $end);
2217
2844
# address_pipe 655KB 1
2218
2845
# smtp 11MB 151
2220
while (<$fh>) { last if (/Volume/); } #Wait until we get the table headers.
2222
print STDERR "Parsing $_" if $debug;
2223
$_ = html2txt($_); #Convert general HTML markup to text.
2224
if (/(\S+)\s+(\d+\S*\s+\d+)/) {
2225
$report_totals{transport}{$1} = {} unless (defined $report_totals{transport}{$1});
2226
add_to_totals($report_totals{transport}{$1},['Volume','Messages'],$2);
2228
last if (/^\s*$/); #Finished if we have a blank line.
2231
elsif (/(Messages received|Deliveries) per/) {
2232
# Messages received per hour (each dot is 2 messages)
2233
#---------------------------------------------------
2235
#00-01 106 .....................................................
2236
#01-02 103 ...................................................
2238
# Set a pointer to the interval array so we can use the same code
2239
# block for both messages received and delivered.
2240
my $interval_aref = ($1 eq 'Deliveries') ? \@delivered_interval_count : \@received_interval_count;
2241
my $reached_table = 0;
2243
$reached_table = 1 if (/^00/);
2244
next unless $reached_table;
2245
print STDERR "Parsing $_" if $debug;
2246
if (/^(\d+):(\d+)\s+(\d+)/) { #hh:mm start time format ?
2247
$$interval_aref[($1*60 + $2)/$hist_interval] += $3;
2249
elsif (/^(\d+)-(\d+)\s+(\d+)/) { #hh-hh start-end time format ?
2250
$$interval_aref[($1*60)/$hist_interval] += $3;
2252
else { #Finished the table ?
2258
elsif (/Time spent on the queue: (all messages|messages with at least one remote delivery)/) {
2847
while (<$fh>) { last if (/Volume/); } #Wait until we get the table headers.
2849
print STDERR "Parsing $_" if $debug;
2850
$_ = html2txt($_); #Convert general HTML markup to text.
2851
if (/(\S+)\s+(\d+\S*\s+\d+)/) {
2852
$report_totals{transport}{$1} = {} unless (defined $report_totals{transport}{$1});
2853
add_to_totals($report_totals{transport}{$1},['Volume','Messages'],$2);
2855
last if (/^\s*$/); #Finished if we have a blank line.
2858
elsif (/Messages received per/) {
2859
parse_histogram($fh, \@received_interval_count);
2861
elsif (/Deliveries per/) {
2862
parse_histogram($fh, \@delivered_interval_count);
2865
#elsif (/Time spent on the queue: (all messages|messages with at least one remote delivery)/) {
2866
elsif (/(Time spent on the queue|Delivery times|Receipt times): ((\S+) messages|messages with at least one remote delivery)((<[^>]*>)*\s*)$/) {
2259
2867
#Time spent on the queue: all messages
2260
2868
#-------------------------------------
2268
2876
# Set a pointer to the queue bin so we can use the same code
2269
2877
# block for both all messages and remote deliveries.
2270
my $bin_aref = ($1 eq 'all messages') ? \@queue_bin : \@remote_queue_bin;
2878
#my $bin_aref = ($1 eq 'all messages') ? \@qt_all_bin : \@qt_remote_bin;
2879
my($bin_aref, $times_aref, $overflow_sref);
2880
if ($1 eq 'Time spent on the queue') {
2881
$times_aref = \@queue_times;
2882
if ($2 eq 'all messages') {
2883
$bin_aref = \@qt_all_bin;
2884
$overflow_sref = \$qt_all_overflow;
2887
$bin_aref = \@qt_remote_bin;
2888
$overflow_sref = \$qt_remote_overflow;
2891
elsif ($1 eq 'Delivery times') {
2892
$times_aref = \@delivery_times;
2893
if ($2 eq 'all messages') {
2894
$bin_aref = \@dt_all_bin;
2895
$overflow_sref = \$dt_all_overflow;
2898
$bin_aref = \@dt_remote_bin;
2899
$overflow_sref = \$dt_remote_overflow;
2903
unless (exists $rcpt_times_bin{$3}) {
2904
initialise_rcpt_times($3);
2906
$bin_aref = $rcpt_times_bin{$3};
2907
$times_aref = \@rcpt_times;
2908
$overflow_sref = \$rcpt_times_overflow{$3};
2271
2912
my $reached_table = 0;
2272
2913
while (<$fh>) {
2273
$_ = html2txt($_); #Convert general HTML markup to text.
2274
$reached_table = 1 if (/^\s*Under/);
2275
next unless $reached_table;
2276
my $previous_seconds_on_queue = 0;
2277
if (/^\s*(Under|Over|)\s+(\d+[smhdw])\s+(\d+)/) {
2278
print STDERR "Parsing $_" if $debug;
2279
my($modifier,$formated_time,$count) = ($1,$2,$3);
2280
my $seconds = unformat_time($formated_time);
2281
my $time_on_queue = ($seconds + $previous_seconds_on_queue) / 2;
2282
$previous_seconds_on_queue = $seconds;
2283
$time_on_queue = $seconds * 2 if ($modifier eq 'Over');
2285
for ($i = 0; $i <= $#queue_times; $i++) {
2286
if ($time_on_queue < $queue_times[$i]) {
2287
$$bin_aref[$i] += $count;
2291
# There's only one counter for messages going over the queue
2292
# times so make sure we only count it once.
2293
$queue_more_than += $count if (($bin_aref == \@queue_bin) && ($i > $#queue_times));
2296
last; #Finished the table ?
2914
$_ = html2txt($_); #Convert general HTML markup to text.
2915
$reached_table = 1 if (/^\s*Under/);
2916
next unless $reached_table;
2917
my $previous_seconds_on_queue = 0;
2918
if (/^\s*(Under|Over|)\s+(\d+[smhdw])\s+(\d+)/) {
2919
print STDERR "Parsing $_" if $debug;
2920
my($modifier,$formated_time,$count) = ($1,$2,$3);
2921
my $seconds = unformat_time($formated_time);
2922
my $time_on_queue = ($seconds + $previous_seconds_on_queue) / 2;
2923
$previous_seconds_on_queue = $seconds;
2924
$time_on_queue = $seconds * 2 if ($modifier eq 'Over');
2926
for ($i = 0; $i <= $#$times_aref; $i++) {
2927
if ($time_on_queue < $times_aref->[$i]) {
2928
$$bin_aref[$i] += $count;
2932
$$overflow_sref += $count if ($i > $#$times_aref);
2936
last; #Finished the table ?
2352
2995
my($count_href,$data_href,$data_gigs_href);
2353
2996
if ($category =~ /local sender/) {
2354
2997
$count_href = \%received_count_user;
2355
$data_href = \%received_data_user;
2356
$data_gigs_href = \%received_data_gigs_user;
2998
$data_href = \%received_data_user;
2999
$data_gigs_href = \%received_data_gigs_user;
2358
3001
elsif ($category =~ /sending (\S+?)s?\b/) {
2359
3002
#Top 50 sending (host|domain|email|edomain)s
2360
3003
#Top sending (host|domain|email|edomain)
2361
3004
$count_href = \%{$received_count{"\u$1"}};
2362
$data_href = \%{$received_data{"\u$1"}};
2363
$data_gigs_href = \%{$received_data_gigs{"\u$1"}};
3005
$data_href = \%{$received_data{"\u$1"}};
3006
$data_gigs_href = \%{$received_data_gigs{"\u$1"}};
2365
3008
elsif ($category =~ /local destination/) {
2366
3009
$count_href = \%delivered_count_user;
2367
$data_href = \%delivered_data_user;
2368
$data_gigs_href = \%delivered_data_gigs_user;
3010
$data_href = \%delivered_data_user;
3011
$data_gigs_href = \%delivered_data_gigs_user;
2370
3013
elsif ($category =~ /(\S+) destination/) {
2371
3014
#Top 50 (host|domain|email|edomain) destinations
2372
3015
#Top (host|domain|email|edomain) destination
2373
3016
$count_href = \%{$delivered_count{"\u$1"}};
2374
$data_href = \%{$delivered_data{"\u$1"}};
2375
$data_gigs_href = \%{$delivered_data_gigs{"\u$1"}};
3017
$data_href = \%{$delivered_data{"\u$1"}};
3018
$data_gigs_href = \%{$delivered_data_gigs{"\u$1"}};
2378
3021
my $reached_table = 0;
2379
3022
while (<$fh>) {
2380
$_ = html2txt($_); #Convert general HTML markup to text.
2381
$reached_table = 1 if (/^\s*\d/);
2382
next unless $reached_table;
2383
if (/^\s*(\d+)\s+(\S+)\s*(.*?)\s*$/) {
2384
my($count,$rounded_volume,$entry) = ($1,$2,$3);
3023
# Watch out for empty tables.
3024
goto PARSE_OLD_REPORT_LINE if (/<h2>/ or /^[a-zA-Z]/);
3026
$_ = html2txt($_); #Convert general HTML markup to text.
3029
$reached_table = 1 if (/^\s*\d/);
3030
next unless $reached_table;
3032
# Remove optional 'average value' column.
3033
s/^\s*(\d+)\s+(\S+)\s+(\d+(KB|MB|GB|\b)\s+)/$1 $2 /;
3035
if (/^\s*(\d+)\s+(\S+)\s*(.*?)\s*$/) {
3036
my($count,$rounded_volume,$entry) = ($1,$2,$3);
2385
3037
#Note: $entry fields can be both null and can contain spaces.
2387
#Add the entry into the %table_order hash if it has a rounded volume (KB/MB/GB).
2388
push(@{$table_order{$rounded_volume}{$by_count_or_volume}},$entry) if ($rounded_volume =~ /\D/);
3039
#Add the entry into the %table_order hash if it has a rounded volume (KB/MB/GB).
3040
push(@{$table_order{$rounded_volume}{$by_count_or_volume}},$entry) if ($rounded_volume =~ /\D/);
2390
3042
unless ($league_table_value_entered{$entry}) {
2391
$league_table_value_entered{$entry} = 1;
2392
unless ($$count_href{$entry}) {
2393
$$count_href{$entry} = 0;
2394
$$data_href{$entry} = 0;
2395
$$data_gigs_href{$entry} = 0;
2396
$league_table_value_was_zero{$entry} = 1;
3043
$league_table_value_entered{$entry} = 1;
3044
unless ($$count_href{$entry}) {
3045
$$count_href{$entry} = 0;
3046
$$data_href{$entry} = 0;
3047
$$data_gigs_href{$entry} = 0;
3048
$league_table_value_was_zero{$entry} = 1;
2399
$$count_href{$entry} += $count;
3051
$$count_href{$entry} += $count;
2400
3052
#Add the rounded value to the data and data_gigs hashes.
2401
un_round($rounded_volume,\$$data_href{$entry},\$$data_gigs_href{$entry});
2402
print STDERR "$category by $by_count_or_volume: added $count,$rounded_volume to $entry\n" if $debug;
2405
else { #Finished the table ?
2406
if ($by_count_or_volume =~ /volume/) {
2407
#Add a few bytes to appropriate entries to preserve the order.
2409
my($rounded_volume);
2410
foreach $rounded_volume (keys %table_order) {
2411
#For each rounded volume, we want to create a list which has things
2412
#ordered from the volume table at the front, and additional things
2413
#from the count table ordered at the back.
2414
@{$table_order{$rounded_volume}{volume}} = () unless defined $table_order{$rounded_volume}{volume};
2415
@{$table_order{$rounded_volume}{'message count'}} = () unless defined $table_order{$rounded_volume}{'message count'};
2417
map {$mark{$_} = 1} @{$table_order{$rounded_volume}{volume}};
2418
@order = @{$table_order{$rounded_volume}{volume}};
2419
map {push(@order,$_)} grep(!$mark{$_},@{$table_order{$rounded_volume}{'message count'}});
2421
my $bonus_bytes = $#order;
2422
$bonus_bytes = 511 if ($bonus_bytes > 511); #Don't go over the half-K boundary!
2423
while (@order and ($bonus_bytes > 0)) {
2424
my $entry = shift(@order);
2425
if ($league_table_value_was_zero{$entry}) {
2426
$$data_href{$entry} += $bonus_bytes;
2427
print STDERR "$category by $by_count_or_volume: added $bonus_bytes bonus bytes to $entry\n" if $debug;
3053
un_round($rounded_volume,\$$data_href{$entry},\$$data_gigs_href{$entry});
3054
print STDERR "$category by $by_count_or_volume: added $count,$rounded_volume to $entry\n" if $debug;
3057
else { #Finished the table ?
3058
if ($by_count_or_volume =~ /volume/) {
3059
#Add a few bytes to appropriate entries to preserve the order.
3061
my($rounded_volume);
3062
foreach $rounded_volume (keys %table_order) {
3063
#For each rounded volume, we want to create a list which has things
3064
#ordered from the volume table at the front, and additional things
3065
#from the count table ordered at the back.
3066
@{$table_order{$rounded_volume}{volume}} = () unless defined $table_order{$rounded_volume}{volume};
3067
@{$table_order{$rounded_volume}{'message count'}} = () unless defined $table_order{$rounded_volume}{'message count'};
3069
map {$mark{$_} = 1} @{$table_order{$rounded_volume}{volume}};
3070
@order = @{$table_order{$rounded_volume}{volume}};
3071
map {push(@order,$_)} grep(!$mark{$_},@{$table_order{$rounded_volume}{'message count'}});
3073
my $bonus_bytes = $#order;
3074
$bonus_bytes = 511 if ($bonus_bytes > 511); #Don't go over the half-K boundary!
3075
while (@order and ($bonus_bytes > 0)) {
3076
my $entry = shift(@order);
3077
if ($league_table_value_was_zero{$entry}) {
3078
$$data_href{$entry} += $bonus_bytes;
3079
print STDERR "$category by $by_count_or_volume: added $bonus_bytes bonus bytes to $entry\n" if $debug;
2438
3090
elsif (/List of errors/) {
2449
3101
my $reached_table = 0;
2450
3102
my($count,$error,$blanks);
2451
3103
while (<$fh>) {
2452
$reached_table = 1 if (/^( *|<li>)(\d+)/);
2453
next unless $reached_table;
2455
s/^<li>(\d+) -/$1/; #Convert an HTML line to a text line.
2456
$_ = html2txt($_); #Convert general HTML markup to text.
2459
$error .= ' ' . $1; #Join a multiline error.
2461
elsif (/^\s*(\d+)\s+(.*)/) {
3104
$reached_table = 1 if (/^( *|<li>)(\d+)/);
3105
next unless $reached_table;
3107
s/^<li>(\d+) -/$1/; #Convert an HTML line to a text line.
3108
$_ = html2txt($_); #Convert general HTML markup to text.
3111
$error .= ' ' . $1; #Join a multiline error.
3113
elsif (/^\s*(\d+)\s+(.*)/) {
2463
3115
#Finished with a previous multiline error so save it.
2464
$errors_count{$error} = 0 unless $errors_count{$error};
2465
$errors_count{$error} += $count;
2467
($count,$error) = ($1,$2);
2469
elsif (/Errors encountered/) {
3116
$errors_count{$error} = 0 unless $errors_count{$error};
3117
$errors_count{$error} += $count;
3119
($count,$error) = ($1,$2);
3121
elsif (/Errors encountered/) {
2471
3123
#Finished the section, so save our stored last error.
2472
$errors_count{$error} = 0 unless $errors_count{$error};
2473
$errors_count{$error} += $count;
3124
$errors_count{$error} = 0 unless $errors_count{$error};
3125
$errors_count{$error} += $count;
2485
=head2 update_relayed();
2487
update_relayed($count,$sender,$recipient);
2489
Adds an entry into the %relayed hash. Currently only used when
3135
#######################################################################
3136
# parse_histogram($fh, \@delivered_interval_count);
3137
# Parse a histogram into the provided array of counters.
3138
#######################################################################
3139
sub parse_histogram {
3140
my($fh, $counters_aref) = @_;
3142
# Messages received per hour (each dot is 2 messages)
3143
#---------------------------------------------------
3145
#00-01 106 .....................................................
3146
#01-02 103 ...................................................
3148
my $reached_table = 0;
3150
$reached_table = 1 if (/^00/);
3151
next unless $reached_table;
3152
print STDERR "Parsing $_" if $debug;
3153
if (/^(\d+):(\d+)\s+(\d+)/) { #hh:mm start time format ?
3154
$$counters_aref[($1*60 + $2)/$hist_interval] += $3 if $hist_opt;
3156
elsif (/^(\d+)-(\d+)\s+(\d+)/) { #hh-hh start-end time format ?
3157
$$counters_aref[($1*60)/$hist_interval] += $3 if $hist_opt;
3159
else { #Finished the table ?
3166
#######################################################################
3169
# update_relayed($count,$sender,$recipient);
3171
# Adds an entry into the %relayed hash. Currently only used when
3173
#######################################################################
2494
3174
sub update_relayed {
2495
3175
my($count,$sender,$recipient) = @_;
3298
#######################################################################
3299
# set_worksheet_line($ws_global, $startrow, $startcol, \@content, $format);
3301
# set values to a sequence of cells in a row.
3303
#######################################################################
3304
sub set_worksheet_line {
3305
my ($worksheet, $row, $col, $content, $format) = @_;
3307
foreach my $token (@$content)
3309
$worksheet->write($row, $col++, $token, $format );
3314
#######################################################################
3315
# @rcpt_times = parse_time_list($string);
3317
# Parse a comma seperated list of time values in seconds given by
3318
# the user and fill an array.
3320
# Return a default list if $string is undefined.
3321
# Return () if $string eq '0'.
3322
#######################################################################
3323
sub parse_time_list {
3325
if (! defined $string) {
3326
return(60, 5*60, 15*60, 30*60, 60*60, 3*60*60, 6*60*60, 12*60*60, 24*60*60);
3328
my(@times) = split(/,/, $string);
3329
foreach my $q (@times) { $q = eval($q) + 0 }
3330
@times = sort { $a <=> $b } @times;
3331
@times = () if ($#times == 0 && $times[0] == 0);
3336
#######################################################################
3337
# initialise_rcpt_times($protocol);
3338
# Initialise an array of rcpt_times to 0 for the specified protocol.
3339
#######################################################################
3340
sub initialise_rcpt_times {
3342
for (my $i = 0; $i <= $#rcpt_times; ++$i) {
3343
$rcpt_times_bin{$protocol}[$i] = 0;
3345
$rcpt_times_overflow{$protocol} = 0;
2624
3349
##################################################
3451
# keep old default behaviour
3452
if (! ($xls_fh or $htm_fh or $txt_fh)) {
2714
3456
# Check that all the charts options are specified.
2715
3457
warn "-charts option not specified. Use -help for help.\n" if ($charts_option_specified && ! $charts);
2717
3459
# Default to display tables by sending Host.
2718
3460
$do_sender{Host} = 1 unless ($do_sender{Domain} || $do_sender{Email} || $do_sender{Edomain});
3462
# prepare xls Excel Workbook
3463
if (defined $xls_fh)
3466
# Create a new Excel workbook
3467
$workbook = Spreadsheet::WriteExcel->new($xls_fh);
3470
$ws_global = $workbook->addworksheet('Exim Statistik');
3471
# show $ws_global as initial sheet
3472
$ws_global->set_first_sheet();
3473
$ws_global->activate();
3476
$ws_relayed = $workbook->addworksheet('Relayed Messages');
3477
$ws_relayed->set_column(1, 2, 80);
3480
$ws_top50 = $workbook->addworksheet('Deliveries');
3483
$ws_errors = $workbook->addworksheet('Errors');
3488
$ws_global->set_column(0, 2, 20); # Columns B-D width set to 30
3489
$ws_global->set_column(3, 3, 15); # Columns B-D width set to 30
3490
$ws_global->set_column(4, 4, 25); # Columns B-D width set to 30
3493
$f_default = $workbook->add_format();
3495
$f_header1 = $workbook->add_format();
3496
$f_header1->set_bold();
3497
#$f_header1->set_color('red');
3498
$f_header1->set_size('15');
3499
$f_header1->set_valign();
3500
# $f_header1->set_align('center');
3501
# $ws_global->write($row++, 2, "Testing Headers 1", $f_header1);
3503
$f_header2 = $workbook->add_format();
3504
$f_header2->set_bold();
3505
$f_header2->set_size('12');
3506
$f_header2->set_valign();
3507
# $ws_global->write($row++, 2, "Testing Headers 2", $f_header2);
3509
$f_percent = $workbook->add_format();
3510
$f_percent->set_num_format('0.0%');
3512
$f_headertab = $workbook->add_format();
3513
$f_headertab->set_bold();
3514
$f_headertab->set_valign();
3515
# $ws_global->write($row++, 2, "Testing Headers tab", $f_headertab);
3520
# Initialise the queue/delivery/rcpt time counters.
2721
3521
for (my $i = 0; $i <= $#queue_times; $i++) {
2723
$remote_queue_bin[$i] = 0;
3522
$qt_all_bin[$i] = 0;
3523
$qt_remote_bin[$i] = 0;
3525
for (my $i = 0; $i <= $#delivery_times; $i++) {
3526
$dt_all_bin[$i] = 0;
3527
$dt_remote_bin[$i] = 0;
3529
initialise_rcpt_times('all');
2726
3532
# Compute the number of slots for the histogram
2728
3533
if ($hist_opt > 0)
2730
3535
if ($hist_opt > 60 || 60 % $hist_opt != 0)
2732
print "Eximstats: -h must specify a factor of 60\n";
3537
print STDERR "Eximstats: -h must specify a factor of 60\n";
2735
$hist_interval = 60/$hist_opt; #Interval in minutes.
2736
$hist_number = (24*60)/$hist_interval; #Number of intervals per day.
3540
$hist_interval = 60/$hist_opt; #Interval in minutes.
3541
$hist_number = (24*60)/$hist_interval; #Number of intervals per day.
2737
3542
@received_interval_count = (0) x $hist_number;
2738
3543
@delivered_interval_count = (0) x $hist_number;
3544
my $user_pattern_index = 0;
3545
for (my $user_pattern_index = 0; $user_pattern_index <= $#user_patterns; ++$user_pattern_index) {
3546
@{$user_pattern_interval_count[$user_pattern_index]} = (0) x $hist_number;
3548
@dt_all_bin = (0) x $hist_number;
3549
@dt_remote_bin = (0) x $hist_number;
2741
3552
#$queue_unknown = 0;
2815
3630
# Print the deliveries per interval as a histogram, unless configured not to.
2816
3631
# First find the maximum in one interval and scale accordingly.
2817
3632
if ($hist_opt > 0) {
2818
print_histogram("Messages received", @received_interval_count);
2819
print_histogram("Deliveries", @delivered_interval_count);
3633
print_histogram("Messages received", 'message', @received_interval_count);
3634
print_histogram("Deliveries", 'delivery', @delivered_interval_count);
2822
3637
# Print times on queue if required.
2823
3638
if ($#queue_times >= 0) {
2824
print_queue_times("all messages", \@queue_bin,$queue_more_than);
2825
print_queue_times("messages with at least one remote delivery",\@remote_queue_bin,$queue_more_than);
3639
print_duration_table("Time spent on the queue", "all messages", \@queue_times, \@qt_all_bin,$qt_all_overflow);
3640
print_duration_table("Time spent on the queue", "messages with at least one remote delivery", \@queue_times, \@qt_remote_bin,$qt_remote_overflow);
3643
# Print delivery times if required.
3644
if ($#delivery_times >= 0) {
3645
print_duration_table("Delivery times", "all messages", \@delivery_times, \@dt_all_bin,$dt_all_overflow);
3646
print_duration_table("Delivery times", "messages with at least one remote delivery", \@delivery_times, \@dt_remote_bin,$dt_remote_overflow);
3649
# Print rcpt times if required.
3650
if ($#rcpt_times >= 0) {
3651
foreach my $protocol ('all', grep(!/^all$/, sort keys %rcpt_times_bin)) {
3652
print_duration_table("Receipt times", "$protocol messages", \@rcpt_times, $rcpt_times_bin{$protocol}, $rcpt_times_overflow{$protocol});
2828
3656
# Print relay information if required.