~ubuntu-branches/ubuntu/hardy/exim4/hardy-proposed

« back to all changes in this revision

Viewing changes to src/eximstats.src

  • Committer: Bazaar Package Importer
  • Author(s): Marc Haber
  • Date: 2005-07-02 06:08:34 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20050702060834-qk17pd52kb9nt3bj
Tags: 4.52-1
* new upstream version 4.51. (mh)
  * adapt 70_remove_exim-users_references
  * remove 37_gnutlsparams
  * adapt 36_pcre
  * adapt 31_eximmanpage
* fix package priorities to have them in sync with override again. (mh)
* Fix error in nb (Norwegian) translation.
  Thanks to Helge Hafting. (mh). Closes: #315775
* Standards-Version: 3.6.2, no changes needed. (mh)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!PERL_COMMAND -w
 
2
# $Cambridge: exim/exim-src/src/eximstats.src,v 1.10 2005/06/30 10:12:42 steve Exp $
2
3
 
3
4
# Copyright (c) 2001 University of Cambridge.
4
5
# See the file NOTICE for conditions of use and distribution.
180
181
# 2004-02-20  V1.31 Andrea Balzi
181
182
#             Only show the Local Sender/Destination links if the tables exist.
182
183
#
183
 
 
 
184
# 2004-07-05  V1.32 Steve Campbell
 
185
#             Fix '-merge -h0' divide by zero error.
 
186
#
 
187
# 2004-07-15  V1.33 Steve Campbell
 
188
#             Documentation update - I've converted the subroutine
 
189
#             documentation from POD to comments.
 
190
#
 
191
# 2004-12-10  V1.34 Steve Campbell
 
192
#             Eximstats can now parse syslog lines as well as mainlog lines.
 
193
#
 
194
# 2004-12-20  V1.35 Wouter Verhelst
 
195
#             Pie charts by volume were actually generated by count. Fixed.
 
196
#
 
197
# 2005-02-07  V1.36 Gregor Herrmann / Steve Campbell
 
198
#             Added average sizes to HTML Top tables.
 
199
#
 
200
# 2005-04-26  V1.37 Frank Heydlauf
 
201
#             Added -xls and the ability to specify output files.
 
202
#
 
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.
 
208
#
 
209
# 2005-05-27  V1.39 Steve Campbell
 
210
#             Added the -include_original_destination flag
 
211
#             Removed tabs and trailing whitespace.
 
212
#
 
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.
 
218
#
 
219
# 2005-06-15  V1.41 Steve Campbell
 
220
#             Added the -show_rt<list> flag.
 
221
#             Added the -show_dt<list> flag.
 
222
#
 
223
# 2005-06-24  V1.42 Steve Campbell
 
224
#             Added Histograms for user specified patterns.
 
225
#
 
226
# 2005-06-30  V1.43 Steve Campbell
 
227
#             Bug fix for V1.42 with -h0 specified. Spotted by Chris Lear.
 
228
#
 
229
#
 
230
# For documentation on the logfile format, see
 
231
# http://www.exim.org/exim-html-4.50/doc/html/spec_48.html#IX2793
184
232
 
185
233
=head1 NAME
186
234
 
187
 
eximstats - generates statistics from Exim mainlog files.
 
235
eximstats - generates statistics from Exim mainlog or syslog files.
188
236
 
189
237
=head1 SYNOPSIS
190
238
 
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
193
241
 
194
 
Options:
 
242
=head2 Output:
 
243
 
 
244
=over 4
 
245
 
 
246
=item B<-txt>
 
247
 
 
248
Output the results in plain text to STDOUT.
 
249
 
 
250
=item B<-txt>=I<filename>
 
251
 
 
252
Output the results in plain text. Filename '-' for STDOUT is accepted.
 
253
 
 
254
=item B<-html>
 
255
 
 
256
Output the results in HTML to STDOUT.
 
257
 
 
258
=item B<-html>=I<filename>
 
259
 
 
260
Output the results in HTML. Filename '-' for STDOUT is accepted.
 
261
 
 
262
=item B<-xls>
 
263
 
 
264
Output the results in Excel compatible Format to STDOUT.
 
265
Requires the Spreadsheet::WriteExcel CPAN module.
 
266
 
 
267
=item B<-xls>=I<filename>
 
268
 
 
269
Output the results in Excel compatible format. Filename '-' for STDOUT is accepted.
 
270
 
 
271
 
 
272
=back
 
273
 
 
274
=head2 Options:
195
275
 
196
276
=over 4
197
277
 
239
319
 
240
320
Include remote users in the top source/destination listings.
241
321
 
 
322
=item B<-include_original_destination>
 
323
 
 
324
Include the original destination email addresses rather than just
 
325
using the final ones.
 
326
Useful for finding out which of your mailing lists are receiving mail.
 
327
 
 
328
=item B<-show_dt>I<list>
 
329
 
 
330
Show the delivery times (B<DT>)for all the messages.
 
331
 
 
332
Exim must have been configured to use the +delivery_time logging option
 
333
for this option to work.
 
334
 
 
335
I<list> is an optional list of times. Eg -show_dt1,2,4,8 will show
 
336
the number of messages with delivery times under 1 second, 2 seconds, 4 seconds,
 
337
8 seconds, and over 8 seconds.
 
338
 
 
339
=item B<-show_rt>I<list>
 
340
 
 
341
Show the receipt times for all the messages. The receipt time is
 
342
defined as the Completed hh:mm:ss - queue_time_overall - the Receipt hh:mm:ss.
 
343
These figures will be skewed by pipelined messages so might not be that useful.
 
344
 
 
345
Exim must have been configured to use the +queue_time_overall logging option
 
346
for this option to work.
 
347
 
 
348
I<list> is an optional list of times. Eg -show_rt1,2,4,8 will show
 
349
the number of messages with receipt times under 1 second, 2 seconds, 4 seconds,
 
350
8 seconds, and over 8 seconds.
 
351
 
242
352
=item B<-byhost>
243
353
 
244
354
Show results by sending host. This may be combined with
305
415
 
306
416
=back
307
417
 
308
 
=item B<-html>
309
 
 
310
 
Output the results in HTML.
311
 
 
312
418
=item B<-charts>
313
419
 
314
420
Create graphical charts to be displayed in HTML output.
 
421
Only valid in combination with I<-html>.
315
422
 
316
423
This requires the following modules which can be obtained
317
424
from http://www.cpan.org/modules/01modules.index.html
352
459
 
353
460
=head1 DESCRIPTION
354
461
 
355
 
Eximstats parses exim mainlog files and outputs a statistical
 
462
Eximstats parses exim mainlog and syslog files to output a statistical
356
463
analysis of the messages processed. By default, a text
357
 
analysis is generated, but you can request an html analysis
358
 
by using the B<-html> flag. See the help (B<-help>) to learn
 
464
analysis is generated, but you can request other output formats
 
465
using flags. See the help (B<-help>) to learn
359
466
about how to create charts from the tables.
360
467
 
361
468
=head1 AUTHOR
370
477
when you have multiple mail servers and a message cannot be
371
478
immeadiately delivered. Fixing this could be tricky...
372
479
 
373
 
=head1 SUBROUTINES
374
 
 
375
 
The following section will only be of interest to the
376
 
program maintainers:
 
480
Merging of xls files is not (yet) possible. Be free to implement :)
377
481
 
378
482
=cut
379
483
 
380
484
use integer;
381
485
use strict;
 
486
use IO::File;
382
487
 
383
488
# use Time::Local;  # PH/FANF
384
489
use POSIX;
385
490
 
386
 
use vars qw($HAVE_GD_Graph_pie $HAVE_GD_Graph_linespoints);
 
491
use vars qw($HAVE_GD_Graph_pie $HAVE_GD_Graph_linespoints $HAVE_Spreadsheet_WriteExcel);
387
492
eval { require GD::Graph::pie; };
388
493
$HAVE_GD_Graph_pie = $@ ? 0 : 1;
389
494
eval { require GD::Graph::linespoints; };
390
495
$HAVE_GD_Graph_linespoints = $@ ? 0 : 1;
 
496
eval { require Spreadsheet::WriteExcel; };
 
497
$HAVE_Spreadsheet_WriteExcel = $@ ? 0 : 1;
391
498
 
392
499
 
393
500
##################################################
397
504
use vars qw(@tab62 @days_per_month $gig);
398
505
use vars qw($VERSION);
399
506
use vars qw($COLUMN_WIDTHS);
 
507
use vars qw($WEEK $DAY $HOUR $MINUTE);
400
508
 
401
509
 
402
510
@tab62 =
410
518
 
411
519
@days_per_month = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334);
412
520
$gig     = 1024 * 1024 * 1024;
413
 
$VERSION = '1.31';
 
521
$VERSION = '1.43';
414
522
 
415
523
# How much space do we allow for the Hosts/Domains/Emails/Edomains column headers?
416
524
$COLUMN_WIDTHS = 8;
417
525
 
 
526
$MINUTE = 60;
 
527
$HOUR   = 60 * $MINUTE;
 
528
$DAY    = 24 * $HOUR;
 
529
$WEEK   =  7 * $DAY;
 
530
 
418
531
# Declare global variables.
419
532
use vars qw($total_received_data  $total_received_data_gigs  $total_received_count);
420
533
use vars qw($total_delivered_data $total_delivered_data_gigs $total_delivered_count);
421
 
use vars qw(%arrival_time %size %from_host %from_address);
422
 
use vars qw(%timestamp2time);                   #Hash of timestamp => time.
423
 
use vars qw($last_timestamp $last_time);        #The last time convertion done.
424
 
use vars qw($last_date $date_seconds);          #The last date convertion done.
425
 
use vars qw($last_offset $offset_seconds);      #The last time offset convertion done.
 
534
use vars qw(%timestamp2time);                   #Hash of timestamp => time.
 
535
use vars qw($last_timestamp $last_time);        #The last time convertion done.
 
536
use vars qw($last_date $date_seconds);          #The last date convertion done.
 
537
use vars qw($last_offset $offset_seconds);      #The last time offset convertion done.
426
538
use vars qw($localtime_offset);
427
 
use vars qw($i);                                #General loop counter.
428
 
use vars qw($debug);                            #Debug mode?
429
 
use vars qw($ntopchart);                        #How many entries should make it into the chart?
430
 
use vars qw($gddirectory);                      #Where to put files from GD::Graph
 
539
use vars qw($i);                                #General loop counter.
 
540
use vars qw($debug);                            #Debug mode?
 
541
use vars qw($ntopchart);                        #How many entries should make it into the chart?
 
542
use vars qw($gddirectory);                      #Where to put files from GD::Graph
 
543
use vars qw($workbook $ws_global $ws_relayed $ws_top50 $ws_errors );   #For use in Speadsheed::WriteExcel
 
544
use vars qw($row $col $row_hist $col_hist $row_league_table);
 
545
use vars qw($run_hist);
 
546
use vars qw($f_default $f_header1 $f_header2 $f_headertab $f_percent); #Format Header
 
547
 
 
548
# Output FileHandles
 
549
use vars qw($txt_fh $htm_fh $xls_fh);
431
550
 
432
551
$ntopchart = 5;
433
552
 
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);
440
562
 
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 ?
444
566
 
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);
456
583
 
457
584
use vars qw(%report_totals);
458
585
 
 
586
# Enumerations
 
587
use vars qw($SIZE $FROM_HOST $FROM_ADDRESS $ARRIVAL_TIME $REMOTE_DELIVERED $PROTOCOL);
 
588
use vars qw($DELAYED $HAD_ERROR);
 
589
$SIZE             = 0;
 
590
$FROM_HOST        = 1;
 
591
$FROM_ADDRESS     = 2;
 
592
$ARRIVAL_TIME     = 3;
 
593
$REMOTE_DELIVERED = 4;
 
594
$DELAYED          = 5;
 
595
$HAD_ERROR        = 6;
 
596
$PROTOCOL         = 7;
459
597
 
460
598
 
461
599
 
463
601
#                   Subroutines                  #
464
602
##################################################
465
603
 
466
 
 
467
 
=head2 volume_rounded();
468
 
 
469
 
 $rounded_volume = volume_rounded($bytes,$gigabytes);
470
 
 
471
 
Given a data size in bytes, round it to KB, MB, or GB
472
 
as appropriate.
473
 
 
474
 
Eg 12000 => 12KB, 15000000 => 14GB, etc.
475
 
 
476
 
Note: I've experimented with Math::BigInt and it results in a 33%
477
 
performance degredation as opposed to storing numbers split into
478
 
bytes and gigabytes.
479
 
 
480
 
=cut
481
 
 
 
604
#######################################################################
 
605
# get_filehandle($file,\%output_files);
 
606
# Return a filehandle writing to $file.
 
607
#
 
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
#######################################################################
 
611
sub get_filehandle {
 
612
  my($file,$output_files_href) = @_;
 
613
 
 
614
  $file = '-' if ($file eq '');
 
615
 
 
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;
 
619
  }
 
620
 
 
621
  if ($file eq '-') {
 
622
    return \*STDOUT;
 
623
  }
 
624
 
 
625
  if (-e $file) {
 
626
    unlink $file or die "Failed to rm $file: $!";
 
627
  }
 
628
 
 
629
  my $fh = new IO::File $file, O_WRONLY|O_CREAT|O_EXCL;
 
630
  die "new IO::File $file failed: $!" unless (defined $fh);
 
631
  return $fh;
 
632
}
 
633
 
 
634
 
 
635
#######################################################################
 
636
# volume_rounded();
 
637
#
 
638
# $rounded_volume = volume_rounded($bytes,$gigabytes);
 
639
#
 
640
# Given a data size in bytes, round it to KB, MB, or GB
 
641
# as appropriate.
 
642
#
 
643
# Eg 12000 => 12KB, 15000000 => 14GB, etc.
 
644
#
 
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 {
483
650
  my($x,$g) = @_;
484
651
  $x = 0 unless $x;
514
681
  }
515
682
  else {
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);
 
689
    no integer;
 
690
    $rounded = sprintf("%.0f", ($g * $gig) + $x);
518
691
  }
519
692
 
520
693
  return $rounded;
521
694
}
522
695
 
523
696
 
524
 
=head2 un_round();
525
 
 
526
 
 un_round($rounded_volume,\$bytes,\$gigabytes);
527
 
 
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.
531
 
 
532
 
Given a data size in bytes, round it to KB, MB, or GB
533
 
as appropriate.
534
 
 
535
 
EG: 500 => (500,0), 14GB => (0,14), etc.
536
 
 
537
 
=cut
538
 
 
 
697
#######################################################################
 
698
# un_round();
 
699
#
 
700
#  un_round($rounded_volume,\$bytes,\$gigabytes);
 
701
#
 
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.
 
705
#
 
706
# Given a data size in bytes, round it to KB, MB, or GB
 
707
# as appropriate.
 
708
#
 
709
# EG: 500 => (500,0), 14GB => (0,14), etc.
 
710
#######################################################################
539
711
sub un_round {
540
712
  my($rounded,$bytes_sref,$gigabytes_sref) = @_;
541
713
 
551
723
    $$bytes_sref     += ($1 % (1024 * 1024) * 1024);
552
724
  }
553
725
  elsif ($rounded =~ /(\d+)/) {
554
 
    $$gigabytes_sref += $1 / $gig;
 
726
    # We need to turn off integer in case we are merging an -nvr report.
 
727
    no integer;
 
728
    $$gigabytes_sref += int($1 / $gig);
555
729
    $$bytes_sref     += $1 % $gig;
556
730
  }
557
731
 
560
734
}
561
735
 
562
736
 
563
 
=head2 add_volume();
564
 
 
565
 
  add_volume(\$bytes,\$gigs,$size);
566
 
 
567
 
Add $size to $bytes/$gigs where this is a number split into
568
 
bytes ($bytes) and gigabytes ($gigs). This is significantly
569
 
faster than using Math::BigInt.
570
 
 
571
 
=cut
572
 
 
 
737
#######################################################################
 
738
# add_volume();
 
739
#
 
740
#   add_volume(\$bytes,\$gigs,$size);
 
741
#
 
742
# Add $size to $bytes/$gigs where this is a number split into
 
743
# bytes ($bytes) and gigabytes ($gigs). This is significantly
 
744
# faster than using Math::BigInt.
 
