[exim-cvs] cvs commit: exim/exim-src/src eximstats.src

Startseite
Nachricht löschen
Nachricht beantworten
Autor: Philip Hazel
Datum:  
To: exim-cvs
Betreff: [exim-cvs] cvs commit: exim/exim-src/src eximstats.src
ph10 2004/11/24 14:43:57 GMT

  Modified files:
    exim-src/src         eximstats.src 
  Log:
  Installed eximstats 1.33


  Revision  Changes    Path
  1.2       +338 -364  exim/exim-src/src/eximstats.src


  Index: eximstats.src
  ===================================================================
  RCS file: /home/cvs/exim/exim-src/src/eximstats.src,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- eximstats.src    7 Oct 2004 10:39:01 -0000    1.1
  +++ eximstats.src    24 Nov 2004 14:43:57 -0000    1.2
  @@ -1,5 +1,5 @@
   #!PERL_COMMAND -w
  -# $Cambridge: exim/exim-src/src/eximstats.src,v 1.1 2004/10/07 10:39:01 ph10 Exp $
  +# $Cambridge: exim/exim-src/src/eximstats.src,v 1.2 2004/11/24 14:43:57 ph10 Exp $


   # Copyright (c) 2001 University of Cambridge.
   # See the file NOTICE for conditions of use and distribution.
  @@ -178,9 +178,15 @@
   #             Added warnings if required GD::Graph modules are not available or
   #             insufficient -chart* options are specified.
   #
  -# 2004-02-20  V1.31 Andrea Balzi
  +# 2004-02-20  V1.31 Andrea Balzi 
   #             Only show the Local Sender/Destination links if the tables exist.
   #
  +# 2004-07-05  V1.32 Steve Campbell
  +#             Fix '-merge -h0' divide by zero error.
  +#
  +# 2004-07-15  V1.33 Steve Campbell
  +#             Documentation update - I've converted the subroutine
  +#             documentation from POD to comments.



=head1 NAME
@@ -190,6 +196,7 @@
=head1 SYNOPSIS

    eximstats [Options] mainlog1 mainlog2 ... > report.txt
  + eximstats -html [Options] mainlog1 mainlog2 ... > report.html
    eximstats -merge [Options] report.1.txt report.2.txt ... > weekly_report.txt


Options:
@@ -371,11 +378,6 @@
when you have multiple mail servers and a message cannot be
immeadiately delivered. Fixing this could be tricky...

-=head1 SUBROUTINES
-
-The following section will only be of interest to the
-program maintainers:
-
=cut

use integer;
@@ -411,7 +413,7 @@

   @days_per_month = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334);
   $gig     = 1024 * 1024 * 1024;
  -$VERSION = '1.31';
  +$VERSION = '1.33';