745
#######################################################################
573
746
sub add_volume {
574
 
my($bytes_ref,$gigs_ref,$size) = @_;
575
 
$$bytes_ref = 0 if ! defined $$bytes_ref;
576
 
$$gigs_ref = 0 if ! defined $$gigs_ref;
577
 
$$bytes_ref += $size;
578
 
while ($$bytes_ref > $gig)
579
 
  {
580
 
  $$gigs_ref++;
581
 
  $$bytes_ref -= $gig;
 
747
  my($bytes_ref,$gigs_ref,$size) = @_;
 
748
  $$bytes_ref = 0 if ! defined $$bytes_ref;
 
749
  $$gigs_ref = 0 if ! defined $$gigs_ref;
 
750
  $$bytes_ref += $size;
 
751
  while ($$bytes_ref > $gig) {
 
752
    $$gigs_ref++;
 
753
    $$bytes_ref -= $gig;
582
754
  }
583
755
}
584
756
 
585
757
 
586
 
=head2 format_time();
587
 
 
588
 
 $formatted_time = format_time($seconds);
589
 
 
590
 
Given a time in seconds, break it down into
591
 
weeks, days, hours, minutes, and seconds.
592
 
 
593
 
Eg 12005 => 3h20m5s
594
 
 
595
 
=cut
596
 
 
 
758
#######################################################################
 
759
# format_time();
 
760
#
 
761
#  $formatted_time = format_time($seconds);
 
762
#
 
763
# Given a time in seconds, break it down into
 
764
# weeks, days, hours, minutes, and seconds.
 
765
#
 
766
# Eg 12005 => 3h20m5s
 
767
#######################################################################
597
768
sub format_time {
598
769
my($t) = pop @_;
599
770
my($s) = $t % 60;
614
785
}
615
786
 
616
787
 
617
 
=head2 unformat_time();
618
 
 
619
 
 $seconds = unformat_time($formatted_time);
620
 
 
621
 
Given a time in weeks, days, hours, minutes, or seconds, convert it to seconds.
622
 
 
623
 
Eg 3h20m5s => 12005
624
 
 
625
 
=cut
626
 
 
 
788
#######################################################################
 
789
#  unformat_time();
 
790
#
 
791
#  $seconds = unformat_time($formatted_time);
 
792
#
 
793
# Given a time in weeks, days, hours, minutes, or seconds, convert it to seconds.
 
794
#
 
795
# Eg 3h20m5s => 12005
 
796
#######################################################################
627
797
sub unformat_time {
628
798
  my($formated_time) = pop @_;
629
799
  my $time = 0;
639
809
}
640
810
 
641
811
 
642
 
=head2 seconds();
643
 
 
644
 
 $time = seconds($timestamp);
645
 
 
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
654
 
IDs, which is UTC.
655
 
 
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'.
659
 
 
660
 
We also store the results of the last conversion done, and only
661
 
recalculate if the date is different.
662
 
 
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
665
 
date obsoletes this.
666
 
 
667
 
=cut
668
 
 
 
812
#######################################################################
 
813
# seconds();
 
814
#
 
815
#  $time = seconds($timestamp);
 
816
#
 
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
 
825
# IDs, which is UTC.
 
826
#
 
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'.
 
830
#
 
831
# We also store the results of the last conversion done, and only
 
832
# recalculate if the date is different.
 
833
#
 
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
#######################################################################
669
838
sub seconds {
670
839
  my($timestamp) = @_;
671
840
 
707
876
}
708
877
 
709
878
 
710
 
=head2 id_seconds();
711
 
 
712
 
 $time = id_seconds($message_id);
713
 
 
714
 
Given a message ID, convert it into a time() value.
715
 
 
716
 
=cut
717
 
 
 
879
#######################################################################
 
880
#  id_seconds();
 
881
#
 
882
#  $time = id_seconds($message_id);
 
883
#
 
884
# Given a message ID, convert it into a time() value.
 
885
#######################################################################
718
886
sub id_seconds {
719
887
my($sub_id) = substr((pop @_), 0, 6);
720
888
my($s) = 0;
723
891
$s;
724
892
}
725
893
 
726
 
 
727
 
 
728
 
=head2 calculate_localtime_offset();
729
 
 
730
 
 $localtime_offset = calculate_localtime_offset();
731
 
 
732
 
Calculate the the localtime offset from gmtime in seconds.
733
 
 
734
 
 $localtime = time() + $localtime_offset.
735
 
 
736
 
These are the same semantics as ISO 8601 and RFC 2822 timezone offsets.
737
 
(West is negative, East is positive.)
738
 
 
739
 
=cut
 
894
#######################################################################
 
895
#  wdhms_seconds();
 
896
#
 
897
#  $seconds = wdhms_seconds($string);
 
898
#
 
899
# Convert a string in a week/day/hour/minute/second format (eg 4h10s)
 
900
# into seconds.
 
901
#######################################################################
 
902
sub wdhms_seconds {
 
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));
 
905
  }
 
906
  return undef;
 
907
}
 
908
 
 
909
#######################################################################
 
910
#  queue_time();
 
911
#
 
912
#  $queued = queue_time($completed_tod, $arrival_time, $id);
 
913
#
 
914
# Given the completed time of day and either the arrival time
 
915
# (preferred), or the message ID, calculate how long the message has
 
916
# been on the queue.
 
917
#
 
918
#######################################################################
 
919
sub queue_time {
 
920
  my($completed_tod, $arrival_time, $id) = @_;
 
921
 
 
922
  # Note: id_seconds() benchmarks as 42% slower than seconds()
 
923
  # and computing the time accounts for a significant portion of
 
924
  # the run time.
 
925
  if (defined $arrival_time) {
 
926
    return(seconds($completed_tod) - seconds($arrival_time));
 
927
  }
 
928
  else {
 
929
    return(seconds($completed_tod) - id_seconds($id));
 
930
  }
 
931
}
 
932
 
 
933
 
 
934
#######################################################################
 
935
#  calculate_localtime_offset();
 
936
#
 
937
#  $localtime_offset = calculate_localtime_offset();
 
938
#
 
939
# Calculate the the localtime offset from gmtime in seconds.
 
940
#
 
941
#  $localtime = time() + $localtime_offset.
 
942
#
 
943
# These are the same semantics as ISO 8601 and RFC 2822 timezone offsets.
 
944
# (West is negative, East is positive.)
 
945
#######################################################################
740
946
 
741
947
# $localtime = gmtime() + $localtime_offset.  OLD COMMENT
742
948
# This subroutine commented out as it's not currently in use.
761
967
}
762
968
 
763
969
 
764
 
=head2 print_queue_times();
765
 
 
766
 
 $time = print_queue_times($message_type,\@queue_times,$queue_more_than);
767
 
 
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
770
 
a table.
771
 
 
772
 
=cut
773
 
 
774
 
sub print_queue_times {
 
970
 
 
971
#######################################################################
 
972
# print_duration_table();
 
973
#
 
974
#  print_duration_table($title, $message_type, \@times, \@values, $overflow);
 
975
#
 
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
 
984
#                  time.
 
985
#######################################################################
 
986
sub print_duration_table {
775
987
no integer;
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);
779
991
 
780
992
my $printed_one = 0;
781
993
my $cumulative_percent = 0;
782
 
#$queue_unknown += keys %arrival_time;
783
 
 
784
 
my $queue_total = $queue_more_than;
785
 
for ($i = 0; $i <= $#queue_times; $i++) { $queue_total += $$array[$i] }
786
 
 
787
 
my $temp = "Time spent on the queue: $string";
788
 
 
789
 
my($format);
790
 
if ($html) {
791
 
  print "<hr><a name=\"$string time\"></a><h2>$temp</h2>\n";
792
 
  print "<table border=0 width=\"100%\">\n";
793
 
  print "<tr><td>\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";
 
994
 
 
995
my $queue_total = $overflow;
 
996
map {$queue_total += $_} @$values_aref;
 
997
 
 
998
my $temp = "$title: $message_type";
 
999
 
 
1000
 
 
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";
 
1003
 
 
1004
# write header
 
1005
printf $txt_fh ("%s\n%s\n\n", $temp, "-" x length($temp)) if $txt_fh;
 
1006
if ($htm_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";
797
1012
}
798
 
else
 
1013
if ($xls_fh)
799
1014
{
800
 
  printf("%s\n%s\n\n", $temp, "-" x length($temp));
801
 
  $format = "%5s %4s   %6d %5.1f%%  %5.1f%%\n";
 
1015
 
 
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);
802
1019
}
803
1020
 
804
 
for ($i = 0; $i <= $#queue_times; $i++) {
805
 
  if ($$array[$i] > 0)
 
1021
 
 
1022
for ($i = 0; $i <= $#$times_aref; ++$i) {
 
1023
  if ($$values_aref[$i] > 0)
806
1024
    {
807
 
    my $percent = ($$array[$i] * 100)/$queue_total;
 
1025
    my $percent = ($values_aref->[$i] * 100)/$queue_total;
808
1026
    $cumulative_percent += $percent;
809
 
    printf($format,
810
 
      $printed_one? "     " : "Under",
811
 
      format_time($queue_times[$i]),
812
 
      $$array[$i], $percent, $cumulative_percent);
813
 
    if (!defined($queue_times[$i])) {
814
 
      print "Not defined";
815
 
    }
 
1027
 
 
1028
    my @content=($printed_one? "     " : "Under",
 
1029
        format_time($times_aref->[$i]),
 
1030
        $values_aref->[$i], $percent, $cumulative_percent);
 
1031
 
 
1032
    if ($htm_fh) {
 
1033
      printf $htm_fh ($htm_format, @content);
 
1034
      if (!defined($values_aref->[$i])) {
 
1035
        print $htm_fh "Not defined";
 
1036
      }
 
1037
    }
 
1038
    if ($txt_fh) {
 
1039
      printf $txt_fh ($txt_format, @content);
 
1040
      if (!defined($times_aref->[$i])) {
 
1041
        print $txt_fh "Not defined";
 
1042
      }
 
1043
    }
 
1044
    if ($xls_fh)
 
1045
    {
 
1046
      no integer;
 
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);
 
1049
 
 
1050
      if (!defined($times_aref->[$i])) {
 
1051
        $col=0;
 
1052
        $ws_global->write($row++, $col, "Not defined"  );
 
1053
      }
 
1054
    }
 
1055
 
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;
820
1060
  }
821
1061
}
822
1062
 
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;
826
 
  printf($format,
827
 
    "Over ",
828
 
    format_time($queue_times[$#queue_times]),
829
 
    $queue_more_than, $percent, $cumulative_percent);
 
1066
 
 
1067
    my @content = ("Over ", format_time($times_aref->[-1]),
 
1068
        $overflow, $percent, $cumulative_percent);
 
1069
 
 
1070
    printf $txt_fh ($txt_format, @content) if $txt_fh;
 
1071
    printf $htm_fh ($htm_format, @content) if $htm_fh;
 
1072
    if ($xls_fh)
 
1073
    {
 
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);
 
1076
    }
 
1077
 
830
1078
}
831
 
push(@chartdatanames, "Over " . format_time($queue_times[$#queue_times]));
832
 
push(@chartdatavals, $queue_more_than);
 
1079
 
 
1080
push(@chartdatanames, "Over " . format_time($times_aref->[-1]));
 
1081
push(@chartdatavals, $overflow);
833
1082
 
834
1083
#printf("Unknown   %6d\n", $queue_unknown) if $queue_unknown > 0;
835
 
if ($html) {
836
 
  print "</table>\n";
837
 
  print "</td><td>\n";
 
1084
if ($htm_fh) {
 
1085
  print $htm_fh "</table>\n";
 
1086
  print $htm_fh "</td><td>\n";
838
1087
 
839
 
  if ($HAVE_GD_Graph_pie && $charts) {
 
1088
  if ($HAVE_GD_Graph_pie && $charts && ($#chartdatavals > 0)) {
840
1089
    my @data = (
841
1090
       \@chartdatanames,
842
1091
       \@chartdatavals
843
1092
    );
844
1093
    my $graph = GD::Graph::pie->new(200, 200);
845
 
    my $pngname;
846
 
    my $title;
847
 
    if ($string =~ /all/) { $pngname = "queue_all.png"; $title = "Queue (all)"; }
848
 
    if ($string =~ /remote/) { $pngname = "queue_rem.png"; $title = "Queue (remote)"; }
849
 
    $graph->set(
850
 
        title             => $title,
851
 
    );
 
1094
    my $pngname = "$title-$message_type.png";
 
1095
    $pngname =~ s/[^\w\-\.]/_/;
 
1096
 
 
1097
    my $graph_title = "$title ($message_type)";
 
1098
    $graph->set(title => $graph_title) if (length($graph_title) < 21);
 
1099
 
852
1100
    my $gd = $graph->plot(\@data) or warn($graph->error);
853
1101
    if ($gd) {
854
 
      open(IMG, ">$chartdir/$pngname") or die $!;
 
1102
      open(IMG, ">$chartdir/$pngname") or die "Could not write $chartdir/$pngname: $!\n";
855
1103
      binmode IMG;
856
1104
      print IMG $gd->png;
857
1105
      close IMG;
858
 
      print "<img src=\"$chartrel/$pngname\">";
 
1106
      print $htm_fh "<img src=\"$chartrel/$pngname\">";
859
1107
    }
860
1108
  }
861
 
  print "</td></tr></table>\n";
862
 
}
863
 
print "\n";
864
 
}
865
 
 
866
 
 
867
 
 
868
 
=head2 print_histogram();
869
 
 
870
 
 print_histogram('Deliverieds|Messages received',@interval_count);
871
 
 
872
 
Print a histogram of the messages delivered/received per time slot
873
 
(hour by default).
874
 
 
875
 
=cut
876
 
 
 
1109
  print $htm_fh "</td></tr></table>\n";
 
1110
}
 
1111
 
 
1112
if ($xls_fh)
 
1113
{
 
1114
  $row++;
 
1115
}
 
1116
print $txt_fh "\n" if $txt_fh;
 
1117
print $htm_fh "\n" if $htm_fh;
 
1118
 
 
1119
}
 
1120
 
 
1121
 
 
1122
#######################################################################
 
1123
# print_histogram();
 
1124
#
 
1125
#  print_histogram('Deliveries|Messages received|$pattern', $unit, @interval_count);
 
1126
#
 
1127
# Print a histogram of the messages delivered/received per time slot
 
1128
# (hour by default).
 
1129
#######################################################################
877
1130
sub print_histogram {
878
 
my($text) = shift;
879
 
my(@interval_count) = @_;
 
1131
my($text, $unit, @interval_count) = @_;
880
1132
my(@chartdatanames);
881
1133
my(@chartdatavals);
882
1134
my($maxd) = 0;
 
1135
 
 
1136
if (!$run_hist) # save first row of print_histogram for xls output
 
1137
{
 
1138
  $row_hist = $row;
 
1139
}
 
1140
else
 
1141
{
 
1142
  $row = $row_hist;
 
1143
}
 
1144
 
883
1145
for ($i = 0; $i < $hist_number; $i++)
884
1146
  { $maxd = $interval_count[$i] if $interval_count[$i] > $maxd; }
885
1147
 
886
1148
my $scale = int(($maxd + 25)/50);
887
1149
$scale = 1 if $scale == 0;
888
1150
 
889
 
my($type);
890
 
if ($text eq "Deliveries")
891
 
  {
892
 
  $type = ($scale == 1)? "delivery" : "deliveries";
893
 
  }
894
 
else
895
 
  {
896
 
  $type = ($scale == 1)? "message" : "messages";
897
 
  }
898
 
 
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");
902
 
 
903
 
if ($html) {
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";
907
 
}
908
 
else {
909
 
  printf("%s\n%s\n\n", $title, "-" x length($title));
910
 
}
 
1151
if ($scale != 1) {
 
1152
  if ($unit !~ s/y$/ies/) {
 
1153
    $unit .= 's';
 
1154
  }
 
1155
}
 
1156
 
 
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");
 
1161
 
 
1162
my $txt_htm_title = $title . " (each dot is $scale $unit)";
 
1163
 
 
1164
printf $txt_fh ("%s\n%s\n\n", $txt_htm_title, "-" x length($txt_htm_title)) if $txt_fh;
 
1165
 
 
1166
if ($htm_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";
 
1170
}
 
1171
 
 
1172
if ($xls_fh)
 
1173
{
 
1174
  $title =~ s/Messages/Msg/ ;
 
1175
  $ws_global->write($row++, $col_hist+1, $title, $f_headertab);
 
1176
}
 
1177
 
911
1178
 
912
1179
my $hour = 0;
913
1180
my $minutes = 0;
914
1181
for ($i = 0; $i < $hist_number; $i++)
915
 
  {
 
1182
{
916
1183
  my $c = $interval_count[$i];
917
1184
 
918
1185
  # If the interval is an hour (the maximum) print the starting and
921
1188
 
922
1189
  my $temp;
923
1190
  if ($hist_opt == 1)
924
 
    {
 
1191
  {
925
1192
    $temp = sprintf("%02d-%02d", $hour, $hour + 1);
926
 
    print $temp;
 
1193
 
 
1194
    print $txt_fh $temp if $txt_fh;
 
1195
    print $htm_fh $temp if $htm_fh;
 
1196
 
 
1197
    if ($xls_fh)
 
1198
    {
 
1199
      if ($run_hist==0) # only on first run
 
1200
      {
 
1201
        &set_worksheet_line($ws_global, $row, 0, [$temp], $f_default);
 
1202
      }
 
1203
    }
 
1204
 
927
1205
    push(@chartdatanames, $temp);
928
1206
    $hour++;
929
 
    }
 
1207
  }
930
1208
  else
931
 
    {
 
1209
  {
932
1210
    if ($minutes == 0)
933
1211
      { $temp = sprintf("%02d:%02d", $hour, $minutes) }
934
1212
    else
935
1213
      { $temp = sprintf("  :%02d", $minutes) }
936
 
    print $temp;
 
1214
 
 
1215
    print $txt_fh $temp if $txt_fh;
 
1216
    print $htm_fh $temp if $htm_fh;
 
1217
    if (($xls_fh) and ($run_hist==0)) # only on first run
 
1218
    {
 
1219
      $temp = sprintf("%02d:%02d", $hour, $minutes);
 
1220
      &set_worksheet_line($ws_global, $row, 0, [$temp], $f_default);
 
1221
    }
 
1222
 
937
1223
    push(@chartdatanames, $temp);
938
1224
    $minutes += $hist_interval;
939
1225
    if ($minutes >= 60)
941
1227
      $minutes = 0;
942
1228
      $hour++;
943
1229
      }
944
 
    }
 
1230
  }
945
1231
  push(@chartdatavals, $c);
946
 
  printf(" %6d %s\n", $c, "." x ($c/$scale));
 
1232
 
 
1233
  printf $txt_fh (" %6d %s\n", $c, "." x ($c/$scale)) if $txt_fh;
 
1234
  printf $htm_fh (" %6d %s\n", $c, "." x ($c/$scale)) if $htm_fh;
 
1235
  if ($xls_fh)
 
1236
  {
 
1237
    &set_worksheet_line($ws_global, $row++, $col_hist+1, [$c], $f_default);
947
1238
  }
948
 
print "\n";
949
 
if ($html)
950
 
  {
951
 
  print "</pre>\n";
952
 
  print "</td><td>\n";
953
 
  if ($HAVE_GD_Graph_linespoints && $charts) {
 
1239
 
 
1240
} #end for
 
1241
 
 
1242
printf $txt_fh "\n" if $txt_fh;
 
1243
printf $htm_fh "\n" if $htm_fh;
 
1244
 
 
1245
if ($htm_fh)
 
1246
{
 
1247
  print $htm_fh "</pre>\n";
 
1248
  print $htm_fh "</td><td>\n";
 
1249
  if ($HAVE_GD_Graph_linespoints && $charts && ($#chartdatavals > 0)) {
954
1250
    # calculate the graph
955
1251
    my @data = (
956
1252
       \@chartdatanames,
963
1259
        title             => $text,
964
1260
        x_labels_vertical => 1
965
1261
    );
966
 
    my($pngname);
967
 
    if ($text =~ /Deliveries/) { $pngname = "histogram_del.png"; }
968
 
    if ($text =~ /Messages/)   { $pngname = "histogram_mes.png"; }
 
1262
    my $pngname = "histogram_$text.png";
 
1263
    $pngname =~ s/[^\w\._]/_/g;
 
1264
 
969
1265
    my $gd = $graph->plot(\@data) or warn($graph->error);
970
1266
    if ($gd) {
971
 
      open(IMG, ">$chartdir/$pngname") or die $!;
 
1267
      open(IMG, ">$chartdir/$pngname") or die "Could not write $chartdir/$pngname: $!\n";
972
1268
      binmode IMG;
973
1269
      print IMG $gd->png;
974
1270
      close IMG;
975
 
      print "<img src=\"$chartrel/$pngname\">";
 
1271
      print $htm_fh "<img src=\"$chartrel/$pngname\">";
976
1272
    }
977
1273
  }
978
 
  print "</td></tr></table>\n";
979
 
}
980
 
}
981
 
 
982
 
 
983
 
 
984
 
=head2 print_league_table();
985
 
 
986
 
 print_league_table($league_table_type,\%message_count,\%message_data,\%message_data_gigs);
987
 
 
988
 
Given hashes of message count and message data, which are keyed by
989
 
the table type (eg by the sending host), print a league table
990
 
showing the top $topcount (defaults to 50).
991
 
 
992
 
=cut
993
 
 
 
1274
  print $htm_fh "</td></tr></table>\n";
 
1275
}
 
1276
 
 
1277
$col_hist++; # where to continue next times
 
1278
 
 
1279
$row+=2;     # leave some space after history block
 
1280
$run_hist=1; # we have done this once or more
 
1281
}
 
1282
 
 
1283
 
 
1284
 
 
1285
#######################################################################
 
1286
# print_league_table();
 
1287
#
 
1288
#  print_league_table($league_table_type,\%message_count,\%message_data,\%message_data_gigs);
 
1289
#
 
1290
# Given hashes of message count and message data, which are keyed by
 
1291
# the table type (eg by the sending host), print a league table
 
1292
# showing the top $topcount (defaults to 50).
 
1293
#######################################################################
994
1294
sub print_league_table {
995
1295
my($text,$m_count,$m_data,$m_data_gigs) = @_;
996
1296
my($name) = ($topcount == 1)? "$text" : "$topcount ${text}s";
999
1299
my(@chartdatavals) = ();
1000
1300
my $chartotherval = 0;
1001
1301
 
1002
 
my($format);
1003
 
if ($html) {
1004
 
  print "<hr><a name=\"$text count\"></a><h2>$temp</h2>\n";
1005
 
  print "<table border=0 width=\"100%\">\n";
1006
 
  print "<tr><td>\n";
1007
 
  print "<table border=1>\n";
1008
 
  print "<tr><th>Messages</th><th>Bytes</th><th>\u$text</th>\n";
 
1302
my $htm_format;
 
1303
my $txt_format = "%7d %10s   %s\n";
 
1304
 
 
1305
# write header
 
1306
printf $txt_fh ("%s\n%s\n\n", $temp, "-" x length($temp)) if $txt_fh;
 
1307
if ($htm_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";
1009
1313
 
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";
1014
 
}
1015
 
else {
1016
 
  printf("%s\n%s\n\n", $temp, "-" x length($temp));
1017
 
  $format = "%7d %10s   %s\n";
1018
 
}
1019
 
 
1020
 
my($key,$htmlkey);
 
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";
 
1318
}
 
1319
if ($xls_fh)
 
1320
{
 
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 );
 
1323
}
 
1324
 
 
1325
 
 
1326
# write content
 
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)) {
1022
 
  if ($html) {
 
1329
 
 
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});
 
1336
  $data = $gigs = 0;
 
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);
 
1341
 
 
1342
  # write content
 
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;
 
1345
 
 
1346
  if ($htm_fh) {
1023
1347
    $htmlkey = $key;
1024
1348
    $htmlkey =~ s/>/\&gt\;/g;
1025
1349
    $htmlkey =~ s/</\&lt\;/g;
1026
 
    printf($format, $$m_count{$key}, volume_rounded($$m_data{$key},$$m_data_gigs{$key}), $htmlkey);
1027
 
  }
1028
 
  else {
1029
 
    printf($format, $$m_count{$key}, volume_rounded($$m_data{$key},$$m_data_gigs{$key}), $key);
1030
 
  }
 
1350
    printf $htm_fh ($htm_format, @content, $htmlkey);
 
1351
  }
 
1352
  if ($xls_fh)
 
1353
  {
 
1354
    &set_worksheet_line($ws_top50, $row_league_table++, 0, [@content, $key], $f_default);
 
1355
  }
 
1356
 
1031
1357
  if (scalar @chartdatanames < $ntopchart)
1032
 
    {
 
1358
  {
1033
1359
    push(@chartdatanames, $key);
1034
1360
    push(@chartdatavals, $$m_count{$key});
1035
 
    }
 
1361
  }
1036
1362
  else
1037
 
    {
 
1363
  {
1038
1364
    $chartotherval += $$m_count{$key};
1039
 
    }
1040
1365
  }
 
1366
}
 
1367
 
1041
1368
push(@chartdatanames, "Other");
1042
1369
push(@chartdatavals, $chartotherval);
1043
1370
 
1044
 
if ($html)
1045
 
  {
1046
 
  print "</table>\n";
1047
 
  print "</td><td>\n";
1048
 
  if ($HAVE_GD_Graph_pie && $charts)
 
1371
print $txt_fh "\n" if $txt_fh;
 
1372
if ($htm_fh)
 
1373
{
 
1374
  print $htm_fh "</table>\n";
 
1375
  print $htm_fh "</td><td>\n";
 
1376
  if ($HAVE_GD_Graph_pie && $charts && ($#chartdatavals > 0))
1049
1377
    {
1050
1378
    # calculate the graph
1051
1379
    my @data = (
1062
1390
    if ($gd) {
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";
1066
1394
      binmode IMG;
1067
1395
      print IMG $gd->png;
1068
1396
      close IMG;
1069
 
      print "<img src=\"$chartrel/${temp}_count.png\">";
 
1397
      print $htm_fh "<img src=\"$chartrel/${temp}_count.png\">";
1070
1398
    }
1071
1399
  }
1072
 
  print "</td><td>\n";
1073
 
  print "</td></tr></table>\n";
1074
 
}
1075
 
print "\n";
 
1400
  print $htm_fh "</td><td>\n";
 
1401
  print $htm_fh "</td></tr></table>\n\n";
 
1402
}
 
1403
if ($xls_fh)
 
1404
{
 
1405
  $row_league_table++;
 
1406
}
 
1407
 
 
1408
 
 
1409
# write header
1076
1410
 
1077
1411
$temp = "Top $name by volume";
1078
 
if ($html) {
1079
 
  print "<hr><a name=\"$text volume\"></a><h2>$temp</h2>\n";
1080
 
  print "<table border=0 width=\"100%\">\n";
1081
 
  print "<tr><td>\n";
1082
 
  print "<table border=1>\n";
1083
 
  print "<tr><th>Messages</th><th>Bytes</th><th>\u$text</th>\n";
 
1412
 
 
1413
printf $txt_fh ("%s\n%s\n\n", $temp, "-" x length($temp)) if $txt_fh;
 
1414
if ($htm_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";
1084
1420
}
1085
 
else {
1086
 
  printf("%s\n%s\n\n", $temp, "-" x length($temp));
 
1421
if ($xls_fh)
 
1422
{
 
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);
1087
1425
}
1088
1426
 
1089
1427
@chartdatanames = ();
1090
1428
@chartdatavals = ();
1091
1429
$chartotherval = 0;
 
1430
my $use_gig = 0;
1092
1431
foreach $key (top_n_sort($topcount,$m_data_gigs,$m_data,$m_count)) {
1093
 
  if ($html) {
 
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}) {
 
1436
    $use_gig = 1;
 
1437
  }
 
1438
 
 
1439
  $rounded_volume = volume_rounded($$m_data{$key},$$m_data_gigs{$key});
 
1440
  $data = $gigs = 0;
 
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 );
 
1445
 
 
1446
  # write content
 
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;
 
1449
  if ($htm_fh) {
1094
1450
    $htmlkey = $key;
1095
1451
    $htmlkey =~ s/>/\&gt\;/g;
1096
1452
    $htmlkey =~ s/</\&lt\;/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);
 
1454
  }
 
1455
  if ($xls_fh)
 
1456
  {
 
1457
    &set_worksheet_line($ws_top50, $row_league_table++, 0, [@content, $key], $f_default);
 
1458
  }
 
1459
 
 
1460
 
 
1461
  if (scalar @chartdatanames < $ntopchart) {
 
1462
    if ($use_gig) {
 
1463
      if ($$m_data_gigs{$key}) {
 
1464
        push(@chartdatanames, $key);
 
1465
        push(@chartdatavals, $$m_data_gigs{$key});
 
1466
      }
 
1467
    }
 
1468
    else {
 
1469
      push(@chartdatanames, $key);
 
1470
      push(@chartdatavals, $$m_data{$key});
 
1471
    }
1098
1472
  }
1099
1473
  else {
1100
 
    printf($format, $$m_count{$key}, volume_rounded($$m_data{$key},$$m_data_gigs{$key}), $key);
1101
 
  }
1102
 
 
1103
 
  if (scalar @chartdatanames < $ntopchart)
1104
 
    {
1105
 
    push(@chartdatanames, $key);
1106
 
    push(@chartdatavals, $$m_count{$key});
1107
 
    }
1108
 
  else
1109
 
    {
1110
 
    $chartotherval += $$m_count{$key};
1111
 
    }
1112
 
  }
 
1474
    $chartotherval += ($use_gig) ? $$m_data_gigs{$key} : $$m_data{$key};
 
1475
  }
 
1476
}
1113
1477
push(@chartdatanames, "Other");
1114
1478
push(@chartdatavals, $chartotherval);
1115
1479
 
1116
 
if ($html) {
1117
 
  print "</table>\n";
1118
 
  print "</td><td>\n";
1119
 
  if ($HAVE_GD_Graph_pie && $charts) {
 
1480
print $txt_fh "\n" if $txt_fh;
 
1481
if ($htm_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
1121
1486
    my @data = (
1122
1487
       \@chartdatanames,
1125
1490
    my $graph = GD::Graph::pie->new(300, 300);
1126
1491
    $graph->set(
1127
1492
        x_label           => 'Name',
1128
 
        y_label           => 'Volume',
 
1493
        y_label           => 'Volume' ,
1129
1494
        title             => 'By Volume',
1130
1495
    );
1131
1496
    my $gd = $graph->plot(\@data) or warn($graph->error);
1132
1497
    if ($gd) {
1133
 
      my $temp = $text;
 
1498
      $temp = $text;
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";
1136
1501
      binmode IMG;
1137
1502
      print IMG $gd->png;
1138
1503
      close IMG;
1139
 
      print "<img src=\"$chartrel/${temp}_volume.png\">";
 
1504
      print $htm_fh "<img src=\"$chartrel/${temp}_volume.png\">";
1140
1505
    }
1141
1506
  }
1142
 
  print "</td><td>\n";
1143
 
  print "</td></tr></table>\n";
1144
 
}
1145
 
 
1146
 
print "\n";
1147
 
}
1148
 
 
1149
 
 
1150
 
=head2 top_n_sort();
1151
 
 
1152
 
  @sorted_keys = top_n_sort($n,$href1,$href2,$href3);
1153
 
 
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.
1157
 
 
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.
1162
 
 
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.
1166
 
 
1167
 
We assume the values are > 0.
1168
 
 
1169
 
=cut
1170
 
 
 
1507
  print $htm_fh "</td><td>\n";
 
1508
  print $htm_fh "</td></tr></table>\n\n";
 
1509
}
 
1510
if ($xls_fh)
 
1511
{
 
1512
  ++$row_league_table;
 
1513
}
 
1514
 
 
1515
}
 
1516
 
 
1517
#######################################################################
 
1518
# top_n_sort();
 
1519
#
 
1520
#   @sorted_keys = top_n_sort($n,$href1,$href2,$href3);
 
1521
#
 
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.
 
1525
#
 
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.
 
1530
#
 
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.
 
1534
#
 
1535
# We assume the values are > 0.
 
1536
#######################################################################
1171
1537
sub top_n_sort {
1172
1538
  my($n,$href1,$href2,$href3) = @_;
1173
1539
 
1200
1566
    # Check to see that the new value is bigger than the lowest of the
1201
1567
    # top n keys that we're keeping.
1202
1568
    $comparison = $value1        <=> $minimum_value1 ||
1203
 
                  $href2->{$key} <=> $minimum_value2 ||
1204
 
                  $href3->{$key} <=> $minimum_value3 ||
1205
 
                  $top_n_key cmp $key;
 
1569
                  $href2->{$key} <=> $minimum_value2 ||
 
1570
                  $href3->{$key} <=> $minimum_value3 ||
 
1571
                  $top_n_key cmp $key;
1206
1572
    next unless ($comparison == 1);
1207
1573
 
1208
1574
    # As we will be using these values a few times, extract them into scalars.
1226
1592
    for ($i = 0; $i < $n_minus_1; $i++) {
1227
1593
      $top_n_key = $top_n_keys[$i];
1228
1594
      if ( ($top_n_key eq '_') ||
1229
 
           ( ($value1 <=> $href1->{$top_n_key} ||
 
1595
           ( ($value1 <=> $href1->{$top_n_key} ||
1230
1596
              $value2 <=> $href2->{$top_n_key} ||
1231
 
              $value3 <=> $href3->{$top_n_key} ||
1232
 
              $top_n_key cmp $key) == 1
1233
 
           )
1234
 
         ) {
1235
 
        $insert_position = $i;
1236
 
        last;
 
1597
              $value3 <=> $href3->{$top_n_key} ||
 
1598
              $top_n_key cmp $key) == 1
 
1599
           )
 
1600
         ) {
 
1601
        $insert_position = $i;
 
1602
        last;
1237
1603
      }
1238
1604
    }
1239
1605
 
1256
1622
}
1257
1623
 