# How much space do we allow for the Hosts/Domains/Emails/Edomains column headers?
$COLUMN_WIDTHS = 8;
@@ -465,21 +467,20 @@
##################################################


  -=head2 volume_rounded();
  -
  - $rounded_volume = volume_rounded($bytes,$gigabytes);
  -
  -Given a data size in bytes, round it to KB, MB, or GB
  -as appropriate.
  -
  -Eg 12000 => 12KB, 15000000 => 14GB, etc.
  -
  -Note: I've experimented with Math::BigInt and it results in a 33%
  -performance degredation as opposed to storing numbers split into
  -bytes and gigabytes.
  -
  -=cut
  -
  +#######################################################################
  +# volume_rounded();
  +#
  +# $rounded_volume = volume_rounded($bytes,$gigabytes);
  +#
  +# Given a data size in bytes, round it to KB, MB, or GB
  +# as appropriate.
  +#
  +# Eg 12000 => 12KB, 15000000 => 14GB, etc.
  +#
  +# Note: I've experimented with Math::BigInt and it results in a 33%
  +# performance degredation as opposed to storing numbers split into
  +# bytes and gigabytes.
  +#######################################################################
   sub volume_rounded {
     my($x,$g) = @_;
     $x = 0 unless $x;
  @@ -522,21 +523,20 @@
   }



  -=head2 un_round();
  -
  - un_round($rounded_volume,\$bytes,\$gigabytes);
  -
  -Given a volume in KB, MB or GB, as generated by volume_rounded(),
  -do the reverse transformation and convert it back into Bytes and Gigabytes.
  -These are added to the $bytes and $gigabytes parameters.
  -
  -Given a data size in bytes, round it to KB, MB, or GB
  -as appropriate.
  -
  -EG: 500 => (500,0), 14GB => (0,14), etc.
  -
  -=cut
  -
  +#######################################################################
  +# un_round();
  +# 
  +#  un_round($rounded_volume,\$bytes,\$gigabytes);
  +# 
  +# Given a volume in KB, MB or GB, as generated by volume_rounded(),
  +# do the reverse transformation and convert it back into Bytes and Gigabytes.
  +# These are added to the $bytes and $gigabytes parameters.
  +# 
  +# Given a data size in bytes, round it to KB, MB, or GB
  +# as appropriate.
  +# 
  +# EG: 500 => (500,0), 14GB => (0,14), etc.
  +#######################################################################
   sub un_round {
     my($rounded,$bytes_sref,$gigabytes_sref) = @_;


@@ -561,40 +561,37 @@
}


  -=head2 add_volume();
  -
  -  add_volume(\$bytes,\$gigs,$size);
  -
  -Add $size to $bytes/$gigs where this is a number split into
  -bytes ($bytes) and gigabytes ($gigs). This is significantly
  -faster than using Math::BigInt.
  -
  -=cut
  -
  +#######################################################################
  +# add_volume();
  +# 
  +#   add_volume(\$bytes,\$gigs,$size);
  +# 
  +# Add $size to $bytes/$gigs where this is a number split into
  +# bytes ($bytes) and gigabytes ($gigs). This is significantly
  +# faster than using Math::BigInt.
  +#######################################################################
   sub add_volume {
  -my($bytes_ref,$gigs_ref,$size) = @_;
  -$$bytes_ref = 0 if ! defined $$bytes_ref;
  -$$gigs_ref = 0 if ! defined $$gigs_ref;
  -$$bytes_ref += $size;
  -while ($$bytes_ref > $gig)
  -  {
  -  $$gigs_ref++;
  -  $$bytes_ref -= $gig;
  +  my($bytes_ref,$gigs_ref,$size) = @_;
  +  $$bytes_ref = 0 if ! defined $$bytes_ref;
  +  $$gigs_ref = 0 if ! defined $$gigs_ref;
  +  $$bytes_ref += $size;
  +  while ($$bytes_ref > $gig) {
  +    $$gigs_ref++;
  +    $$bytes_ref -= $gig;
     }
   }



-=head2 format_time();
-
- $formatted_time = format_time($seconds);
-
-Given a time in seconds, break it down into
-weeks, days, hours, minutes, and seconds.
-
-Eg 12005 => 3h20m5s
-
-=cut
-
+#######################################################################
+# format_time();
+#
+# $formatted_time = format_time($seconds);
+#
+# Given a time in seconds, break it down into
+# weeks, days, hours, minutes, and seconds.
+#
+# Eg 12005 => 3h20m5s
+#######################################################################
sub format_time {
my($t) = pop @_;
my($s) = $t % 60;
@@ -615,16 +612,15 @@
}


  -=head2 unformat_time();
  -
  - $seconds = unformat_time($formatted_time);
  -
  -Given a time in weeks, days, hours, minutes, or seconds, convert it to seconds.
  -
  -Eg 3h20m5s => 12005
  -
  -=cut
  -
  +#######################################################################
  +#  unformat_time();
  +# 
  +#  $seconds = unformat_time($formatted_time);
  +# 
  +# Given a time in weeks, days, hours, minutes, or seconds, convert it to seconds.
  +# 
  +# Eg 3h20m5s => 12005
  +#######################################################################
   sub unformat_time {
     my($formated_time) = pop @_;
     my $time = 0;
  @@ -640,33 +636,32 @@
   }



  -=head2 seconds();
  -
  - $time = seconds($timestamp);
  -
  -Given a time-of-day timestamp, convert it into a time() value using
  -POSIX::mktime.  We expect the timestamp to be of the form
  -"$year-$mon-$day $hour:$min:$sec", with month going from 1 to 12,
  -and the year to be absolute (we do the necessary conversions). The
  -timestamp may be followed with an offset from UTC like "+$hh$mm"; if the
  -offset is not present, and we have not been told that the log is in UTC
  -(with the -utc option), then we adjust the time by the current local
  -time offset so that it can be compared with the time recorded in message
  -IDs, which is UTC.
  -
  -To improve performance, we only use mktime on the date ($year-$mon-$day),
  -and only calculate it if the date is different to the previous time we
  -came here. We then add on seconds for the '$hour:$min:$sec'.
  -
  -We also store the results of the last conversion done, and only
  -recalculate if the date is different.
  -
  -We used to have the '-cache' flag which would store the results of the
  -mktime() call. However, the current way of just using mktime() on the
  -date obsoletes this.
  -
  -=cut
  -
  +#######################################################################
  +# seconds();
  +# 
  +#  $time = seconds($timestamp);
  +# 
  +# Given a time-of-day timestamp, convert it into a time() value using
  +# POSIX::mktime.  We expect the timestamp to be of the form
  +# "$year-$mon-$day $hour:$min:$sec", with month going from 1 to 12,
  +# and the year to be absolute (we do the necessary conversions). The
  +# timestamp may be followed with an offset from UTC like "+$hh$mm"; if the
  +# offset is not present, and we have not been told that the log is in UTC
  +# (with the -utc option), then we adjust the time by the current local
  +# time offset so that it can be compared with the time recorded in message
  +# IDs, which is UTC.
  +# 
  +# To improve performance, we only use mktime on the date ($year-$mon-$day),
  +# and only calculate it if the date is different to the previous time we
  +# came here. We then add on seconds for the '$hour:$min:$sec'.
  +# 
  +# We also store the results of the last conversion done, and only
  +# recalculate if the date is different.
  +# 
  +# We used to have the '-cache' flag which would store the results of the
  +# mktime() call. However, the current way of just using mktime() on the
  +# date obsoletes this.
  +#######################################################################
   sub seconds {
     my($timestamp) = @_;


@@ -708,14 +703,13 @@
}


-=head2 id_seconds();
-
- $time = id_seconds($message_id);
-
-Given a message ID, convert it into a time() value.
-
-=cut
-
+#######################################################################
+# id_seconds();
+#
+# $time = id_seconds($message_id);
+#
+# Given a message ID, convert it into a time() value.
+#######################################################################
sub id_seconds {
my($sub_id) = substr((pop @_), 0, 6);
my($s) = 0;
@@ -726,18 +720,18 @@



-=head2 calculate_localtime_offset();
-
- $localtime_offset = calculate_localtime_offset();
-
-Calculate the the localtime offset from gmtime in seconds.
-
- $localtime = time() + $localtime_offset.
-
-These are the same semantics as ISO 8601 and RFC 2822 timezone offsets.
-(West is negative, East is positive.)
-
-=cut
+#######################################################################
+# calculate_localtime_offset();
+#
+# $localtime_offset = calculate_localtime_offset();
+#
+# Calculate the the localtime offset from gmtime in seconds.
+#
+# $localtime = time() + $localtime_offset.
+#
+# These are the same semantics as ISO 8601 and RFC 2822 timezone offsets.
+# (West is negative, East is positive.)
+#######################################################################

# $localtime = gmtime() + $localtime_offset. OLD COMMENT
# This subroutine commented out as it's not currently in use.
@@ -762,16 +756,15 @@
}


-=head2 print_queue_times();
-
- $time = print_queue_times($message_type,\@queue_times,$queue_more_than);
-
-Given the type of messages being output, the array of message queue times,
-and the number of messages which exceeded the queue times, print out
-a table.
-
-=cut
-
+#######################################################################
+# print_queue_times();
+#
+# $time = print_queue_times($message_type,\@queue_times,$queue_more_than);
+#
+# Given the type of messages being output, the array of message queue times,
+# and the number of messages which exceeded the queue times, print out
+# a table.
+#######################################################################
sub print_queue_times {
no integer;
my($string,$array,$queue_more_than) = @_;
@@ -866,15 +859,14 @@



-=head2 print_histogram();
-
- print_histogram('Deliverieds|Messages received',@interval_count);
-
-Print a histogram of the messages delivered/received per time slot
-(hour by default).
-
-=cut
-
+#######################################################################
+# print_histogram();
+#
+# print_histogram('Deliverieds|Messages received',@interval_count);
+#
+# Print a histogram of the messages delivered/received per time slot
+# (hour by default).
+#######################################################################
sub print_histogram {
my($text) = shift;
my(@interval_count) = @_;
@@ -982,16 +974,15 @@



-=head2 print_league_table();
-
- print_league_table($league_table_type,\%message_count,\%message_data,\%message_data_gigs);
-
-Given hashes of message count and message data, which are keyed by
-the table type (eg by the sending host), print a league table
-showing the top $topcount (defaults to 50).
-
-=cut
-
+#######################################################################
+# print_league_table();
+#
+# print_league_table($league_table_type,\%message_count,\%message_data,\%message_data_gigs);
+#
+# Given hashes of message count and message data, which are keyed by
+# the table type (eg by the sending host), print a league table
+# showing the top $topcount (defaults to 50).
+#######################################################################
sub print_league_table {
my($text,$m_count,$m_data,$m_data_gigs) = @_;
my($name) = ($topcount == 1)? "$text" : "$topcount ${text}s";
@@ -1148,27 +1139,26 @@
}


  -=head2 top_n_sort();
  -
  -  @sorted_keys = top_n_sort($n,$href1,$href2,$href3);
  -
  -Given a hash which has numerical values, return the sorted $n keys which
  -point to the top values. The second and third hashes are used as
  -tiebreakers. They all must have the same keys.
  -
  -The idea behind this routine is that when you only want to see the
  -top n members of a set, rather than sorting the entire set and then
  -plucking off the top n, sort through the stack as you go, discarding
  -any member which is lower than your current n'th highest member.
  -
  -This proves to be an order of magnitude faster for large hashes.
  -On 200,000 lines of mainlog it benchmarked 9 times faster.
  -On 700,000 lines of mainlog it benchmarked 13.8 times faster.
  -
  -We assume the values are > 0.
  -
  -=cut
  -
  +#######################################################################
  +# top_n_sort();
  +# 
  +#   @sorted_keys = top_n_sort($n,$href1,$href2,$href3);
  +# 
  +# Given a hash which has numerical values, return the sorted $n keys which
  +# point to the top values. The second and third hashes are used as
  +# tiebreakers. They all must have the same keys.
  +# 
  +# The idea behind this routine is that when you only want to see the
  +# top n members of a set, rather than sorting the entire set and then
  +# plucking off the top n, sort through the stack as you go, discarding
  +# any member which is lower than your current n'th highest member.
  +# 
  +# This proves to be an order of magnitude faster for large hashes.
  +# On 200,000 lines of mainlog it benchmarked 9 times faster.
  +# On 700,000 lines of mainlog it benchmarked 13.8 times faster.
  +# 
  +# We assume the values are > 0.
  +#######################################################################
   sub top_n_sort {
     my($n,$href1,$href2,$href3) = @_;


  @@ -1192,15 +1182,15 @@
     my $n_minus_1 = $n - 1;
     my $n_minus_2 = $n - 2;


  -  # Pick out the top $n keys.
  +  # Pick out the top $n keys. 
     my($key,$value1,$value2,$value3,$i,$comparison,$insert_position);
     while (($key,$value1) = each %$href1) {


       #print STDERR "key $key ($value1,",$href2->{$key},",",$href3->{$key},") <=> ($minimum_value1,$minimum_value2,$minimum_value3)\n";
  -
  +    
       # Check to see that the new value is bigger than the lowest of the
       # top n keys that we're keeping.
  -    $comparison = $value1        <=> $minimum_value1 ||
  +    $comparison = $value1        <=> $minimum_value1 || 
             $href2->{$key} <=> $minimum_value2 ||
             $href3->{$key} <=> $minimum_value3 ||
             $top_n_key cmp $key;
  @@ -1227,7 +1217,7 @@
       for ($i = 0; $i < $n_minus_1; $i++) {
         $top_n_key = $top_n_keys[$i];
         if ( ($top_n_key eq '_') ||
  -       ( ($value1 <=> $href1->{$top_n_key} ||
  +       ( ($value1 <=> $href1->{$top_n_key} || 
                 $value2 <=> $href2->{$top_n_key} ||
             $value3 <=> $href3->{$top_n_key} ||
             $top_n_key cmp $key) == 1
  @@ -1257,14 +1247,13 @@
   }



  -=head2 html_header();
  -
  - $header = html_header($title);
  -
  -Print our HTML header and start the <body> block.
  -
  -=cut
  -
  +#######################################################################
  +# html_header();
  +# 
  +#  $header = html_header($title);
  +# 
  +# Print our HTML header and start the <body> block.
  +#######################################################################
   sub html_header {
     my($title) = @_;
     my $text = << "EoText";
  @@ -1282,14 +1271,13 @@




  -=head2 help();
  -
  - help();
  -
  -Display usage instructions and exit.
  -
  -=cut
  -
  +#######################################################################
  +# help();
  +# 
  +#  help();
  +# 
  +# Display usage instructions and exit.
  +#######################################################################
   sub help {
     print << "EoText";


@@ -1348,22 +1336,21 @@



  -=head2 generate_parser();
  -
  - $parser = generate_parser();
  -
  -This subroutine generates the parsing routine which will be
  -used to parse the mainlog. We take the base operation, and remove bits not in use.
  -This improves performance depending on what bits you take out or add.
  -
  -I've tested using study(), but this does not improve performance.
  -
  -We store our parsing routing in a variable, and process it looking for #IFDEF (Expression)
  -or #IFNDEF (Expression) statements and corresponding #ENDIF (Expression) statements. If
  -the expression evaluates to true, then it is included/excluded accordingly.
  -
  -=cut
  -
  +#######################################################################
  +# generate_parser();
  +# 
  +#  $parser = generate_parser();
  +# 
  +# This subroutine generates the parsing routine which will be
  +# used to parse the mainlog. We take the base operation, and remove bits not in use.
  +# This improves performance depending on what bits you take out or add.
  +# 
  +# I've tested using study(), but this does not improve performance.
  +# 
  +# We store our parsing routing in a variable, and process it looking for #IFDEF (Expression)
  +# or #IFNDEF (Expression) statements and corresponding #ENDIF (Expression) statements. If
  +# the expression evaluates to true, then it is included/excluded accordingly.
  +#######################################################################
   sub generate_parser {
     my $parser = '
     my($ip,$host,$email,$edomain,$domain,$thissize,$size,$old,$new);
  @@ -1693,15 +1680,14 @@




  -=head2 parse();
  -
  - parse($parser,\*FILEHANDLE);
  -
  -This subroutine accepts a parser and a filehandle from main and parses each
  -line. We store the results into global variables.
  -
  -=cut
  -
  +#######################################################################
  +# parse();
  +# 
  +#  parse($parser,\*FILEHANDLE);
  +# 
  +# This subroutine accepts a parser and a filehandle from main and parses each
  +# line. We store the results into global variables.
  +#######################################################################
   sub parse {
     my($parser,$fh) = @_;


@@ -1717,14 +1703,13 @@



-=head2 print_header();
-
- print_header();
-
-Print our headers and contents.
-
-=cut
-
+#######################################################################
+# print_header();
+#
+# print_header();
+#
+# Print our headers and contents.
+#######################################################################
sub print_header {

     my $title = "Exim statistics from $begin to $end";
  @@ -1774,14 +1759,13 @@
   }



-=head2 print_grandtotals();
-
- print_grandtotals();
-
-Print the grand totals.
-
-=cut
-
+#######################################################################
+# print_grandtotals();
+#
+# print_grandtotals();
+#
+# Print the grand totals.
+#######################################################################
sub print_grandtotals {

     # Get the sender by headings and results. This is complicated as we can have
  @@ -1862,14 +1846,13 @@
   }



  -=head2 print_user_patterns()
  -
  - print_user_patterns();
  -
  -Print the counts of user specified patterns.
  -
  -=cut
  -
  +#######################################################################
  +# print_user_patterns()
  +# 
  +#  print_user_patterns();
  +# 
  +# Print the counts of user specified patterns.
  +#######################################################################
   sub print_user_patterns {
     my($format1);


@@ -1911,14 +1894,13 @@
}


  -=head2 print_transport();
  -
  - print_transport();
  -
  -Print totals by transport.
  -
  -=cut
  -
  +#######################################################################
  +# print_transport();
  +# 
  +#  print_transport();
  +# 
  +# Print totals by transport.
  +#######################################################################
   sub print_transport {
     my($format1);
     my(@chartdatanames);
  @@ -2017,14 +1999,13 @@




  -=head2 print_relay();
  -
  - print_relay();
  -
  -Print our totals by relay.
  -
  -=cut
  -
  +#######################################################################
  +# print_relay();
  +# 
  +#  print_relay();
  +# 
  +# Print our totals by relay.
  +#######################################################################
   sub print_relay {
     my $temp = "Relayed messages";
     print "<hr><a name=\"$temp\"></a><h2>$temp</h2>\n" if $html;
  @@ -2064,15 +2045,14 @@




  -=head2 print_errors();
  -
  - print_errors();
  -
  -Print our errors. In HTML, we display them as a list rather than a table -
  -Netscape doesn't like large tables!
  -
  -=cut
  -
  +#######################################################################
  +# print_errors();
  +# 
  +#  print_errors();
  +# 
  +# Print our errors. In HTML, we display them as a list rather than a table -
  +# Netscape doesn't like large tables!
  +#######################################################################
   sub print_errors {
     my $total_errors = 0;


  @@ -2095,7 +2075,7 @@
         $text =~ s/\s\s+/ /g;    #Convert multiple spaces to a single space.
         $total_errors += $errors_count{$key};
         if ($html) {
  -
  +     
           #Translate HTML tag characters. Sergey Sholokh.
           $text =~ s/\</\&lt\;/g;
           $text =~ s/\>/\&gt\;/g;
  @@ -2123,42 +2103,41 @@
   }



  -=head2 parse_old_eximstat_reports();
  -
  - parse_old_eximstat_reports($fh);
  -
  -Parse old eximstat output so we can merge daily stats to weekly stats and weekly to monthly etc.
  -
  -To test that the merging still works after changes, do something like the following.
  -All the diffs should produce no output.
  -
  - options='-bydomain -byemail -byhost -byedomain'
  - options="$options -pattern 'Completed Messages' /Completed/"
  - options="$options -pattern 'Received Messages' /<=/"
  -
  - ./eximstats $options mainlog > mainlog.txt
  - ./eximstats $options -merge mainlog.txt > mainlog.2.txt
  - diff mainlog.txt mainlog.2.txt
  -
  - ./eximstats $options -html mainlog > mainlog.html
  - ./eximstats $options -merge -html mainlog.txt  > mainlog.2.html
  - diff mainlog.html mainlog.2.html
  -
  - ./eximstats $options -merge mainlog.html > mainlog.3.txt
  - diff mainlog.txt mainlog.3.txt
  -
  - ./eximstats $options -merge -html mainlog.html > mainlog.3.html
  - diff mainlog.html mainlog.3.html
  -
  - ./eximstats $options -nvr   mainlog > mainlog.nvr.txt
  - ./eximstats $options -merge mainlog.nvr.txt > mainlog.4.txt
  - diff mainlog.txt mainlog.4.txt
  -
  - # double_mainlog.txt should have twice the values that mainlog.txt has.
  - ./eximstats $options mainlog mainlog > double_mainlog.txt
  -
  -=cut
  -
  +#######################################################################
  +# parse_old_eximstat_reports();
  +# 
  +#  parse_old_eximstat_reports($fh);
  +# 
  +# Parse old eximstat output so we can merge daily stats to weekly stats and weekly to monthly etc.
  +# 
  +# To test that the merging still works after changes, do something like the following.
  +# All the diffs should produce no output.
  +# 
  +#  options='-bydomain -byemail -byhost -byedomain'
  +#  options="$options -pattern 'Completed Messages' /Completed/"
  +#  options="$options -pattern 'Received Messages' /<=/"
  +# 
  +#  ./eximstats $options mainlog > mainlog.txt
  +#  ./eximstats $options -merge mainlog.txt > mainlog.2.txt
  +#  diff mainlog.txt mainlog.2.txt
  +# 
  +#  ./eximstats $options -html mainlog > mainlog.html
  +#  ./eximstats $options -merge -html mainlog.txt  > mainlog.2.html
  +#  diff mainlog.html mainlog.2.html
  +# 
  +#  ./eximstats $options -merge mainlog.html > mainlog.3.txt
  +#  diff mainlog.txt mainlog.3.txt
  +# 
  +#  ./eximstats $options -merge -html mainlog.html > mainlog.3.html
  +#  diff mainlog.html mainlog.3.html
  +# 
  +#  ./eximstats $options -nvr   mainlog > mainlog.nvr.txt
  +#  ./eximstats $options -merge mainlog.nvr.txt > mainlog.4.txt
  +#  diff mainlog.txt mainlog.4.txt
  +# 
  +#  # double_mainlog.txt should have twice the values that mainlog.txt has.
  +#  ./eximstats $options mainlog mainlog > double_mainlog.txt
  +#######################################################################
   sub parse_old_eximstat_reports {
     my($fh) = @_;


  @@ -2245,10 +2224,10 @@
       next unless $reached_table;
       print STDERR "Parsing $_" if $debug;
       if (/^(\d+):(\d+)\s+(\d+)/) {        #hh:mm start time format ?
  -      $$interval_aref[($1*60 + $2)/$hist_interval] += $3;
  +      $$interval_aref[($1*60 + $2)/$hist_interval] += $3 if $hist_opt;
       }
       elsif (/^(\d+)-(\d+)\s+(\d+)/) {    #hh-hh start-end time format ?
  -      $$interval_aref[($1*60)/$hist_interval] += $3;
  +      $$interval_aref[($1*60)/$hist_interval] += $3 if $hist_opt;
       }
       else {                    #Finished the table ?
         last;
  @@ -2483,15 +2462,14 @@




  -=head2 update_relayed();
  -
  - update_relayed($count,$sender,$recipient);
  -
  -Adds an entry into the %relayed hash. Currently only used when
  -merging reports.
  -
  -=cut
  -
  +#######################################################################
  +# update_relayed();
  +# 
  +#  update_relayed($count,$sender,$recipient);
  +# 
  +# Adds an entry into the %relayed hash. Currently only used when
  +# merging reports.
  +#######################################################################
   sub update_relayed {
     my($count,$sender,$recipient) = @_;


@@ -2509,18 +2487,17 @@
}


  -=head2 add_to_totals();
  -
  - add_to_totals(\%totals,\@keys,$values);
  -
  -Given a line of space seperated values, add them into the provided hash using @keys
  -as the hash keys.
  -
  -If the value contains a '%', then the value is set rather than added. Otherwise, we
  -convert the value to bytes and gigs. The gigs get added to I<Key>-gigs.
  -
  -=cut
  -
  +#######################################################################
  +# add_to_totals();
  +# 
  +#  add_to_totals(\%totals,\@keys,$values);
  +# 
  +# Given a line of space seperated values, add them into the provided hash using @keys
  +# as the hash keys.
  +# 
  +# If the value contains a '%', then the value is set rather than added. Otherwise, we
  +# convert the value to bytes and gigs. The gigs get added to I<Key>-gigs.
  +#######################################################################
   sub add_to_totals {
     my($totals_href,$keys_aref,$values) = @_;
     my(@values) = split(/\s+/,$values);
  @@ -2540,16 +2517,15 @@
     }
   }


  -=head2 get_report_total();
  -
  - $total = get_report_total(\%hash,$key);
  -
  -If %hash contains values split into Units and Gigs, we calculate and return
  -
  -  $hash{$key} + 1024*1024*1024 * $hash{"${key}-gigs"}
  -
  -=cut
  -
  +#######################################################################
  +# get_report_total();
  +# 
  +#  $total = get_report_total(\%hash,$key);
  +# 
  +# If %hash contains values split into Units and Gigs, we calculate and return
  +# 
  +#   $hash{$key} + 1024*1024*1024 * $hash{"${key}-gigs"}
  +#######################################################################
   sub get_report_total {
     no integer;
     my($hash_ref,$key) = @_;
  @@ -2559,15 +2535,14 @@
     return $$hash_ref{$key} || 0;
   }


  -=head2 html2txt();
  -
  - $text_line = html2txt($html_line);
  -
  -Convert a line from html to text. Currently we just convert HTML tags to spaces
  -and convert &gt;, &lt;, and &nbsp; tags back.
  -
  -=cut
  -
  +#######################################################################
  +# html2txt();
  +# 
  +#  $text_line = html2txt($html_line);
  +# 
  +# Convert a line from html to text. Currently we just convert HTML tags to spaces
  +# and convert &gt;, &lt;, and &nbsp; tags back.
  +#######################################################################
   sub html2txt {
     ($_) = @_;


  @@ -2583,24 +2558,23 @@
     return($_);
   }


  -=head2 get_next_arg();
  -
  - $arg = get_next_arg();
  -
  -Because eximstats arguments are often passed as variables,
  -we can't rely on shell parsing to deal with quotes. This
  -subroutine returns $ARGV[1] and does a shift. If $ARGV[1]
  -starts with a quote (' or "), and doesn't end in one, then
  -we append the next argument to it and shift again. We repeat
  -until we've got all of the argument.
  -
  -This isn't perfect as all white space gets reduced to one space,
  -but it's as good as we can get! If it's esential that spacing
  -be preserved precisely, then you get that by not using shell
  -variables.
  -
  -=cut
  -
  +#######################################################################
  +# get_next_arg();
  +# 
  +#  $arg = get_next_arg();
  +# 
  +# Because eximstats arguments are often passed as variables,
  +# we can't rely on shell parsing to deal with quotes. This
  +# subroutine returns $ARGV[1] and does a shift. If $ARGV[1]
  +# starts with a quote (' or "), and doesn't end in one, then
  +# we append the next argument to it and shift again. We repeat
  +# until we've got all of the argument.
  +# 
  +# This isn't perfect as all white space gets reduced to one space,
  +# but it's as good as we can get! If it's esential that spacing
  +# be preserved precisely, then you get that by not using shell
  +# variables.
  +#######################################################################
   sub get_next_arg {
     my $arg = '';
     my $matched_pattern = 0;