1258
1624
 
1259
 
=head2 html_header();
1260
 
 
1261
 
 $header = html_header($title);
1262
 
 
1263
 
Print our HTML header and start the <body> block.
1264
 
 
1265
 
=cut
1266
 
 
 
1625
#######################################################################
 
1626
# html_header();
 
1627
#
 
1628
#  $header = html_header($title);
 
1629
#
 
1630
# Print our HTML header and start the <body> block.
 
1631
#######################################################################
1267
1632
sub html_header {
1268
1633
  my($title) = @_;
1269
1634
  my $text = << "EoText";
1281
1646
 
1282
1647
 
1283
1648
 
1284
 
=head2 help();
1285
 
 
1286
 
 help();
1287
 
 
1288
 
Display usage instructions and exit.
1289
 
 
1290
 
=cut
1291
 
 
 
1649
#######################################################################
 
1650
# help();
 
1651
#
 
1652
#  help();
 
1653
#
 
1654
# Display usage instructions and exit.
 
1655
#######################################################################
1292
1656
sub help {
1293
1657
  print << "EoText";
1294
1658
 
1295
1659
eximstats Version $VERSION
1296
1660
 
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
1301
 
 
1302
 
Parses exim mainlog files and generates a statistical analysis of
1303
 
the messages processed. Valid options are:
1304
 
 
 
1661
Usage:
 
1662
  eximstats [Output] [Options] mainlog1 mainlog2 ...
 
1663
  eximstats -merge -html [Options] report.1.html ... > weekly_rep.html
 
1664
 
 
1665
Examples:
 
1666
  eximstats -html=eximstats.html mainlog1 mainlog2 ...
 
1667
  eximstats mainlog1 mainlog2 ... > report.txt
 
1668
 
 
1669
Parses exim mainlog or syslog files and generates a statistical analysis
 
1670
of the messages processed.
 
1671
 
 
1672
Valid output types are:
 
1673
-txt[=<file>]   plain text (default unless no other type is specified)
 
1674
-html[=<file>]  HTML
 
1675
-xls[=<file>]   Excel
 
1676
With no type and file given, defaults to -txt and STDOUT.
 
1677
 
 
1678
Valid options are:
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
1320
 
 
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.
 
1696
                Eg -show_rt1,2,4,8.
 
1697
 
 
1698
-include_original_destination   show both the final and original
 
1699
                destinations in the results rather than just the final ones.
 
1700
 
 
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
1326
1706
 
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/'
1331
1711
 
1332
1712
-merge          merge previously generated reports into a new report
1333
1713
 
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 '.'
1340
1720
 
1341
 
-d              Debug mode - dump the eval'ed parser onto STDERR.
 
1721
-d              Debug mode - dump the eval'ed parser onto STDERR.
1342
1722
 
1343
1723
EoText
1344
1724
 
1347
1727
 
1348
1728
 
1349
1729
 
1350
 
=head2 generate_parser();
1351
 
 
1352
 
 $parser = generate_parser();
1353
 
 
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.
1357
 
 
1358
 
I've tested using study(), but this does not improve performance.
1359
 
 
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.
1363
 
 
1364
 
=cut
1365
 
 
 
1730
#######################################################################
 
1731
# generate_parser();
 
1732
#
 
1733
#  $parser = generate_parser();
 
1734
#
 
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.
 
1738
#
 
1739
# I've tested using study(), but this does not improve performance.
 
1740
#
 
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 {
1367
1746
  my $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>) {
 
1751
 
 
1752
    # Convert syslog lines to mainlog format.
 
1753
    if (! /^\\d{4}/) {
 
1754
      next unless s/^.*? exim\\b.*?: //;
 
1755
    }
 
1756
 
1371
1757
    next if length($_) < 38;
1372
 
 
1373
 
    # PH/FANF
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;
1376
1759
 
1377
1760
    ($tod,$m_hour,$m_min) = ($1,$2,$3);
1386
1769
  my $user_pattern_index = 0;
1387
1770
  foreach (@user_patterns) {
1388
1771
    $user_pattern_totals[$user_pattern_index] = 0;
1389
 
    $parser .= "  \$user_pattern_totals[$user_pattern_index]++ if $_;\n";
 
1772
    $parser .= "    if ($_) {\n";
 
1773
    $parser .= "      \$user_pattern_totals[$user_pattern_index]++;\n";
 
1774
    $parser .= "      \$user_pattern_interval_count[$user_pattern_index][(\$m_hour*60 + \$m_min)/$hist_interval]++;\n" if ($hist_opt > 0);
 
1775
    $parser .= "    }\n";
1390
1776
    $user_pattern_index++;
1391
1777
  }
1392
1778
 
1399
1785
 
1400
1786
    $_ = substr($_, 40 + $extra);  # PH
1401
1787
 
 
1788
    # Get a pointer to an array of information about the message.
 
1789
    # This minimises the number of calls to hash functions.
 
1790
    $messages{$id} = [] unless exists $messages{$id};
 
1791
    $message_aref = $messages{$id};
 
1792
 
 
1793
 
1402
1794
    # JN - Skip over certain transports as specified via the "-nt/.../" command
1403
1795
    # line switch (where ... is a perl style regular expression).  This is
1404
1796
    # required so that transports that skew stats such as SpamAssassin can be
1410
1802
    #ENDIF ($transport_pattern)
1411
1803
 
1412
1804
 
1413
 
    $host = "local";            #Host is local unless otherwise specified.
1414
 
    $domain = "localdomain";    #Domain is localdomain unless otherwise specified.
 
1805
    $host = "local";          #Host is local unless otherwise specified.
 
1806
    $domain = "localdomain";  #Domain is localdomain unless otherwise specified.
1415
1807
 
1416
1808
 
1417
1809
    # Do some pattern matches to get the host and IP address.
1429
1821
 
1430
1822
      #IFDEF ($do_sender{Domain})
1431
1823
      if ($host !~ /^\\[/ && $host =~ /^(\\(?)[^\\.]+\\.([^\\.]+\\..*)/) {
1432
 
        # Remove the host portion from the DNS name. We ensure that we end up with
1433
 
        # at least xxx.yyy. $host can be "(x.y.z)" or  "x.y.z".
1434
 
        $domain = lc("$1.$2");
1435
 
        $domain =~ s/^\\.//;            #Remove preceding dot.
 
1824
        # Remove the host portion from the DNS name. We ensure that we end up
 
1825
        # with at least xxx.yyy. $host can be "(x.y.z)" or  "x.y.z".
 
1826
        $domain = lc("$1.$2");
 
1827
        $domain =~ s/^\\.//;         #Remove preceding dot.
1436
1828
      }
1437
1829
      #ENDIF ($do_sender{Domain})
1438
1830
 
1439
1831
    }
1440
1832
 
1441
1833
    #IFDEF ($do_sender{Email})
1442
 
    $email = (/^(\S+)/) ? $1 : "";
 
1834
      #IFDEF ($include_original_destination)
 
1835
      # Catch both "a@b.com <c@d.com>" and "e@f.com"
 
1836
      #$email = (/^(\S+) (<(\S*?)>)?/) ? $3 || $1 : "";
 
1837
      $email = (/^(\S+ (<[^@>]+@?[^>]*>)?)/) ? $1 : "";
 
1838
      chomp($email);
 
1839
      #ENDIF ($include_original_destination)
 
1840
 
 
1841
      #IFNDEF ($include_original_destination)
 
1842
      $email = (/^(\S+)/) ? $1 : "";
 
1843
      #ENDIF ($include_original_destination)
1443
1844
    #ENDIF ($do_sender{Email})
1444
1845
 
1445
1846
    #IFDEF ($do_sender{Edomain})
1446
 
    $edomain = (/^\S*?\\@(\S+)/) ? lc($1) : "";
 
1847
      #IFDEF ($include_original_destination)
 
1848
      #$edomain = (/^(\S+) (<\S*?\\@(\S+)>)?/) ? $3 || $1 : "";
 
1849
      $edomain = (/^(\S+ (<\S*?\\@(\S+?)>)?)/) ? $1 : "";
 
1850
      chomp($edomain);
 
1851
      lc($edomain);
 
1852
      #ENDIF ($include_original_destination)
 
1853
 
 
1854
      #IFNDEF ($include_original_destination)
 
1855
      $edomain = (/^\S*?\\@(\S+)/) ? lc($1) : "";
 
1856
      #ENDIF ($include_original_destination)
1447
1857
    #ENDIF ($do_sender{Edomain})
1448
1858
 
1449
1859
    if ($tod lt $begin) {
1456
1866
 
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;
1460
1871
 
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;
1468
1879
      }
1469
1880
      #ENDIF ($show_relay)
1470
1881
 
1471
1882
      #IFDEF ($local_league_table || $include_remote_users)
1472
 
        if (/\sU=(\\S+)/) {
1473
 
          my $user = $1;
1474
 
 
1475
 
          #IFDEF ($local_league_table && $include_remote_users)
1476
 
          {                             #Store both local and remote users.
1477
 
          #ENDIF ($local_league_table && $include_remote_users)
1478
 
 
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)
1482
 
 
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)
1486
 
 
1487
 
            $received_count_user{$user}++;
1488
 
            add_volume(\\$received_data_user{$user},\\$received_data_gigs_user{$user},$thissize);
 
1883
        if (/\sU=(\\S+)/) {
 
1884
          my $user = $1;
 
1885
 
 
1886
          #IFDEF ($local_league_table && $include_remote_users)
 
1887
          {                         #Store both local and remote users.
 
1888
          #ENDIF ($local_league_table && $include_remote_users)
 
1889
 
 
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)
 
1893
 
 
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)
 
1897
 
 
1898
            ++$received_count_user{$user};
 
1899
            add_volume(\\$received_data_user{$user},\\$received_data_gigs_user{$user},$thissize);
1489
1900
          }
1490
 
        }
 
1901
        }
1491
1902
      #ENDIF ($local_league_table || $include_remote_users)
1492
1903
 
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})
1497
1908
 
1498
1909
      #IFDEF ($do_sender{Domain})
1499
1910
        if ($domain) {
1500
 
          $received_count{Domain}{$domain}++;
1501
 
          add_volume(\\$received_data{Domain}{$domain},\\$received_data_gigs{Domain}{$domain},$thissize);
1502
 
        }
 
1911
          ++$received_count{Domain}{$domain};
 
1912
          add_volume(\\$received_data{Domain}{$domain},\\$received_data_gigs{Domain}{$domain},$thissize);
 
1913
        }
1503
1914
      #ENDIF ($do_sender{Domain})
1504
1915
 
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})
1509
1920
 
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})
1514
1925
 
1515
 
      $total_received_count++;
 
1926
      ++$total_received_count;
1516
1927
      add_volume(\\$total_received_data,\\$total_received_data_gigs,$thissize);
1517
1928
 
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)
1521
1932
 
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)
1525
1936
    }
1526
1937
 
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;
1531
1942
 
1532
1943
 
1533
1944
        #IFDEF ($show_relay)
1537
1948
        # addresses, there may be a further address between the first
1538
1949
        # and last.
1539
1950
 
1540
 
        if (defined $from_host{$id}) {
 
1951
        if (defined $message_aref->[$FROM_HOST]) {
1541
1952
          if (/^(\\S+)(?:\\s+\\([^)]\\))?\\s+<([^>]+)>/) {
1542
1953
            ($old,$new) = ($1,$2);
1543
 
          }
 
1954
          }
1544
1955
          else {
1545
 
            $old = $new = "";
1546
 
          }
 
1956
            $old = $new = "";
 
1957
          }
1547
1958
 
1548
1959
          if ("\\L$new" eq "\\L$old") {
1549
1960
            ($old) = /^(\\S+)/ if $old eq "";
1550
 
            my $key = "H=\\L$from_host{$id}\\E A=\\L$from_address{$id}\\E => " .
 
1961
            my $key = "H=\\L$message_aref->[$FROM_HOST]\\E A=\\L$message_aref->[$FROM_ADDRESS]\\E => " .
1551
1962
              "H=\\L$host\\E$ip A=\\L$old\\E";
1552
1963
            if (!defined $relay_pattern || $key !~ /$relay_pattern/o) {
1553
1964
              $relayed{$key} = 0 if !defined $relayed{$key};
1554
 
              $relayed{$key}++;
1555
 
            }
 
1965
              ++$relayed{$key};
 
1966
            }
1556
1967
            else {
1557
 
              $relayed_unshown++
 
1968
              ++$relayed_unshown;
1558
1969
            }
1559
1970
          }
1560
1971
        }
1563
1974
      }
1564
1975
 
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)
1569
 
 
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)
1573
 
 
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)
1577
 
 
1578
 
          if (my($user) = split((/\\s</)? " <" : " ", $_)) {
1579
 
            if ($user =~ /^[\\/|]/) {
1580
 
              my($parent) = $_ =~ /(<[^@]+@?[^>]*>)/;
1581
 
              $user = "$user $parent" if defined $parent;
1582
 
            }
1583
 
            $delivered_count_user{$user}++;
1584
 
            add_volume(\\$delivered_data_user{$user},\\$delivered_data_gigs_user{$user},$size);
1585
 
          }
1586
 
        }
 
1977
        #IFDEF ($local_league_table && $include_remote_users)
 
1978
        {                         #Store both local and remote users.
 
1979
        #ENDIF ($local_league_table && $include_remote_users)
 
1980
 
 
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)
 
1984
 
 
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)
 
1988
 
 
1989
          if (my($user) = split((/\\s</)? " <" : " ", $_)) {
 
1990
            #IFDEF ($include_original_destination)
 
1991
            {
 
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;
 
1998
            }
 
1999
            ++$delivered_count_user{$user};
 
2000
            add_volume(\\$delivered_data_user{$user},\\$delivered_data_gigs_user{$user},$size);
 
2001
          }
 
2002
        }
1587
2003
      #ENDIF ($local_league_table || $include_remote_users)
1588
2004
 
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})
1594
2010
        if ($domain) {
1595
 
          $delivered_count{Domain}{$domain}++;
1596
 
          add_volume(\\$delivered_data{Domain}{$domain},\\$delivered_data_gigs{Domain}{$domain},$size);
1597
 
        }
 
2011
          ++$delivered_count{Domain}{$domain};
 
2012
          add_volume(\\$delivered_data{Domain}{$domain},\\$delivered_data_gigs{Domain}{$domain},$size);
 
2013
        }
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})
1607
2023
 
1608
 
      $total_delivered_count++;
 
2024
      ++$total_delivered_count;
1609
2025
      add_volume(\\$total_delivered_data,\\$total_delivered_data_gigs,$size);
1610
2026
 
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)
1616
2032
 
1618
2034
        $delivered_interval_count[($m_hour*60 + $m_min)/$hist_interval]++;
1619
2035
      #ENDIF ($hist_opt > 0)
1620
2036
 
 
2037
      #IFDEF ($#delivery_times > 0)
 
2038
        if (/ DT=(\S+)/) {
 
2039
          $seconds = wdhms_seconds($1);
 
2040
          for ($i = 0; $i <= $#delivery_times; $i++) {
 
2041
            if ($seconds < $delivery_times[$i]) {
 
2042
              ++$dt_all_bin[$i];
 
2043
              ++$dt_remote_bin[$i] if $message_aref->[$REMOTE_DELIVERED];
 
2044
              last;
 
2045
            }
 
2046
          }
 
2047
          if ($i > $#delivery_times) {
 
2048
            ++$dt_all_overflow;
 
2049
            ++$dt_remote_overflow if $message_aref->[$REMOTE_DELIVERED];
 
2050
          }
 
2051
        }
 
2052
      #ENDIF ($#delivery_times > 0)
 
2053
 
1621
2054
    }
1622
2055
 
1623
 
    elsif ($flag eq "==" && defined($size{$id}) && !defined($delayed{$id})) {
1624
 
      $delayed_count++;
1625
 
      $delayed{$id} = 1;
 
2056
    elsif ($flag eq "==" && defined($message_aref->[$SIZE]) && !defined($message_aref->[$DELAYED])) {
 
2057
      ++$delayed_count;
 
2058
      $message_aref->[$DELAYED] = 1;
1626
2059
    }
1627
2060
 
1628
2061
    elsif ($flag eq "**") {
1629
 
      $had_error{$id} = 1 if defined ($size{$id});
 
2062
      if (defined ($message_aref->[$SIZE])) {
 
2063
        unless (defined $message_aref->[$HAD_ERROR]) {
 
2064
          ++$message_errors;
 
2065
          $message_aref->[$HAD_ERROR] = 1;
 
2066
        }
 
2067
      }
1630
2068
 
1631
2069
      #IFDEF ($show_errors)
1632
 
        $errors_count{$_}++;
 
2070
        ++$errors_count{$_};
1633
2071
      #ENDIF ($show_errors)
1634
2072
 
1635
2073
    }
1637
2075
    elsif ($flag eq "Co") {
1638
2076
      #Completed?
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.
1642
 
        my($queued);
1643
 
        if (defined $arrival_time{$id}) {
1644
 
          $queued = seconds($tod) - seconds($arrival_time{$id});
1645
 
          delete($arrival_time{$id});
1646
 
        }
1647
 
        else {
1648
 
          $queued = seconds($tod) - id_seconds($id);
1649
 
        }
 
2078
        $queued = queue_time($tod, $message_aref->[$ARRIVAL_TIME], $id);
1650
2079
 
1651
2080
        for ($i = 0; $i <= $#queue_times; $i++) {
1652
2081
          if ($queued < $queue_times[$i]) {
1653
 
            $queue_bin[$i]++;
1654
 
            $remote_queue_bin[$i]++ if $remote_delivered{$id};
 
2082
            ++$qt_all_bin[$i];
 
2083
            ++$qt_remote_bin[$i] if $message_aref->[$REMOTE_DELIVERED];
1655
2084
            last;
1656
 
          }
1657
 
        }
1658
 
        $queue_more_than++ if $i > $#queue_times;
 
2085
          }
 
2086
        }
 
2087
        if ($i > $#queue_times) {
 
2088
          ++$qt_all_overflow;
 
2089
          ++$qt_remote_overflow if $message_aref->[$REMOTE_DELIVERED];
 
2090
        }
1659
2091
      #ENDIF ($#queue_times >= 0)
1660
2092
 
1661
 
      #IFDEF ($show_relay)
1662
 
        delete($from_host{$id});
1663
 
        delete($from_address{$id});
1664
 
      #ENDIF ($show_relay)
1665
 
 
 
2093
      #IFDEF ($#rcpt_times >= 0)
 
2094
        if (/ QT=(\S+)/) {
 
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;
 
2101
          my($protocol);
 
2102
 
 
2103
          if (defined $message_aref->[$PROTOCOL]) {
 
2104
            $protocol = $message_aref->[$PROTOCOL];
 
2105
 
 
2106
            # Create the bin if its not already defined.
 
2107
            unless (exists $rcpt_times_bin{$protocol}) {
 
2108
              initialise_rcpt_times($protocol);
 
2109
            }
 
2110
          }
 
2111
 
 
2112
 
 
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;
 
2117
              last;
 
2118
            }
 
2119
          }
 
2120
 
 
2121
          if ($i > $#rcpt_times) {
 
2122
            ++$rcpt_times_overflow{all};
 
2123
            ++$rcpt_times_overflow{$protocol} if defined $protocol;
 
2124
          }
 
2125
        }
 
2126
      #ENDIF ($#rcpt_times >= 0)
 
2127
 
 
2128
      delete($messages{$id});
1666
2129
    }
1667
2130
  }';
1668
2131
 
1671
2134
  my(%defines_in_operation,$removing_lines,$processed_parser);
1672
2135
  foreach (split (/\n/,$parser)) {
1673
2136
    if ((/^\s*#\s*IFDEF\s*\((.*?)\)/i  && ! eval $1) ||
1674
 
        (/^\s*#\s*IFNDEF\s*\((.*?)\)/i &&   eval $1)    ) {
 
2137
        (/^\s*#\s*IFNDEF\s*\((.*?)\)/i &&   eval $1)    ) {
1675
2138
      $defines_in_operation{$1} = 1;
1676
2139
      $removing_lines = 1;
1677
2140
    }
1678
2141
 
 
2142
    # Convert constants.
 
2143
    while (/(\$[A-Z][A-Z_]*)\b/) {
 
2144
      my $constant = eval $1;
 
2145
      s/(\$[A-Z][A-Z_]*)\b/$constant/;
 
2146
    }
 
2147
 
1679
2148
    $processed_parser .= $_."\n" unless $removing_lines;
1680
2149
 
1681
2150
    if (/^\s*#\s*ENDIF\s*\((.*?)\)/i) {
1682
2151
      delete $defines_in_operation{$1};
1683
2152
      unless (keys %defines_in_operation) {
1684
 
        $removing_lines = 0;
 
2153
        $removing_lines = 0;
1685
2154
      }
1686
2155
    }
1687
2156
  }
1688
 
  print STDERR "# START OF PARSER:\n$processed_parser\n# END OF PARSER\n\n" if $debug;
 
2157
  print STDERR "# START OF PARSER:$processed_parser\n# END OF PARSER\n\n" if $debug;
1689
2158
 
1690
2159
  return $processed_parser;
1691
2160
}
1692
2161
 
1693
2162
 
1694
2163
 
1695
 
=head2 parse();
1696
 
 
1697
 
 parse($parser,\*FILEHANDLE);
1698
 
 
1699
 
This subroutine accepts a parser and a filehandle from main and parses each
1700
 
line. We store the results into global variables.
1701
 
 
1702
 
=cut
1703
 
 
 
2164
#######################################################################
 
2165
# parse();
 
2166
#
 
2167
#  parse($parser,\*FILEHANDLE);
 
2168
#
 
2169
# This subroutine accepts a parser and a filehandle from main and parses each
 
2170
# line. We store the results into global variables.
 
2171
#######################################################################
1704
2172
sub parse {
1705
2173
  my($parser,$fh) = @_;
1706
2174
 
1716
2184
 
1717
2185
 
1718
2186
 
1719
 
=head2 print_header();
1720
 
 
1721
 
 print_header();
1722
 
 
1723
 
Print our headers and contents.
1724
 
 
1725
 
=cut
1726
 
 
 
2187
#######################################################################
 
2188
# print_header();
 
2189
#
 
2190
#  print_header();
 
2191
#
 
2192
# Print our headers and contents.
 
2193
#######################################################################
1727
2194
sub print_header {
1728
2195
 
 
2196
 
1729
2197
  my $title = "Exim statistics from $begin to $end";
1730
2198
 
1731
 
  if ($html) {
1732
 
    print html_header($title);
1733
 
    print "<ul>\n";
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;
 
2200
  if ($htm_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";
1740
2209
    }
 
2210
 
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";
1744
 
    }
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";
 
2214
    }
 
2215
 
 
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";
 
2219
    }
 
2220
 
 
2221
    if ($#rcpt_times >= 0) {
 
2222
      print $htm_fh "<li><a href=\"#Receipt times all messages\">Receipt times</a>\n";
 
2223
    }
 
2224
 
 
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";
1751
2231
      }
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";
1755
2235
      }
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";
1760
2240
      }
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";
1764
2244
      }
1765
2245
    }
1766
 
    print "<li><a href=\"#errors\">List of errors</a>\n" if %errors_count;
1767
 
    print "</ul>\n<hr>\n";
1768
 
 
 
2246
    print $htm_fh "<li><a href=\"#errors\">List of errors</a>\n" if %errors_count;
 
2247
    print $htm_fh "</ul>\n<hr>\n";
1769
2248
  }
1770
 
  else {
1771
 
    print "\n$title\n";
 
2249
  if ($xls_fh)
 
2250
  {
 
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);
 
2253
    $row+=2;
1772
2254
  }
1773
2255
}
1774
2256
 
1775
2257
 
1776
 
=head2 print_grandtotals();
1777
 
 
1778
 
 print_grandtotals();
1779
 
 
1780
 
Print the grand totals.
1781
 
 
1782
 
=cut
1783
 
 
 
2258
#######################################################################
 
2259
# print_grandtotals();
 
2260
#
 
2261
#  print_grandtotals();
 
2262
#
 
2263
# Print the grand totals.
 
2264
#######################################################################
1784
2265
sub print_grandtotals {
1785
2266
 
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);
 
2272
 
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";
1804
2287
  }
1805
2288
 
1806
 
  my($format1,$format2);
1807
 
  if ($html) {
1808
 
    print << "EoText";
1809
 
<a name="grandtotal"></a>
1810
 
<h2>Grand total summary</h2>
1811
 
<table border=1>
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>
1813
 
EoText
 
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>";
1814
2293
 
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>";
1817
 
  }
1818
 
  else {
 
2294
  if ($txt_fh) {
1819
2295
    my $sender_spaces = " " x length($sender_txt_header);
1820
 
    print << "EoText";
1821
 
 
1822
 
Grand total summary
1823
 
-------------------
1824
 
                                    $sender_spaces           At least one address
1825
 
  TOTAL               Volume    Messages $sender_txt_header      Delayed       Failed
1826
 
EoText
1827
 
    $format1 = "  %-16s %9s      %6d $sender_txt_format";
1828
 
    $format2 = "  %6d %4.1f%% %6d %4.1f%%",
1829
 
  }
 
2296
    print $txt_fh "\n";
 
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";
 
2301
  }
 
2302
  if ($htm_fh) {
 
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";
 
2307
  }
 
2308
  if ($xls_fh)
 
2309
  {
 
2310
      $ws_global->write($row++, $col, "Grand total summary", $f_header2);
 
2311
 
 
2312
      $row_tablehead = $row+1; # header-row of TOTALS table
 
2313
 
 
2314
      &set_worksheet_line($ws_global, $row_tablehead, 0, ['Received', 'Delivered', 'TOTAL'], $f_headertab);
 
2315
 
 
2316
      my @content= (
 
2317
        "Volume",
 
2318
        "Messages",
 
2319
        $sender_txt_header,
 
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)"
 
2324
      );
 
2325
 
 
2326
      for (my $i=0; $i < scalar(@content); $i++)
 
2327
      {
 
2328
        $ws_global->write($row_tablehead+$i+1, 2, $content[$i], $f_default);
 
2329
        $row++;
 
2330
      }
 
2331
      $row_max = $row_tablehead+scalar(@content)+2; # continue from this row
 
2332
  }
 
2333
 
 
2334
 
1830
2335
 
1831
2336
  my($volume,$failed_count);
1832
2337
  if ($merge_reports) {
1837
2342
  }
1838
2343
  else {
1839
2344
    $volume = volume_rounded($total_received_data, $total_received_data_gigs);
1840
 
    $failed_count = keys %had_error;
 
2345
    $failed_count = $message_errors;
1841
2346
  }
1842
2347
 
1843
2348
  {
1844
2349
    no integer;
1845
 
    printf("$format1$format2\n",'Received',$volume,$total_received_count,
1846
 
      @received_totals,$delayed_count,
1847
 
      ($total_received_count) ? ($delayed_count*100/$total_received_count) : 0,
1848
 
      $failed_count,
1849
 
      ($total_received_count) ? ($failed_count*100/$total_received_count) : 0);
 
2350
 
 
2351
    my @content=(
 
2352
        $volume,$total_received_count,
 
2353
        @received_totals,
 
2354
        $delayed_count,
 
2355
        ($total_received_count) ? ($delayed_count*100/$total_received_count) : 0,
 
2356
        $failed_count,
 
2357
        ($total_received_count) ? ($failed_count*100/$total_received_count) : 0
 
2358
    );
 
2359
 
 
2360
    printf $txt_fh ("$txt_format1$txt_format2\n", 'Received', @content) if $txt_fh;
 
2361
    printf $htm_fh ("$htm_format1$htm_format2\n", 'Received', @content) if $htm_fh;
 
2362
    if ($xls_fh)
 
2363
    {
 
2364
      $row = $row_tablehead+1;
 
2365
      for (my $i=0; $i < scalar(@content); $i++)
 
2366
      {
 
2367
        if ($i == 4 || $i == 6) {
 
2368
          $ws_global->write($row+$i, 0, $content[$i]/100, $f_percent);
 
2369
        }
 
2370
        else {
 
2371
          $ws_global->write($row+$i, 0, $content[$i], $f_default);
 
2372
        }
 
2373
      }
 
2374
    }
1850
2375
  }
1851
 
 
1852
2376
  if ($merge_reports) {
1853
2377
    $volume = volume_rounded($report_totals{Delivered}{Volume}, $report_totals{Delivered}{'Volume-gigs'});
1854
2378
    $total_delivered_count = get_report_total($report_totals{Delivered},'Messages');
1856
2380
  else {
1857
2381
    $volume = volume_rounded($total_delivered_data, $total_delivered_data_gigs);
1858
2382
  }
1859
 
  printf("$format1\n\n",'Delivered',$volume,$total_delivered_count,@delivered_totals);
1860
 
  print "</table>\n" if $html;
 
2383
 
 
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;
 
2388
  if ($xls_fh)
 
2389
  {
 
2390
 
 
2391
      $row = $row_tablehead+1;
 
2392
      for (my $i=0; $i < scalar(@content); $i++)
 
2393
      {
 
2394
        $ws_global->write($row+$i, 1, $content[$i], $f_default);
 
2395
      }
 
2396
      $row = $row_max;
 
2397
  }
1861
2398
}
1862
2399
 
1863
2400
 
1864
 
=head2 print_user_patterns()
1865
 
 
1866
 
 print_user_patterns();
1867
 
 
1868
 
Print the counts of user specified patterns.
1869
 
 
1870
 
=cut
1871
 
 
 
2401
#######################################################################
 
2402
# print_user_patterns()
 
2403
#
 
2404
#  print_user_patterns();
 
2405
#
 
2406
# Print the counts of user specified patterns.
 
2407
#######################################################################
1872
2408
sub print_user_patterns {
1873
 
  my($format1);
1874
 
 
1875
 
  if ($html) {
1876
 
    print "<hr><a name=\"patterns\"></a><h2>User Specified Patterns</h2>\n";
1877
 
    print "<table border=0 width=\"100%\">\n";
1878
 
    print "<tr><td>\n";
1879
 
    print "<table border=1>\n";
1880
 
    print "<tr><th>&nbsp;</th><th>Total</th>\n";
1881
 
    $format1 = "<tr><td>%s</td><td align=\"right\">%d</td>";
1882
 
  }
1883
 
  else {
1884
 
    print "User Specified Patterns\n";
1885
 
    print "-----------------------";
1886
 
    print "\n                       Total\n";
1887
 
    $format1 = "  %-18s  %6d";
1888
 
  }
 
2409
  my $txt_format1 = "  %-18s  %6d";
 
2410
  my $htm_format1 = "<tr><td>%s</td><td align=\"right\">%d</td>";
 
2411
 
 
2412
  if ($txt_fh) {
 
2413
    print $txt_fh "User Specified Patterns\n";
 
2414
    print $txt_fh "-----------------------";
 
2415
    print $txt_fh "\n                       Total\n";
 
2416
  }
 
2417
  if ($htm_fh) {
 
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>&nbsp;</th><th>Total</th>\n";
 
2423
  }
 
2424
  if ($xls_fh) {
 
2425
      $ws_global->write($row++, $col, "User Specified Patterns", $f_header2);
 
2426
      &set_worksheet_line($ws_global, $row++, 1, ["Total"], $f_headertab);
 
2427
  }
 
2428
 
1889
2429
 
1890
2430
  my($key);
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;
 
2437
      if ($xls_fh)
 
2438
      {
 
2439
        &set_worksheet_line($ws_global, $row++, 0, [$key,$count], $f_default);
 
2440
      }
1896
2441
    }
1897
2442
  }
1898
2443
  else {
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++;
1904
 
    }
1905
 
  }
1906
 
  if ($html) {
1907
 
    print "</table>\n";
1908
 
  }
1909
 
  print "\n";
 
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;
 
2449
      if ($xls_fh)
 
2450
      {
 
2451
        &set_worksheet_line($ws_global, $row++, 0, [$key,$user_pattern_totals[$user_pattern_index]]);
 
2452
      }
 
2453
      $user_pattern_index++;
 
2454
    }
 
2455
  }
 
2456
  print $txt_fh "\n" if $txt_fh;
 
2457
  print $htm_fh "</table>\n\n" if $htm_fh;
 
2458
  if ($xls_fh)
 
2459
  {
 
2460
    ++$row;
 
2461
  }
 
2462
 
 
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++;
 
2468
    }
 
2469
  }
1910
2470
}
1911
2471
 
1912
2472
 
1913
 
=head2 print_transport();
1914
 
 
1915
 
 print_transport();
1916
 
 
1917
 
Print totals by transport.
1918
 
 
1919
 
=cut
1920
 
 
 
2473
#######################################################################
 
2474
# print_transport();
 
2475
#
 
2476
#  print_transport();
 
2477
#
 
2478
# Print totals by transport.
 
2479
#######################################################################
1921
2480
sub print_transport {
1922
 
  my($format1);
1923
2481
  my(@chartdatanames);
1924
2482
  my(@chartdatavals_count);
1925
2483
  my(@chartdatavals_vol);
1926
 
  no integer;           #Lose this for charting the data.
1927
 
 
1928
 
  if ($html) {
1929
 
    print "<hr><a name=\"transport\"></a><h2>Deliveries by Transport</h2>\n";
1930
 
    print "<table border=0 width=\"100%\">\n";
1931
 
    print "<tr><td>\n";
1932
 
    print "<table border=1>\n";
1933
 
    print "<tr><th>&nbsp;</th><th>Volume</th><th>Messages</th>\n";
1934
 
    $format1 = "<tr><td>%s</td><td align=\"right\">%s</td><td align=\"right\">%d</td>";
1935
 
  }
1936
 
  else {
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.
 
2485
 
 
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>";
 
2488
 
 
2489
  if ($txt_fh) {
 
2490
    print $txt_fh "Deliveries by transport\n";
 
2491
    print $txt_fh "-----------------------";
 
2492
    print $txt_fh "\n                      Volume    Messages\n";
 
2493
  }
 
2494
  if ($htm_fh) {
 
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>&nbsp;</th><th>Volume</th><th>Messages</th>\n";
 
2500
  }
 
2501
  if ($xls_fh) {
 
2502
    $ws_global->write($row++, $col, "Deliveries by transport", $f_header2);
 
2503
    &set_worksheet_line($ws_global, $row++, 1, ["Volume", "Messages"], $f_headertab);
1941
2504
  }
1942
2505
 
1943
2506
  my($key);
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'}),
1950
 
        $count);
 
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;
 
2518
      if ($xls_fh) {
 
2519
        &set_worksheet_line($ws_global, $row++, 0, \@content, $f_default);
 
2520
      }
1954
2521
    }
1955
2522
  }
1956
2523
  else {
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;
 
2533
      if ($xls_fh) {
 
2534
        &set_worksheet_line($ws_global, $row++, 0, \@content);
 
2535
      }
1965
2536
    }
1966
2537
  }
1967
 
  if ($html) {
1968
 
    print "</table>\n";
1969
 
    print "</td><td>\n";
1970
 
    if ($HAVE_GD_Graph_pie && $charts)
 
2538
  print $txt_fh "\n" if $txt_fh;
 
2539
  if ($htm_fh) {
 
2540
    print $htm_fh "</table>\n";
 
2541
    print $htm_fh "</td><td>\n";
 
2542
    if ($HAVE_GD_Graph_pie && $charts && ($#chartdatavals_count > 0))
1971
2543
      {
1972
2544
      # calculate the graph
1973
2545
      my @data = (
1982
2554
      );
1983
2555
      my $gd = $graph->plot(\@data) or warn($graph->error);
1984
2556
      if ($gd) {
1985
 
        open(IMG, ">$chartdir/transports_count.png") or die $!;
1986
 
        binmode IMG;
1987
 
        print IMG $gd->png;
1988
 
        close IMG;
1989
 
        print "<img src=\"$chartrel/transports_count.png\">";
 
2557
        open(IMG, ">$chartdir/transports_count.png") or die "Could not write $chartdir/transports_count.png: $!\n";
 
2558
        binmode IMG;
 
2559
        print IMG $gd->png;
 
2560
        close IMG;
 
2561
        print $htm_fh "<img src=\"$chartrel/transports_count.png\">";
1990
2562
      }
1991
2563
    }
1992
 
    print "</td><td>\n";
 
2564
    print $htm_fh "</td><td>\n";
1993
2565
 
1994
 
    if ($HAVE_GD_Graph_pie && $charts) {
 
2566
    if ($HAVE_GD_Graph_pie && $charts && ($#chartdatavals_vol > 0)) {
1995
2567
      my @data = (
1996
2568
         \@chartdatanames,
1997
2569
         \@chartdatavals_vol
2002
2574
      );
2003
2575
      my $gd = $graph->plot(\@data) or warn($graph->error);
2004
2576
      if ($gd) {
2005
 
        open(IMG, ">$chartdir/transports_vol.png") or die $!;
2006
 
        binmode IMG;
2007
 
        print IMG $gd->png;
2008
 
        close IMG;
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";
 
2578
        binmode IMG;
 
2579
        print IMG $gd->png;
 
2580
        close IMG;
 
2581
        print $htm_fh "<img src=\"$chartrel/transports_vol.png\">";
2010
2582
      }
2011
2583
    }
2012
 
    print "</td></tr></table>\n";
2013
 
  }
2014
 
  print "\n";
 
2584
    print $htm_fh "</td></tr></table>\n\n";
 
2585
  }
 
2586
  if ($xls_fh) {
 
2587
    $row++;
 
2588
  }
 
2589
 
2015
2590
}
2016
2591
 
2017
2592
 
2018
2593
 
2019
 
=head2 print_relay();
2020
 
 
2021
 
 print_relay();
2022
 
 
2023
 
Print our totals by relay.
2024
 
 
2025
 
=cut
2026
 
 
 
2594
#######################################################################
 
2595
# print_relay();
 
2596
#
 
2597
#  print_relay();
 
2598
#
 
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) {
2031
2606
    my $shown = 0;
2032
2607
    my $spacing = "";
2033
 
    my($format);
2034
 
 
2035
 
    if ($html) {
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";
2039
 
    }
2040
 
    else {
2041
 
      printf("%s\n%s\n\n", $temp, "-" x length($temp));
2042
 
      $format = "%7d %s\n      => %s\n";
2043
 
    }
 
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";
 
2610
 
 
2611
    printf $txt_fh ("%s\n%s\n\n", $temp, "-" x length($temp)) if $txt_fh;
 
2612
    if ($htm_fh) {
 
2613
      print $htm_fh "<table border=1>\n";
 
2614
      print $htm_fh "<tr><th>Count</th><th>From</th><th>To</th>\n";
 
2615
    }
 
2616
    if ($xls_fh) {
 
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);
 
2619
    }
 
2620
 
2044
2621
 
2045
2622
    my($key);
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;
 
2631
      if ($xls_fh)
 
2632
      {
 
2633
        &set_worksheet_line($ws_relayed, $row_print_relay++, 0, \@content);
 
2634
      }
2052
2635
      $spacing = "\n";
2053
2636
    }
2054
 
    print "</table>\n<p>\n" if $html;
2055
 
    print "${spacing}Total: $shown (plus $relayed_unshown unshown)\n";
 
2637
 
 
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;
 
2641
    if ($xls_fh)
 
2642
    {
 
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"]);
 
2645
       $row_print_relay++;
 
2646
    }
2056
2647
  }
2057
2648
  else {
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;
 
2651
    if ($xls_fh)
 
2652
    {
 
2653
      $row_print_relay++;
 
2654
    }
2060
2655
  }
2061
 
  print "\n";
2062
2656
}
2063
2657
 
2064
2658
 
2065
2659
 
2066
 
=head2 print_errors();
2067
 
 
2068
 
 print_errors();
2069
 
 
2070
 
Print our errors. In HTML, we display them as a list rather than a table -
2071
 
Netscape doesn't like large tables!
2072
 
 
2073
 
=cut
2074
 
 
 
2660
#######################################################################
 
2661
# print_errors();
 
2662
#
 
2663
#  print_errors();
 
2664
#
 
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;
 
2670
  $row=1;
2077
2671
 
2078
2672
  if (scalar(keys %errors_count) != 0) {
2079
2673
    my $temp = "List of errors";
2080
 
    my($format);
2081
 
    if ($html) {
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";
2085
 
    }
2086
 
    else {
2087
 
      printf("%s\n%s\n\n", $temp, "-" x length($temp));
2088
 
    }
 
2674
    my $htm_format = "<li>%d - %s\n";
 
2675
 
 
2676
    printf $txt_fh ("%s\n%s\n\n", $temp, "-" x length($temp)) if $txt_fh;
 
2677
    if ($htm_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";
 
2680
    }
 
2681
    if ($xls_fh)
 
2682
    {
 
2683
      $ws_errors->write($row++, 0, $temp, $f_header2);
 
2684
      &set_worksheet_line($ws_errors, $row++, 0, ["Count", "Error"], $f_headertab);
 
2685
    }
 
2686
 
2089
2687
 
2090
2688
    my($key);
2091
2689
    foreach $key (sort keys %errors_count) {
2092
2690
      my $text = $key;
2093
2691
      chomp($text);
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};
2096
 
      if ($html) {
 
2694
 
 
2695
      if ($txt_fh) {
 
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+(.+)/;
 
2700
          last if !$first;
 
2701
          printf $txt_fh ("%s\n\t    ", $first);
 
2702
          $text_remaining = $rest;
 
2703
        }
 
2704
        printf $txt_fh ("%s\n\n", $text_remaining);
 
2705
      }
 
2706
 
 
2707
      if ($htm_fh) {
2097
2708
 
2098
2709
        #Translate HTML tag characters. Sergey Sholokh.
2099
2710
        $text =~ s/\</\&lt\;/g;
2100
2711
        $text =~ s/\>/\&gt\;/g;
2101
2712
 
2102
 
        printf($format,$errors_count{$key},$text);
 
2713
        printf $htm_fh ($htm_format,$errors_count{$key},$text);
2103
2714
      }
2104
 
      else {
2105
 
        printf("%5d ", $errors_count{$key});
2106
 
        while (length($text) > 65) {
2107
 
          my($first,$rest) = $text =~ /(.{50}\S*)\s+(.+)/;
2108
 
          last if !$first;
2109
 
          printf("%s\n      ", $first);
2110
 
          $text = $rest;
2111
 
        }
2112
 
        printf("%s\n\n", $text);
 
2715
      if ($xls_fh)
 
2716
      {
 
2717
        &set_worksheet_line($ws_errors, $row++, 0, [$errors_count{$key},$text]);
2113
2718
      }
2114
2719
    }
2115
 
    print "</ul>\n<p>\n" if $html;
2116
2720
 
2117
2721
    $temp = "Errors encountered: $total_errors";
2118
 
    print $temp,"\n";
2119
 
    print "-" x length($temp),"\n" unless $html;
 
2722
 
 
2723
    if ($txt_fh) {
 
2724
      print $txt_fh $temp, "\n";
 
2725
      print $txt_fh "-" x length($temp),"\n";
 
2726
    }
 
2727
    if ($htm_fh) {
 
2728
      print $htm_fh "</ul>\n<p>\n";
 
2729
      print $htm_fh $temp, "\n";
 
2730
    }
 
2731
    if ($xls_fh)
 
2732
    {
 
2733
        &set_worksheet_line($ws_errors, $row++, 0, [$total_errors, "Sum of Errors encountered"]);
 
2734
    }
2120
2735
  }
2121
2736
 
2122
2737
}
2123
2738
 
2124
2739
 
2125
 
=head2 parse_old_eximstat_reports();
2126
 
 
2127
 
 parse_old_eximstat_reports($fh);
2128
 
 
2129
 
Parse old eximstat output so we can merge daily stats to weekly stats and weekly to monthly etc.
2130
 
 
2131
 
To test that the merging still works after changes, do something like the following.
2132
 
All the diffs should produce no output.
2133
 
 
2134
 
 options='-bydomain -byemail -byhost -byedomain'
2135
 
 options="$options -pattern 'Completed Messages' /Completed/"
2136
 
 options="$options -pattern 'Received Messages' /<=/"
2137
 
 
2138
 
 ./eximstats $options mainlog > mainlog.txt
2139
 
 ./eximstats $options -merge mainlog.txt > mainlog.2.txt
2140
 
 diff mainlog.txt mainlog.2.txt
2141
 
 
2142
 
 ./eximstats $options -html mainlog > mainlog.html
2143
 
 ./eximstats $options -merge -html mainlog.txt  > mainlog.2.html
2144
 
 diff mainlog.html mainlog.2.html
2145
 
 
2146
 
 ./eximstats $options -merge mainlog.html > mainlog.3.txt
2147
 
 diff mainlog.txt mainlog.3.txt
2148
 
 
2149
 
 ./eximstats $options -merge -html mainlog.html > mainlog.3.html
2150
 
 diff mainlog.html mainlog.3.html
2151
 
 
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
2155
 
 
2156
 
 # double_mainlog.txt should have twice the values that mainlog.txt has.
2157
 
 ./eximstats $options mainlog mainlog > double_mainlog.txt
2158
 
 
2159
 
=cut
2160
 
 
 
2740
#######################################################################
 
2741
# parse_old_eximstat_reports();
 
2742
#
 
2743
#  parse_old_eximstat_reports($fh);
 
2744
#
 
2745
# Parse old eximstat output so we can merge daily stats to weekly stats and weekly to monthly etc.
 
2746
#
 
2747
# To test that the merging still works after changes, do something like the following.
 
2748
# All the diffs should produce no output.
 
2749
#
 
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' /<=/"
 
2754
#
 
2755
#  ./eximstats $options mainlog > mainlog.txt
 
2756
#  ./eximstats $options -merge mainlog.txt > mainlog.2.txt
 
2757
#  diff mainlog.txt mainlog.2.txt
 
2758
#
 
2759
#  ./eximstats $options -html mainlog > mainlog.html
 
2760
#  ./eximstats $options -merge -html mainlog.txt  > mainlog.2.html
 
2761
#  diff mainlog.html mainlog.2.html
 
2762
#
 
2763
#  ./eximstats $options -merge mainlog.html > mainlog.3.txt
 
2764
#  diff mainlog.txt mainlog.3.txt
 
2765
#
 
2766
#  ./eximstats $options -merge -html mainlog.html > mainlog.3.html
 
2767
#  diff mainlog.html mainlog.3.html
 
2768
#
 
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
 
2772
#
 
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 {
2162
2777
  my($fh) = @_;
2163
2778
 
2164
2779
  my(%league_table_value_entered, %league_table_value_was_zero, %table_order);
2165
2780
 
 
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);
 
2785
 
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);
2172
2793
      # Fill in $report_totals{Received|Delivered}{Volume|Messages|Hosts|Domains|...|Delayed|DelayedPercent|Failed|FailedPercent}
2173
2794
      my(@fields);
2174
2795
      while (<$fh>) {
2175
 
        $_ = html2txt($_);              #Convert general HTML markup to text.
2176
 
        s/At least one addr//g;         #Another part of the HTML output we don't want.
 
2796
        $_ = html2txt($_);       #Convert general HTML markup to text.
 
2797
        s/At least one addr//g;  #Another part of the HTML output we don't want.
2177
2798
 
2178
2799
#  TOTAL               Volume    Messages    Hosts Domains      Delayed       Failed
2179
2800
#  Received              26MB         237      177      23       8  3.4%     28 11.8%
2180
2801
#  Delivered             13MB         233       99      88
2181
 
        if (/TOTAL\s+(.*?)\s*$/) {
2182
 
          @fields = split(/\s+/,$1);
 
2802
        if (/TOTAL\s+(.*?)\s*$/) {
 
2803
          @fields = split(/\s+/,$1);
2183
2804
          #Delayed and Failed have two columns each, so add the extra field names in.
2184
 
          splice(@fields,-1,1,'DelayedPercent','Failed','FailedPercent');
2185
 
        }
2186
 
        elsif (/(Received|Delivered)\s+(.*?)\s*$/) {
2187
 
          print STDERR "Parsing $_" if $debug;
2188
 
          add_to_totals($report_totals{$1},\@fields,$2);
2189
 
        }
2190
 
        last if (/Delivered/);          #Last line of this section.
 
2805
          splice(@fields,-1,1,'DelayedPercent','Failed','FailedPercent');
 
2806
        }
 
2807
        elsif (/(Received|Delivered)\s+(.*?)\s*$/) {
 
2808
          print STDERR "Parsing $_" if $debug;
 
2809
          add_to_totals($report_totals{$1},\@fields,$2);
 
2810
        }
 
2811
        last if (/Delivered/);   #Last line of this section.
2191
2812
      }
2192
2813
    }
2193
2814
 
2197
2818
#                       Total
2198
2819
#  Description             85
2199
2820
 
2200
 
      while (<$fh>) { last if (/Total/); }      #Wait until we get the table headers.
 
2821
      while (<$fh>) { last if (/Total/); }  #Wait until we get the table headers.
2201
2822
      while (<$fh>) {
2202
 
        print STDERR "Parsing $_" if $debug;
2203
 
        $_ = html2txt($_);              #Convert general HTML markup to text.
2204
 
        if (/^\s*(.*?)\s+(\d+)\s*$/) {
2205
 
          $report_totals{patterns}{$1} = {} unless (defined $report_totals{patterns}{$1});
2206
 
          add_to_totals($report_totals{patterns}{$1},['Total'],$2);
2207
 
        }
2208
 
        last if (/^\s*$/);                      #Finished if we have a blank line.
 
2823
        print STDERR "Parsing $_" if $debug;
 
2824
        $_ = html2txt($_);              #Convert general HTML markup to text.
 
2825
        if (/^\s*(.*?)\s+(\d+)\s*$/) {
 
2826
          $report_totals{patterns}{$1} = {} unless (defined $report_totals{patterns}{$1});
 
2827
          add_to_totals($report_totals{patterns}{$1},['Total'],$2);
 
2828
        }
 
2829
        last if (/^\s*$/);              #Finished if we have a blank line.
2209
2830
      }
2210
2831
    }
2211
2832
 
 
2833
    elsif (/(^|<h2>)($user_pattern_keys) per /o) {
 
2834
      # Parse User defined pattern histograms if they exist.
 
2835
      parse_histogram($fh, $user_pattern_interval_count[$user_pattern_index{$2}] );
 
2836
    }
 
2837
 
 
2838
 
2212
2839
    elsif (/Deliveries by transport/i) {
2213
2840
#Deliveries by transport
2214
2841
#-----------------------
2217
2844
#  address_pipe         655KB           1
2218
2845
#  smtp                  11MB         151
2219
2846
 
2220
 
      while (<$fh>) { last if (/Volume/); }     #Wait until we get the table headers.
2221
 
      while (<$fh>) {
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);
2227
 
        }
2228
 
        last if (/^\s*$/);                      #Finished if we have a blank line.
2229
 
      }
2230
 
    }
2231
 
    elsif (/(Messages received|Deliveries) per/) {
2232
 
#      Messages received per hour (each dot is 2 messages)
2233
 
#---------------------------------------------------
2234
 
#
2235
 
#00-01    106 .....................................................
2236
 
#01-02    103 ...................................................
2237
 
 
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;
2242
 
      while (<$fh>) {
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;
2248
 
        }
2249
 
        elsif (/^(\d+)-(\d+)\s+(\d+)/) {        #hh-hh start-end time format ?
2250
 
          $$interval_aref[($1*60)/$hist_interval] += $3;
2251
 
        }
2252
 
        else {                                  #Finished the table ?
2253
 
          last;
2254
 
        }
2255
 
      }
2256
 
    }
2257
 
 
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.
 
2848
      while (<$fh>) {
 
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);
 
2854
        }
 
2855
        last if (/^\s*$/);              #Finished if we have a blank line.
 
2856
      }
 
2857
    }
 
2858
    elsif (/Messages received per/) {
 
2859
      parse_histogram($fh, \@received_interval_count);
 
2860
    }
 
2861
    elsif (/Deliveries per/) {
 
2862
      parse_histogram($fh, \@delivered_interval_count);
 
2863
    }
 
2864
 
 
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
#-------------------------------------
2261
2869
#
2267
2875
 
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;
 
2885
        }
 
2886
        else {
 
2887
          $bin_aref = \@qt_remote_bin;
 
2888
          $overflow_sref = \$qt_remote_overflow;
 
2889
        }
 
2890
      }
 
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;
 
2896
        }
 
2897
        else {
 
2898
          $bin_aref = \@dt_remote_bin;
 
2899
          $overflow_sref = \$dt_remote_overflow;
 
2900
        }
 
2901
      }
 
2902
      else {
 
2903
        unless (exists $rcpt_times_bin{$3}) {
 
2904
          initialise_rcpt_times($3);
 
2905
        }
 
2906
        $bin_aref = $rcpt_times_bin{$3};
 
2907
        $times_aref = \@rcpt_times;
 
2908
        $overflow_sref = \$rcpt_times_overflow{$3};
 
2909
      }
 
2910
 
 
2911
 
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');
2284
 
          my($i);
2285
 
          for ($i = 0; $i <= $#queue_times; $i++) {
2286
 
            if ($time_on_queue < $queue_times[$i]) {
2287
 
              $$bin_aref[$i] += $count;
2288
 
              last;
2289
 
            }
2290
 
          }
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));
2294
 
        }
2295
 
        else {
2296
 
          last;                                 #Finished the table ?
2297
 
        }
 
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');
 
2925
          my($i);
 
2926
          for ($i = 0; $i <= $#$times_aref; $i++) {
 
2927
            if ($time_on_queue < $times_aref->[$i]) {
 
2928
              $$bin_aref[$i] += $count;
 
2929
              last;
 
2930
            }
 
2931
          }
 
2932
          $$overflow_sref += $count if ($i > $#$times_aref);
 
2933
 
 
2934
        }
 
2935
        else {
 
2936
          last;                             #Finished the table ?
 
2937
        }
2298
2938
      }
2299
2939
    }
2300
2940
 
2310
2950
      my $reached_table = 0;
2311
2951
      my($count,$sender);
2312
2952
      while (<$fh>) {
2313
 
        unless ($reached_table) {
2314
 
          last if (/No relayed messages/);
2315
 
          $reached_table = 1 if (/^\s*\d/ || />\d+</);
2316
 
          next unless $reached_table;
2317
 
        }
2318
 
        if (/>(\d+)<.td><td>(.*?) ?<.td><td>(.*?)</) {
2319
 
          update_relayed($1,$2,$3);
2320
 
        }
2321
 
        elsif (/^\s*(\d+)\s+(.*?)\s*$/) {
2322
 
          ($count,$sender) = ($1,$2);
2323
 
        }
2324
 
        elsif (/=>\s+(.*?)\s*$/) {
2325
 
          update_relayed($count,$sender,$1);
2326
 
        }
2327
 
        else {
2328
 
          last;                                 #Finished the table ?
2329
 
        }
 
2953
        unless ($reached_table) {
 
2954
          last if (/No relayed messages/);
 
2955
          $reached_table = 1 if (/^\s*\d/ || />\d+</);
 
2956
          next unless $reached_table;
 
2957
        }
 
2958
        if (/>(\d+)<.td><td>(.*?) ?<.td><td>(.*?)</) {
 
2959
          update_relayed($1,$2,$3);
 
2960
        }
 
2961
        elsif (/^\s*(\d+)\s+(.*?)\s*$/) {
 
2962
          ($count,$sender) = ($1,$2);
 
2963
        }
 
2964
        elsif (/=>\s+(.*?)\s*$/) {
 
2965
          update_relayed($count,$sender,$1);
 
2966
        }
 
2967
        else {
 
2968
          last;                           #Finished the table ?
 
2969
        }
2330
2970
      }
2331
2971
    }
2332
2972
 
2335
2975
#-------------------------------------
2336
2976
#
2337
2977
#     48     1468KB   local
 
2978
# Could also have average values for HTML output.
 
2979
#     48     1468KB   30KB  local
 
2980
 
2338
2981
      my($category,$by_count_or_volume) = ($1,$2);
2339
2982
 
2340
2983
      #As we show 2 views of each table (by count and by volume),
2342
2985
      #Set up a hash to record which entries we have already seen
2343
2986
      #and one to record which ones we are seeing for the first time.
2344
2987
      if ($by_count_or_volume =~ /count/) {
2345
 
        undef %league_table_value_entered;
2346
 
        undef %league_table_value_was_zero;
2347
 
        undef %table_order;
 
2988
        undef %league_table_value_entered;
 
2989
        undef %league_table_value_was_zero;
 
2990
        undef %table_order;
2348
2991
      }
2349
2992
 
2350
2993
      #As this section processes multiple different table categories,
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;
2357
3000
      }
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"}};
2364
3007
      }
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;
2369
3012
      }
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"}};
2376
3019
      }
2377
3020
 
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]/);
 
3025
 
 
3026
        $_ = html2txt($_);              #Convert general HTML markup to text.
 
3027
 
 
3028
 
 
3029
        $reached_table = 1 if (/^\s*\d/);
 
3030
        next unless $reached_table;
 
3031
 
 
3032
        # Remove optional 'average value' column.
 
3033
        s/^\s*(\d+)\s+(\S+)\s+(\d+(KB|MB|GB|\b)\s+)/$1 $2 /;
 
3034
 
 
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.
2386
3038
 
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/);
2389
3041
 
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;
2397
 
            }
 
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;
 
3049
            }
2398
3050
 
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;
2403
 
          }
2404
 
        }
2405
 
        else {          #Finished the table ?
2406
 
          if ($by_count_or_volume =~ /volume/) {
2407
 
            #Add a few bytes to appropriate entries to preserve the order.
2408
 
 
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'};
2416
 
              my(@order,%mark);
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'}});
2420
 
 
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;
2428
 
                }
2429
 
                $bonus_bytes--;
2430
 
              }
2431
 
            }
2432
 
          }
2433
 
 
2434
 
          last;
2435
 
        }
 
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;
 
3055
          }
 
3056
        }
 
3057
        else {         #Finished the table ?
 
3058
          if ($by_count_or_volume =~ /volume/) {
 
3059
            #Add a few bytes to appropriate entries to preserve the order.
 
3060
 
 
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'};
 
3068
              my(@order,%mark);
 
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'}});
 
3072
 
 
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;
 
3080
                }
 
3081
                $bonus_bytes--;
 
3082
              }
 
3083
            }
 
3084
          }
 
3085
 
 
3086
          last;
 
3087
        }
2436
3088
      }
2437
3089
    }
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;
2454
 
 
2455
 
        s/^<li>(\d+) -/$1/;     #Convert an HTML line to a text line.
2456
 
        $_ = html2txt($_);      #Convert general HTML markup to text.
2457
 
 
2458
 
        if (/\t\s*(.*)/) {
2459
 
          $error .= ' ' . $1;   #Join a multiline error.
2460
 
        }
2461
 
        elsif (/^\s*(\d+)\s+(.*)/) {
2462
 
          if ($error) {
 
3104
        $reached_table = 1 if (/^( *|<li>)(\d+)/);
 
3105
        next unless $reached_table;
 
3106
 
 
3107
        s/^<li>(\d+) -/$1/;     #Convert an HTML line to a text line.
 
3108
        $_ = html2txt($_);      #Convert general HTML markup to text.
 
3109
 
 
3110
        if (/\t\s*(.*)/) {
 
3111
          $error .= ' ' . $1;   #Join a multiline error.
 
3112
        }
 
3113
        elsif (/^\s*(\d+)\s+(.*)/) {
 
3114
          if ($error) {
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;
2466
 
          }
2467
 
          ($count,$error) = ($1,$2);
2468
 
        }
2469
 
        elsif (/Errors encountered/) {
2470
 
          if ($error) {
 
3116
            $errors_count{$error} = 0 unless $errors_count{$error};
 
3117
            $errors_count{$error} += $count;
 
3118
          }
 
3119
          ($count,$error) = ($1,$2);
 
3120
        }
 
3121
        elsif (/Errors encountered/) {
 
3122
          if ($error) {
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;
2474
 
          }
2475
 
          last;
2476
 
        }
 
3124
            $errors_count{$error} = 0 unless $errors_count{$error};
 
3125
            $errors_count{$error} += $count;
 
3126
          }
 
3127
          last;
 
3128
        }
2477
3129
      }
2478
3130
    }
2479
3131
 
2480
3132
  }
2481
3133
}
2482
3134
 
2483
 
 
2484
 
 
2485
 
=head2 update_relayed();
2486
 
 
2487
 
 update_relayed($count,$sender,$recipient);
2488
 
 
2489
 
Adds an entry into the %relayed hash. Currently only used when
2490
 
merging reports.
2491
 
 
2492
 
=cut
2493
 
 
 
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) = @_;
 
3141
 
 
3142
  #      Messages received per hour (each dot is 2 messages)
 
3143
  #---------------------------------------------------
 
3144
  #
 
3145
  #00-01    106 .....................................................
 
3146
  #01-02    103 ...................................................
 
3147
 
 
3148
  my $reached_table = 0;
 
3149
  while (<$fh>) {
 
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;
 
3155
    }
 
3156
    elsif (/^(\d+)-(\d+)\s+(\d+)/) {        #hh-hh start-end time format ?
 
3157
      $$counters_aref[($1*60)/$hist_interval] += $3 if $hist_opt;
 
3158
    }
 
3159
    else {                                  #Finished the table ?
 
3160
      last;
 
3161
    }
 
3162
  }
 
3163
}
 
3164
 
 
3165
 
 
3166
#######################################################################
 
3167
# update_relayed();
 
3168
#
 
3169
#  update_relayed($count,$sender,$recipient);
 
3170
#
 
3171
# Adds an entry into the %relayed hash. Currently only used when
 
3172
# merging reports.
 
3173
#######################################################################
2494
3174
sub update_relayed {
2495
3175
  my($count,$sender,$recipient) = @_;
2496
3176
 
2508
3188
}
2509
3189
 
2510
3190
 
2511
 
=head2 add_to_totals();
2512
 
 
2513
 
 add_to_totals(\%totals,\@keys,$values);
2514
 
 
2515
 
Given a line of space seperated values, add them into the provided hash using @keys
2516
 
as the hash keys.
2517
 
 
2518
 
If the value contains a '%', then the value is set rather than added. Otherwise, we
2519
 
convert the value to bytes and gigs. The gigs get added to I<Key>-gigs.
2520
 
 
2521
 
=cut
2522
 
 
 
3191
#######################################################################
 
3192
# add_to_totals();
 
3193
#
 
3194
#  add_to_totals(\%totals,\@keys,$values);
 
3195
#
 
3196
# Given a line of space seperated values, add them into the provided hash using @keys
 
3197
# as the hash keys.
 
3198
#
 
3199
# If the value contains a '%', then the value is set rather than added. Otherwise, we
 
3200
# convert the value to bytes and gigs. The gigs get added to I<Key>-gigs.
 
3201
#######################################################################
2523
3202
sub add_to_totals {
2524
3203
  my($totals_href,$keys_aref,$values) = @_;
2525
3204
  my(@values) = split(/\s+/,$values);
2526
 
  my(@keys) = @$keys_aref;              #Make a copy as we destroy the one we use.
 
3205
  my(@keys) = @$keys_aref;        #Make a copy as we destroy the one we use.
2527
3206
  my($value);
2528
3207
  foreach $value (@values) {
2529
3208
    my $key = shift(@keys) or next;
2539
3218
  }
2540
3219
}
2541
3220
 
2542
 
=head2 get_report_total();
2543
 
 
2544
 
 $total = get_report_total(\%hash,$key);
2545
 
 
2546
 
If %hash contains values split into Units and Gigs, we calculate and return
2547
 
 
2548
 
  $hash{$key} + 1024*1024*1024 * $hash{"${key}-gigs"}
2549
 
 
2550
 
=cut
2551
 
 
 
3221
#######################################################################
 
3222
# get_report_total();
 
3223
#
 
3224
#  $total = get_report_total(\%hash,$key);
 
3225
#
 
3226
# If %hash contains values split into Units and Gigs, we calculate and return
 
3227
#
 
3228
#   $hash{$key} + 1024*1024*1024 * $hash{"${key}-gigs"}
 
3229
#######################################################################
2552
3230
sub get_report_total {
2553
3231
  no integer;
2554
3232
  my($hash_ref,$key) = @_;
2558
3236
  return $$hash_ref{$key} || 0;
2559
3237
}
2560
3238
 
2561
 
=head2 html2txt();
2562
 
 
2563
 
 $text_line = html2txt($html_line);
2564
 
 
2565
 
Convert a line from html to text. Currently we just convert HTML tags to spaces
2566
 
and convert &gt;, &lt;, and &nbsp; tags back.
2567
 
 
2568
 
=cut
2569
 
 
 
3239
#######################################################################
 
3240
# html2txt();
 
3241
#
 
3242
#  $text_line = html2txt($html_line);
 
3243
#
 
3244
# Convert a line from html to text. Currently we just convert HTML tags to spaces
 
3245
# and convert &gt;, &lt;, and &nbsp; tags back.
 
3246
#######################################################################
2570
3247
sub html2txt {
2571
3248
  ($_) = @_;
2572
3249
 
2574
3251
  # <Userid@Domain> words, so explicitly specify the HTML tags we will remove
2575
3252
  # (the ones used by this program). If someone is careless enough to have their
2576
3253
  # Userid the same as an HTML tag, there's not much we can do about it.
2577
 
  s/<\/?(html|head|title|body|h\d|ul|li|a\s+|table|tr|td|th|pre|hr|p|br)\b.*?>/ /og;
 
3254
  s/<\/?(html|head|title|body|h\d|ul|li|a\s+|table|tr|td|th|pre|hr|p|br)\b.*?>/ /g;
2578
3255
 
2579
3256
  s/\&lt\;/\</og;             #Convert '&lt;' to '<'.
2580
3257
  s/\&gt\;/\>/og;             #Convert '&gt;' to '>'.
2582
3259
  return($_);
2583
3260
}
2584
3261
 
2585
 
=head2 get_next_arg();
2586
 
 
2587
 
 $arg = get_next_arg();
2588
 
 
2589
 
Because eximstats arguments are often passed as variables,
2590
 
we can't rely on shell parsing to deal with quotes. This
2591
 
subroutine returns $ARGV[1] and does a shift. If $ARGV[1]
2592
 
starts with a quote (' or "), and doesn't end in one, then
2593
 
we append the next argument to it and shift again. We repeat
2594
 
until we've got all of the argument.
2595
 
 
2596
 
This isn't perfect as all white space gets reduced to one space,
2597
 
but it's as good as we can get! If it's esential that spacing
2598
 
be preserved precisely, then you get that by not using shell
2599
 
variables.
2600
 
 
2601
 
=cut
2602
 
 
 
3262
#######################################################################
 
3263
# get_next_arg();
 
3264
#
 
3265
#  $arg = get_next_arg();
 
3266
#
 
3267
# Because eximstats arguments are often passed as variables,
 
3268
# we can't rely on shell parsing to deal with quotes. This
 
3269
# subroutine returns $ARGV[1] and does a shift. If $ARGV[1]
 
3270
# starts with a quote (' or "), and doesn't end in one, then
 
3271
# we append the next argument to it and shift again. We repeat
 
3272
# until we've got all of the argument.
 
3273
#
 
3274
# This isn't perfect as all white space gets reduced to one space,
 
3275
# but it's as good as we can get! If it's esential that spacing
 
3276
# be preserved precisely, then you get that by not using shell
 
3277
# variables.
 
3278
#######################################################################
2603
3279
sub get_next_arg {
2604
3280
  my $arg = '';
2605
3281
  my $matched_pattern = 0;
2619
3295
  return $arg;
2620
3296
}
2621
3297
 
 
3298
#######################################################################
 
3299
# set_worksheet_line($ws_global, $startrow, $startcol, \@content, $format);
 
3300
#
 
3301
# set values to a sequence of cells in a row.
 
3302
#
 
3303
#######################################################################
 
3304
sub set_worksheet_line {
 
3305
  my ($worksheet, $row, $col, $content, $format) = @_;
 
3306
 
 
3307
  foreach my $token (@$content)
 
3308
  {
 
3309
     $worksheet->write($row, $col++, $token, $format );
 
3310
  }
 
3311
 
 
3312
}
 
3313
 
 
3314
#######################################################################
 
3315
# @rcpt_times = parse_time_list($string);
 
3316
#
 
3317
# Parse a comma seperated list of time values in seconds given by
 
3318
# the user and fill an array.
 
3319
#
 
3320
# Return a default list if $string is undefined.
 
3321
# Return () if $string eq '0'.
 
3322
#######################################################################
 
3323
sub parse_time_list {
 
3324
  my($string) = @_;
 
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);
 
3327
  }
 
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);
 
3332
  return(@times);
 
3333
}
 
3334
 
 
3335
 
 
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 {
 
3341
  my($protocol) = @_;
 
3342
  for (my $i = 0; $i <= $#rcpt_times; ++$i) {
 
3343
    $rcpt_times_bin{$protocol}[$i] = 0;
 
3344
  }
 
3345
  $rcpt_times_overflow{$protocol} = 0;
 
3346
}
2622
3347
 
2623
3348
 
2624
3349
##################################################
2634
3359
$topcount = 50;
2635
3360
$local_league_table = 1;
2636
3361
$include_remote_users = 0;
 
3362
$include_original_destination = 0;
2637
3363
$hist_opt = 1;
2638
3364
$volume_rounding = 1;
2639
3365
$localtime_offset = calculate_localtime_offset();    # PH/FANF
2643
3369
$chartrel = ".";
2644
3370
$chartdir = ".";
2645
3371
 
2646
 
@queue_times = (60, 5*60, 15*60, 30*60, 60*60, 3*60*60, 6*60*60,
2647
 
                12*60*60, 24*60*60);
 
3372
@queue_times = parse_time_list();
 
3373
@rcpt_times = ();
 
3374
@delivery_times = ();
2648
3375
 
2649
3376
$last_offset = '';
2650
3377
$offset_seconds = 0;
2651
3378
 
 
3379
$row=1;
 
3380
$row_league_table=1;
 
3381
$col=0;
 
3382
$col_hist=0;
 
3383
$run_hist=0;
 
3384
my(%output_files);     # What output files have been specified?
 
3385
 
2652
3386
# Decode options
2653
3387
 
2654
 
while (@ARGV > 0 && substr($ARGV[0], 0, 1) eq '-')
2655
 
  {
 
3388
while (@ARGV > 0 && substr($ARGV[0], 0, 1) eq '-') {
2656
3389
  if    ($ARGV[0] =~ /^\-h(\d+)$/) { $hist_opt = $1 }
2657
3390
  elsif ($ARGV[0] =~ /^\-ne$/)     { $show_errors = 0 }
2658
 
  elsif ($ARGV[0] =~ /^\-nr(.?)(.*)\1$/)
2659
 
    {
 
3391
  elsif ($ARGV[0] =~ /^\-nr(.?)(.*)\1$/) {
2660
3392
    if ($1 eq "") { $show_relay = 0 } else { $relay_pattern = $2 }
2661
 
    }
2662
 
  elsif ($ARGV[0] =~ /^\-q([,\d\+\-\*\/]+)$/)
2663
 
    {
2664
 
    @queue_times = split(/,/, $1);
2665
 
    my($q);
2666
 
    foreach $q (@queue_times) { $q = eval($q) + 0 }
2667
 
    @queue_times = sort { $a <=> $b } @queue_times;
2668
 
    @queue_times = () if ($#queue_times == 0 && $queue_times[0] == 0);
2669
 
    }
 
3393
  }
 
3394
  elsif ($ARGV[0] =~ /^\-q([,\d\+\-\*\/]+)$/) { @queue_times = parse_time_list($1) }
2670
3395
  elsif ($ARGV[0] =~ /^-nt$/)       { $show_transport = 0 }
2671
3396
  elsif ($ARGV[0] =~ /^\-nt(.?)(.*)\1$/)
2672
3397
    {
2674
3399
    }
2675
3400
  elsif ($ARGV[0] =~ /^-t(\d+)$/)   { $topcount = $1 }
2676
3401
  elsif ($ARGV[0] =~ /^-tnl$/)      { $local_league_table = 0 }
2677
 
  elsif ($ARGV[0] =~ /^-html$/)     { $html = 1 }
 
3402
  elsif ($ARGV[0] =~ /^-txt=?(\S*)$/)  { $txt_fh = get_filehandle($1,\%output_files) }
 
3403
  elsif ($ARGV[0] =~ /^-html=?(\S*)$/) { $htm_fh = get_filehandle($1,\%output_files) }
 
3404
  elsif ($ARGV[0] =~ /^-xls=?(\S*)$/) {
 
3405
    if ($HAVE_Spreadsheet_WriteExcel) {
 
3406
      $xls_fh = get_filehandle($1,\%output_files);
 
3407
    }
 
3408
    else {
 
3409
      warn "WARNING: CPAN Module Spreadsheet::WriteExcel not installed. Obtain from www.cpan.org\n";
 
3410
    }
 
3411
  }
2678
3412
  elsif ($ARGV[0] =~ /^-merge$/)    { $merge_reports = 1 }
2679
3413
  elsif ($ARGV[0] =~ /^-charts$/)   {
2680
3414
    $charts = 1;
2683
3417
  }
2684
3418
  elsif ($ARGV[0] =~ /^-chartdir$/) { $chartdir = $ARGV[1]; shift; $charts_option_specified = 1; }
2685
3419
  elsif ($ARGV[0] =~ /^-chartrel$/) { $chartrel = $ARGV[1]; shift; $charts_option_specified = 1; }
 
3420
  elsif ($ARGV[0] =~ /^-include_original_destination$/)    { $include_original_destination = 1 }
2686
3421
  elsif ($ARGV[0] =~ /^-cache$/)    { } #Not currently used.
2687
3422
  elsif ($ARGV[0] =~ /^-byhost$/)   { $do_sender{Host} = 1 }
2688
3423
  elsif ($ARGV[0] =~ /^-bydomain$/) { $do_sender{Domain} = 1 }
2690
3425
  elsif ($ARGV[0] =~ /^-byemaildomain$/)  { $do_sender{Edomain} = 1 }
2691
3426
  elsif ($ARGV[0] =~ /^-byedomain$/)  { $do_sender{Edomain} = 1 }
2692
3427
  elsif ($ARGV[0] =~ /^-nvr$/)      { $volume_rounding = 0 }
 
3428
  elsif ($ARGV[0] =~ /^-show_rt([,\d\+\-\*\/]+)?$/) { @rcpt_times = parse_time_list($1) }
 
3429
  elsif ($ARGV[0] =~ /^-show_dt([,\d\+\-\*\/]+)?$/) { @delivery_times = parse_time_list($1) }
2693
3430
  elsif ($ARGV[0] =~ /^-d$/)        { $debug = 1 }
2694
3431
  elsif ($ARGV[0] =~ /^--?h(elp)?$/){ help() }
2695
3432
  elsif ($ARGV[0] =~ /^-t_remote_users$/) { $include_remote_users = 1 }
2711
3448
  shift;
2712
3449
  }
2713
3450
 
 
3451
  # keep old default behaviour
 
3452
  if (! ($xls_fh or $htm_fh or $txt_fh)) {
 
3453
    $txt_fh = \*STDOUT;
 
3454
  }
 
3455
 
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);
2716
3458
 
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});
2719
3461
 
2720
 
 
 
3462
  # prepare xls Excel Workbook
 
3463
  if (defined $xls_fh)
 
3464
  {
 
3465
 
 
3466
    # Create a new Excel workbook
 
3467
    $workbook  = Spreadsheet::WriteExcel->new($xls_fh);
 
3468
 
 
3469
    # Add worksheets
 
3470
    $ws_global = $workbook->addworksheet('Exim Statistik');
 
3471
    # show $ws_global as initial sheet
 
3472
    $ws_global->set_first_sheet();
 
3473
    $ws_global->activate();
 
3474
 
 
3475
    if ($show_relay) {
 
3476
      $ws_relayed = $workbook->addworksheet('Relayed Messages');
 
3477
      $ws_relayed->set_column(1, 2,  80);
 
3478
    }
 
3479
    if ($topcount) {
 
3480
    $ws_top50 = $workbook->addworksheet('Deliveries');
 
3481
    }
 
3482
    if ($show_errors) {
 
3483
      $ws_errors = $workbook->addworksheet('Errors');
 
3484
    }
 
3485
 
 
3486
 
 
3487
    # set column widths
 
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
 
3491
 
 
3492
    # Define Formats
 
3493
    $f_default = $workbook->add_format();
 
3494
 
 
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);
 
3502
 
 
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);
 
3508
 
 
3509
    $f_percent = $workbook->add_format();
 
3510
    $f_percent->set_num_format('0.0%');
 
3511
 
 
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);
 
3516
 
 
3517
  }
 
3518
 
 
3519
 
 
3520
# Initialise the queue/delivery/rcpt time counters.
2721
3521
for (my $i = 0; $i <= $#queue_times; $i++) {
2722
 
  $queue_bin[$i] = 0;
2723
 
  $remote_queue_bin[$i] = 0;
2724
 
}
 
3522
  $qt_all_bin[$i] = 0;
 
3523
  $qt_remote_bin[$i] = 0;
 
3524
}
 
3525
for (my $i = 0; $i <= $#delivery_times; $i++) {
 
3526
  $dt_all_bin[$i] = 0;
 
3527
  $dt_remote_bin[$i] = 0;
 
3528
}
 
3529
initialise_rcpt_times('all');
 
3530
 
2725
3531
 
2726
3532
# Compute the number of slots for the histogram
2727
 
 
2728
3533
if ($hist_opt > 0)
2729
3534
  {
2730
3535
  if ($hist_opt > 60 || 60 % $hist_opt != 0)
2731
3536
    {
2732
 
    print "Eximstats: -h must specify a factor of 60\n";
 
3537
    print STDERR "Eximstats: -h must specify a factor of 60\n";
2733
3538
    exit 1;
2734
3539
    }
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;
2739
3547
  }
 
3548
  @dt_all_bin = (0) x $hist_number;
 
3549
  @dt_remote_bin = (0) x $hist_number;
 
3550
}
2740
3551
 
2741
3552
#$queue_unknown = 0;
2742
3553
 
2748
3559
$total_delivered_data_gigs = 0;
2749
3560
$total_delivered_count = 0;
2750
3561
 
2751
 
$queue_more_than = 0;
 
3562
$qt_all_overflow = 0;
 
3563
$qt_remote_overflow = 0;
 
3564
$dt_all_overflow = 0;
 
3565
$dt_remote_overflow = 0;
2752
3566
$delayed_count = 0;
2753
3567
$relayed_unshown = 0;
 
3568
$message_errors = 0;
2754
3569
$begin = "9999-99-99 99:99:99";
2755
3570
$end = "0000-00-00 00:00:00";
2756
3571
my($section,$type);
2770
3585
  foreach my $file (@ARGV) {
2771
3586
    if ($file =~ /\.gz/) {
2772
3587
      unless (open(FILE,"gunzip -c $file |")) {
2773
 
        print STDERR "Failed to gunzip -c $file: $!";
2774
 
        next;
 
3588
        print STDERR "Failed to gunzip -c $file: $!";
 
3589
        next;
2775
3590
      }
2776
3591
    }
2777
3592
    elsif ($file =~ /\.Z/) {
2778
3593
      unless (open(FILE,"uncompress -c $file |")) {
2779
 
        print STDERR "Failed to uncompress -c $file: $!";
2780
 
        next;
 
3594
        print STDERR "Failed to uncompress -c $file: $!";
 
3595
        next;
2781
3596
      }
2782
3597
    }
2783
3598
    else {
2784
3599
      unless (open(FILE,$file)) {
2785
 
        print STDERR "Failed to read $file: $!";
2786
 
        next;
 
3600
        print STDERR "Failed to read $file: $!";
 
3601
        next;
2787
3602
      }
2788
3603
    }
2789
3604
    #Now parse the filehandle, updating the global variables.
2798
3613
 
2799
3614
 
2800
3615
if ($begin eq "9999-99-99 99:99:99") {
2801
 
  print "**** No valid log lines read\n";
 
3616
  print STDERR "**** No valid log lines read\n";
2802
3617
  exit 1;
2803
3618
}
2804
3619
 
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);
2820
3635
}
2821
3636
 
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);
 
3641
}
 
3642
 
 
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);
 
3647
}
 
3648
 
 
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});
 
3653
  }
2826
3654
}
2827
3655
 
2828
3656
# Print relay information if required.
2848
3676
# Print the error statistics if required.
2849
3677
print_errors() if $show_errors;
2850
3678
 
2851
 
if ($html) {
2852
 
  print "</body>\n</html>\n"
 
3679
print $htm_fh "</body>\n</html>\n" if $htm_fh;
 
3680
 
 
3681
 
 
3682
$txt_fh->close if $txt_fh;
 
3683
$htm_fh->close if $htm_fh;
 
3684
 
 
3685
if ($xls_fh) {
 
3686
  # close Excel Workbook
 
3687
  $ws_global->set_first_sheet();
 
3688
  # FIXME: whyever - activate does not work :-/
 
3689
  $ws_global->activate();
 
3690
  $workbook->close();
2853
3691
}
2854
3692
 
 
3693
 
2855
3694
# End of eximstats
 
3695
 
 
3696
 
 
3697
# FIXME: Doku