#!/usr/local/bin/perl
#
# adsm-report -- To generate a periodic report on ADSM activity, through its accounting records.
#
#     If accounting is turned on in the ADSM server ('Set ACCounting ON'), it will produce a
#     dsmaccnt.log file in the server directory for each transaction.  The format of each records
#     is documented in the ADSM Administrator's Guide manual.
#
#     The report file will contain the following sections:
#      - Sessions by nodename, listing number of objects and KB in all categories of ADSM session
#	 types, with column sums.
#      - Summary statistics.
#      - By-day report, listing sessions, objects, sesion KB and data KB.
#      - By-user report, listing number of objects and KB in all categories of ADSM session
#	 types, with timings.
#
#     The reporting is adaptive, accommodating data widths as encountered, to generate neat
#     report column alignments.
#
#     Output is to file "adsm-report-file".  The report header will show the time reporting range.
#     The program checks for the pre-existence of the output file to prevent over-write of an
#     earlier version, which will be renamed if encountered.
#
#     By convention we generate monthly reports:  /usr/csg/ADSM-reports/ADSM-sessions.YYYYMMDD* .
#
#
# ENVIRONMENT:  Written for ADSM version 3
#		(Product accounting changes little, so will likely work fine on later versions and
#		 releases)
#		Written in Perl 4, and should work under Perl 5 as well.
#
#
# INVOCATION:  "adsm-report [fromyear YEAR]
#			    [frommonth MONTH_NAME|MONTH_NUMBER]
#			    [fromday DAY_OF_MONTH_NUMBER]
#			    [fromtime 24_HR_TIME_VALUE]
#			    [toyear YEAR]
#			    [tomonth MONTH_NAME|MONTH_NUMBER]
#			    [today DAY_OF_MONTH_NUMBER]
#			    [totime 24_HR_TIME_VALUE]
#			    [sortby name|user|size]
#			    [FILE_NAME(S)]"
#     where:
#	      fromyear year_value
#				limits data to that which starts in the given year, expressed as a
#				4-digit number (1996).
#	      frommonth month_name|month_number
#				limits data to that which starts in the given month, expressed
#				either as a month name (case insensitive; Jan, jan, JANUAry being
#				equivalent).
#	      fromday day_of_month_number
#				limits data to that which begins on the given day of month.
#	      fromtime 24_hr_time_value
#				limits data to that which starts at the given time of day, in
#				24-hour-clock time.
#				Examples:  07  to specify just the hour;
#					   07:30  to specify hour & minutes;
#					   13:10:47  to specify hour, min, sec.
#	      toyear year_value
#				limits data to that which ends in the given year, expressed as a
#				4-digit number (1996).
#	      tomonth month_name|month_number
#				limits data to that which ends in the given month, expressed either
#				as a month name (case insensitive; Dec, dec, DECEmber being
#				equivalent).
#	      today day_of_month_number
#				limits data to that which ends on the given day of month.
#	      totime 24_hr_time_value
#				limits data to that which ends at or before the	given time of day,
#				in 24-hour-clock time.
#				Examples:  07  to specify just the hour;
#					   07:30  to specify hour & minutes;
#					   13:10:47  to specify hour, min, sec.
#				Note that if you omit trailing components of the time, maximum
#				values will default.  So if you	code '07', it will be taken as
#				'07:59:59'.  Hence you should code '07:00:00' if you want the
#				"to" time to end exactly at that hour.
#             sortby name|user|size
#				permits reporting in order of user name, or by number of pages
#				printed (the default).
#             FILE_NAME(S)	May specify the file or files which contain the ADSM accounting
#				data which is to be reported.  If not specified, the program
#				checks for the input coming from Stdin,	thus allowing you to pipe
#				the input to it, as perhaps from the zcat of a compressed file.
#
#	If you code any timestamp level, you must code all the levels above it in order to be
#	specific.  So, for example, if you code "fromtime", you need to code "fromday", "frommonth",
#	and "fromyear" as well.
#
#	If no "from" or "to" time range is specified, all data will be reported.
#
#	Set the internal $debug variable to show processing progress, if desired.
#
#
# EXAMPLES:
#
#    To produce a size-sorted report for the month of June, 1995, enter:
#           adsm-report  fromyear 1995 frommonth june toyear 1995 tomonth june \
#				ADSM_accounting.*199306*
#
#    To report from compressed accounting files, use zcat to pipe the data:
#	zcat ADSM_accounting.* | adsm-report ...
#
#
# NOTES:
#
#    - ADSM accounting records contain exclusively character data.
#    - ADSM accounting record dates are stored in MM/DD/YYYY form, and time in hh:mm:ss form.
#    - Advanced maintenance levels will add fields to the end of the accounting record.
#
#
# FUTURE ENHANCEMENTS:
#
#    - Report each input file separately rather than combining all data into one report?
#    - Possibly allow selectivity by nodename or user.  I didn't see much demand for this, so
#      have not include such logic.
#
#
# HISTORY:
#
#    1999/06/14  Created by Richard Sims on personal time, for ADSM accounting data reporting.
#    2007/01/05  The timelocal Perl library function has had historic defects, hopefully now
#		 all corrected, such that compensating is no longer necessary.  RBS
#

require "ctime.pl";
require "timelocal.pl";

#____________________________Governing definitions________________________________________
$report_filename = "adsm-report-file";	# Define the name of the output report.
					# If already existing, the original will be
					# preserved by renaming, then a new one produced.
$debug = 0;				# Set 1 to show progress.
sub ascending  { $a <=> $b; }	# For doing an ascending numerical sort.

# Filtration timestamps which may be overridden by specifying from- and to- time
# values as invocation options.
$from_datestamp = 0;   $to_datestamp = 99999999999999;

$GB = 1073741824;   $BYTES_IN_GB = 1073741824;
$MB = 1048576;   $BYTES_IN_MB = 1048576;
$KB = 1024;   $BYTES_IN_KB = 1024;


#___________________________________Preliminaries___________________________________________
# Get current date-time:
($timenow_wkday,$timenow_mon,$timenow_mday,$timenow_time,$timenow_est,$timenow_year)
   = split(' ',&ctime(time));
# Associative array for translating month name to a number.  Month numbers are
# defined as 2-digit literals to facilitate sorting and date comparision, as
# in "19930617" as a month-day-year combo.
%mon_to_num = ( "Jan","01", "Feb","02", "Mar","03", "Apr","04", "May","05", "Jun","06",
                "Jul","07", "Aug","08", "Sep","09", "Oct","10", "Nov","11", "Dec","12");
#printf("yyyymmdd hh:mm:ss = %s/%s/%s %s.  Weekday = %s.  Month name = %s.\n",
#       $timenow_year, $mon_to_num{$timenow_mon}, $timenow_mday, $timenow_time,
#       $timenow_wkday, $timenow_mon);


#______________________________Process invocation options__________________________________
# Set defaults.
$default_wkday = "any";    $arg_wkday = $default_wkday;
$default_month = "any";    $arg_month = $default_month;
$default_mday  = "any";    $arg_mday  = $default_mday;
$default_year  = "any";    $arg_year  = $default_year;
$default_fromyear  = "any";   $arg_fromyear  = $default_fromyear;
$default_frommonth = "any";   $arg_frommonth = $default_frommonth;
$default_fromday   = "any";   $arg_fromday   = $default_fromday;
$default_fromhour  = "any";   $arg_fromhour  = $default_fromhour;
$default_frommin   = "any";   $arg_frommin   = $default_frommin;
$default_fromsec   = "any";   $arg_fromsec   = $default_fromsec;
$default_toyear    = "any";   $arg_toyear    = $default_toyear;
$default_tomonth   = "any";   $arg_tomonth   = $default_tomonth;
$default_today     = "any";   $arg_today     = $default_today;
$default_tohour    = "any";   $arg_tohour    = $default_tohour;
$default_tomin     = "any";   $arg_tomin     = $default_tomin;
$default_tosec     = "any";   $arg_tosec     = $default_tosec;
$sortby_name = 0;   $sortby_size = 1;   # Default is to sort by size.
# Obsolete operands - do not use:
#					[mon|month MONTH_NAME]
#					[wkday|weekday WEEKDAY_NAME]
#					[day DAY_OF_MONTH_NUMBER]
#					[year YEAR]
#	      mon|month  month_name
#				permits reporting printing which occurred on
#				a certain month name ("jan", "Feb", etc.).
#				Enter at least the first 3 characters of the
#				month name (in upper or lower case).
#             wkday|weekday  weekday_name
#				permits reporting printing which occurred on
#				a certain day of the week ("mon", "Tue", etc.).
#				Enter at least the first 3 characters of the
#				day name (in upper or lower case).
#             day  day_of_month_number
#				permits reporting printing which occurred on
#				a certain day number in the month ("3", "29",
#				etc.).
#             year  year_number
#				permits reporting printing which occurred in
#				a certain year.
$argc = $#ARGV + 1;

# If no invocation operands, we will subsequently try Stdin as data source.
while ($#ARGV > -1)
   {
   $word = shift(@ARGV);  # Take the leftmost word.

   if (($argc == 1)  &&  (($word =~ m|help|i)  ||  ($word =~ m|-help|i)))
      {
      &Show_Usage_From_Prolog();   exit(0);
      }
   elsif (($word eq "wkday")  ||  ($word eq "weekday"))
      {
      if ($#ARGV == -1)
         {
         printf("Keyword '%s' found, but no weekday name follows it.\n", $word);
         exit(1);
         }
      else
         {
         $_ = shift(@ARGV);  # Take the next token as weekday name.
         ARG_WKDAY:
            {
            # Look for days of the week:
            /^[Mm][Oo][Nn].*/  &&  do {$arg_wkday = "Mon"; last ARG_WKDAY; };
            /^[Tt][Uu][Ee].*/  &&  do {$arg_wkday = "Tue"; last ARG_WKDAY; };
            /^[Ww][Ee][Dd].*/  &&  do {$arg_wkday = "Wed"; last ARG_WKDAY; };
            /^[Tt][Hh][Uu].*/  &&  do {$arg_wkday = "Thu"; last ARG_WKDAY; };
            /^[Ff][Rr][Ii].*/  &&  do {$arg_wkday = "Fri"; last ARG_WKDAY; };
            /^[Ss][Aa][Tt].*/  &&  do {$arg_wkday = "Sat"; last ARG_WKDAY; };
            /^[Ss][Uu][Nn].*/  &&  do {$arg_wkday = "Sun"; last ARG_WKDAY; };
            # None of the above, so reject.
            printf("Weekday name value '%s' not recognized.\n",$_);   exit(1);
            }
         }
      } # end of "weekday" parameter processing
   elsif (($word eq "mon")  ||  ($word eq "month"))
      {
      if ($#ARGV == -1)
         {
         printf("Keyword '%s' found, but no month name follows it.\n", $word);
         exit(1);
         }
      else
         {
         $_ = shift(@ARGV);  # Take the next token as month name.
         ARG_MONTH:
            {
	    # Spell out the month names - looks better in header than 3-char abbrev.
            /^[Jj][Aa][Nn].*/  &&  do {$arg_month = "January";   last ARG_MONTH; };
            /^[Ff][Ee][Bb].*/  &&  do {$arg_month = "February";  last ARG_MONTH; };
            /^[Mm][Aa][Rr].*/  &&  do {$arg_month = "March";     last ARG_MONTH; };
            /^[Aa][Pp][Rr].*/  &&  do {$arg_month = "April";     last ARG_MONTH; };
            /^[Mm][Aa][Yy].*/  &&  do {$arg_month = "May";       last ARG_MONTH; };
            /^[Jj][Uu][Nn].*/  &&  do {$arg_month = "June";      last ARG_MONTH; };
            /^[Jj][Uu][Ll].*/  &&  do {$arg_month = "July";      last ARG_MONTH; };
            /^[Aa][Uu][Gg].*/  &&  do {$arg_month = "August";    last ARG_MONTH; };
            /^[Ss][Ee][Pp].*/  &&  do {$arg_month = "September"; last ARG_MONTH; };
            /^[Oo][Cc][Tt].*/  &&  do {$arg_month = "October";   last ARG_MONTH; };
            /^[Nn][Oo][Vv].*/  &&  do {$arg_month = "November";  last ARG_MONTH; };
            /^[Dd][Ee][Cc].*/  &&  do {$arg_month = "December";  last ARG_MONTH; };
            # None of the above, so reject.
            printf("Month name value '%s' not recognized.\n",$_);   exit(1);
            }
         }
      } # end of month parameter processing
   elsif ($word eq "day")
      {
      if ($#ARGV == -1)
         {
         printf("Keyword '%s' found, but no day-of-month number follows it.\n", $word);
         exit(1);
         }
      else
         {
         $arg_mday = shift(@ARGV);  # Take the next token as day number.
         if ($arg_mday =~ /\D/)
            {
            printf("Ahem...your given day of month value, '%s', is not numeric.\n",
                   $arg_mday);
            exit 1;
            }
         elsif ( ($arg_mday < 1)  ||  ($arg_mday > 31) )
            {
            printf("Ahem...your given day of month value, '%s', is not in range 1-31.\n",
                   $arg_mday);
            exit 1;
            }
         }
      } # end of "day" parameter processing
   elsif ($word eq "year")
      {
      if ($#ARGV == -1)
         {
         printf("Keyword '%s' found, but no year value follows it.\n", $word);
         exit(1);
         }
      else
         {
         $arg_year = shift(@ARGV);  # Take the next token as year.
         if ($arg_year =~ /\D/)
            {
            printf("Ahem...your given year value, '%s', is not numeric.\n",
                   $arg_year);
            exit 1;
            }
         elsif ( ($arg_year < 1111)  ||  ($arg_year > 9999) )
            {
            printf("Ahem...your given year value, '%s', is not a 4-digit number.\n",
                   $arg_year);
            exit 1;
            }
         }
      } # end of "year" parameter processing
   elsif ($word eq "fromyear")
      {
      if ($#ARGV == -1)
         {
         printf("Keyword '%s' found, but no year value follows it.\n", $word);
         exit(1);
         }
      else
         {
	 #_______________________________Evaluate "fromyear year_value"_____________________________________
         $arg_fromyear = shift(@ARGV);  # Take the next token as value.
	 if (length($arg_fromyear) != 4)
	    {
	    printf(STDERR "fromyear value '%s' is not 4 digits - quitting\n", $arg_fromyear);
	    exit(1);
	    }
	 # Value is 4 characters.  See if digits.
	 if ($arg_fromyear !~ /(\d\d\d\d)/)
	    {
	    printf(STDERR "fromyear value '%s' is not 4 digits - quitting\n", $arg_fromyear);
	    exit(1);
            }
	 # The value is 4 digits.  Assure reasonable.
	 if ($arg_fromyear > $timenow_year)
	    {
	    printf(STDERR "fromyear value '%s' is beyond this year, %s, which doesn't make sense - quitting\n",
		   $arg_fromyear, $timenow_year);
	    exit(1);
            }
	 # (We don't check for the from-year being below any value, because it doesn't matter.)
	 # The value seems reasonable.
	 $from_specified = 1;
         }
      } # end of "fromyear" parameter processing
   elsif ($word eq "frommonth")
      {
      if ($#ARGV == -1)
         {
         printf("Keyword '%s' found, but no month value follows it.\n", $word);
         exit(1);
         }
      else
         {
	 #___________________________Evaluate "frommonth month_value"_______________________________________
         $arg_frommonth = shift(@ARGV);  # Take the next token as value.
	 if ($arg_frommonth =~ /\d+/)	# Numeric month.
	    {
	    #_____________________________Evaluate numeric month value____________________________________
	    # Month value should then be 1 or 2 digits, from 1 - 12.
	    if (($arg_frommonth < 1)  ||  ($arg_frommonth > 12))
	       {
	       printf(STDERR "Numeric frommonth value '%s' is not 1-12 - quitting\n", $arg_frommonth);
	       exit(1);
	       }
	    # The month number is 1 - 12; convert to zero-basis month number, for later feeding to timelocal().
	    $arg_frommonthnum = $arg_frommonth - 1;
	    }
	 else				# Alphabetic month.
	    {
	    #_________________________Evaluate alphabetic month value____________________________
	    # Convert month name to zero-basis month number, for later feeding to timelocal().
	    ARG_FROMMONTH:
               {
	       $arg_frommonth =~ /^[Jj][Aa][Nn].*/  &&  do {$arg_frommonthnum = 0;   last ARG_FROMMONTH; };
	       $arg_frommonth =~ /^[Ff][Ee][Bb].*/  &&  do {$arg_frommonthnum = 1;   last ARG_FROMMONTH; };
	       $arg_frommonth =~ /^[Mm][Aa][Rr].*/  &&  do {$arg_frommonthnum = 2;   last ARG_FROMMONTH; };
	       $arg_frommonth =~ /^[Aa][Pp][Rr].*/  &&  do {$arg_frommonthnum = 3;   last ARG_FROMMONTH; };
	       $arg_frommonth =~ /^[Mm][Aa][Yy].*/  &&  do {$arg_frommonthnum = 4;   last ARG_FROMMONTH; };
	       $arg_frommonth =~ /^[Jj][Uu][Nn].*/  &&  do {$arg_frommonthnum = 5;   last ARG_FROMMONTH; };
	       $arg_frommonth =~ /^[Jj][Uu][Ll].*/  &&  do {$arg_frommonthnum = 6;   last ARG_FROMMONTH; };
	       $arg_frommonth =~ /^[Aa][Uu][Gg].*/  &&  do {$arg_frommonthnum = 7;   last ARG_FROMMONTH; };
	       $arg_frommonth =~ /^[Ss][Ee][Pp].*/  &&  do {$arg_frommonthnum = 8;   last ARG_FROMMONTH; };
	       $arg_frommonth =~ /^[Oo][Cc][Tt].*/  &&  do {$arg_frommonthnum = 9;   last ARG_FROMMONTH; };
	       $arg_frommonth =~ /^[Nn][Oo][Vv].*/  &&  do {$arg_frommonthnum = 10;  last ARG_FROMMONTH; };
	       $arg_frommonth =~ /^[Dd][Ee][Cc].*/  &&  do {$arg_frommonthnum = 11;  last ARG_FROMMONTH; };
	       # None of the above, so reject.
	       printf("frommonth name value '%s' not recognized - needs to be like Jan, February, etc.\n",
		      $arg_frommonth);
	       exit(1);
	       }
	    }
	 $from_specified = 1;
	 }
      } # end of "frommonth" parameter processing
   elsif ($word eq "fromday")
      {
      if ($#ARGV == -1)
         {
         printf("Keyword '%s' found, but no day value follows it.\n", $word);
         exit(1);
         }
      else
         {
	 #___________________________Evaluate "fromday day_value"_________________________________
         $arg_fromday = shift(@ARGV);  # Take the next token as value.
	 if ($arg_fromday =~ /\d+/)	# Numeric day.
	    {
	    #_________________________Evaluate numeric day value________________________________
	    # Day value should then be 1 or 2 digits, from 1 - 31.
	    if (($arg_fromday < 1)  ||  ($arg_fromday > 31))
	       {
	       printf(STDERR "Numeric fromday value '%s' is not 1-31 - quitting\n", $arg_fromday);
	       exit(1);
	       }
	    # The day number is 1 - 31.
	    $arg_fromdaynum = $arg_fromday;
	    }
	 else				# Alphabetic day.
	    {
	    printf("fromday value '%s' is not numeric (1-31) - quitting.\n",
		   $arg_fromday);
	    exit(1);
	    }
	 $from_specified = 1;
	 }
      } # end of "fromday" parameter processing
   elsif ($word eq "fromtime")
      {
      if ($#ARGV == -1)
         {
         printf("Keyword '%s' found, but no time value follows it.\n", $word);
         exit(1);
         }
      else
         {
	 #_______________________________Evaluate "fromtime time_value"_____________________________________
         $arg_fromtime = shift(@ARGV);  # Take the next token as value.
         ARG_FROMTIME:
	    {
	    $arg_fromtime =~ /(\d{1,2}):(\d{1,2}):(\d{1,2})/  &&  do { $arg_fromhour = $1; $arg_frommin = $2;
								       $arg_fromsec = $3; last ARG_FROMTIME; };
	    $arg_fromtime =~ /(\d{1,2}):(\d{1,2})/  &&  do { $arg_fromhour = $1; $arg_frommin = $2;
							     last ARG_FROMTIME; };
	    $arg_fromtime =~ /(\d{1,2})/  &&  do { $arg_fromhour = $1; last ARG_FROMTIME; };
            # None of the above, so reject.
            printf("Fromtime value '%s' not recognized.\n", $arg_fromtime);   exit(1);
            }
	 # Assure validity:
	 if ($arg_fromhour ne "any")
	    {
	    if ($arg_fromhour  >  23)
	       {
	       printf(STDERR "fromtime hour value '%s' is invalid - quitting\n", $arg_fromhour);   exit(1);
	       }
	    }
	 if ($arg_frommin ne "any")
	    {
	    if ($arg_frommin  >  59)
	       {
	       printf(STDERR "fromtime minute value '%s' is invalid - quitting\n", $arg_frommin);   exit(1);
	       }
	    }
	 if ($arg_fromsec ne "any")
	    {
	    if ($arg_fromsec  >  59)
	       {
	       printf(STDERR "fromtime second value '%s' is invalid - quitting\n", $arg_fromsec);   exit(1);
	       }
	    }
	 #printf("Fromtime '%s' interpreted to %s:%s:%s.\n", $arg_fromtime, $arg_fromhour, $arg_frommin, $arg_fromsec);
	 $from_specified = 1;
         }
      } # end of "fromtime" parameter processing
   elsif ($word eq "toyear")
      {
      if ($#ARGV == -1)
         {
         printf("Keyword '%s' found, but no year value follows it.\n", $word);
         exit(1);
         }
      else
         {
	 #_______________________________Evaluate "toyear year_value"_____________________________________
         $arg_toyear = shift(@ARGV);  # Take the next token as value.
	 if (length($arg_toyear) != 4)
	    {
	    printf(STDERR "toyear value '%s' is not 4 digits - quitting\n", $arg_toyear);
	    exit(1);
	    }
	 # Value is 4 characters.  See if digits.
	 if ($arg_toyear !~ /(\d\d\d\d)/)
	    {
	    printf(STDERR "toyear value '%s' is not 4 digits - quitting\n", $arg_toyear);
	    exit(1);
            }
	 # The value is 4 digits.  Assure reasonable.
	 if ($arg_toyear > $timenow_year)
	    {
	    printf(STDERR "toyear value '%s' is beyond this year, %s, which doesn't make sense - quitting\n",
		   $arg_toyear, $timenow_year);
	    exit(1);
            }
	 # The value seems reasonable.
	 $to_specified = 1;
         }
      } # end of "toyear" parameter processing
   elsif ($word eq "tomonth")
      {
      if ($#ARGV == -1)
         {
         printf("Keyword '%s' found, but no month value follows it.\n", $word);
         exit(1);
         }
      else
         {
	 #_______________________________Evaluate "tomonth month_value"_____________________________________
         $arg_tomonth = shift(@ARGV);  # Take the next token as value.
	 if ($arg_tomonth =~ /\d+/)	# Numeric month.
	    {
	    #_____________________________Evaluate numeric month value____________________________________
	    # Month value should then be 1 or 2 digits, to 1 - 12.
	    if (($arg_tomonth < 1)  ||  ($arg_tomonth > 12))
	       {
	       printf(STDERR "Numeric tomonth value '%s' is not 1-12 - quitting\n", $arg_tomonth);
	       exit(1);
	       }
	    # The month number is 1 - 12; convert to zero-basis month number, for later feeding to timelocal().
	    $arg_tomonthnum = $arg_tomonth - 1;
	    }
	 else				# Alphabetic month.
	    {
	    #_________________________Evaluate alphabetic month value________________________________
	    # Convert month name to zero-basis month number, for later feeding to timelocal().
	    ARG_TOMONTH:
               {
	       $arg_tomonth =~ /^[Jj][Aa][Nn].*/  &&  do {$arg_tomonthnum = 0;   last ARG_TOMONTH; };
	       $arg_tomonth =~ /^[Ff][Ee][Bb].*/  &&  do {$arg_tomonthnum = 1;   last ARG_TOMONTH; };
	       $arg_tomonth =~ /^[Mm][Aa][Rr].*/  &&  do {$arg_tomonthnum = 2;   last ARG_TOMONTH; };
	       $arg_tomonth =~ /^[Aa][Pp][Rr].*/  &&  do {$arg_tomonthnum = 3;   last ARG_TOMONTH; };
	       $arg_tomonth =~ /^[Mm][Aa][Yy].*/  &&  do {$arg_tomonthnum = 4;   last ARG_TOMONTH; };
	       $arg_tomonth =~ /^[Jj][Uu][Nn].*/  &&  do {$arg_tomonthnum = 5;   last ARG_TOMONTH; };
	       $arg_tomonth =~ /^[Jj][Uu][Ll].*/  &&  do {$arg_tomonthnum = 6;   last ARG_TOMONTH; };
	       $arg_tomonth =~ /^[Aa][Uu][Gg].*/  &&  do {$arg_tomonthnum = 7;   last ARG_TOMONTH; };
	       $arg_tomonth =~ /^[Ss][Ee][Pp].*/  &&  do {$arg_tomonthnum = 8;   last ARG_TOMONTH; };
	       $arg_tomonth =~ /^[Oo][Cc][Tt].*/  &&  do {$arg_tomonthnum = 9;   last ARG_TOMONTH; };
	       $arg_tomonth =~ /^[Nn][Oo][Vv].*/  &&  do {$arg_tomonthnum = 10;  last ARG_TOMONTH; };
	       $arg_tomonth =~ /^[Dd][Ee][Cc].*/  &&  do {$arg_tomonthnum = 11;  last ARG_TOMONTH; };
	       # None of the above, so reject.
	       printf("tomonth name value '%s' not recognized - needs to be like Jan, February, etc.\n",
		      $arg_tomonth);
	       exit(1);
	       }
	    }
	 $to_specified = 1;
	 }
      } # end of "tomonth" parameter processing
   elsif ($word eq "today")
      {
      if ($#ARGV == -1)
         {
         printf("Keyword '%s' found, but no day value follows it.\n", $word);
         exit(1);
         }
      else
         {
	 #_____________________________Evaluate "today day_value"_______________________________
         $arg_today = shift(@ARGV);  # Take the next token as value.
	 if ($arg_today =~ /\d+/)	# Numeric day.
	    {
	    #_________________________Evaluate numeric day value______________________________
	    # Day value should then be 1 or 2 digits, to 1 - 31.
	    if (($arg_today < 1)  ||  ($arg_today > 31))
	       {
	       printf(STDERR "Numeric today value '%s' is not 1-31 - quitting\n", $arg_today);
	       exit(1);
	       }
	    # The day number is 1 - 31.
	    $arg_todaynum = $arg_today;
	    }
	 else				# Alphabetic day.
	    {
	    printf("today value '%s' is not numeric (1-31) - quitting.\n",
		   $arg_today);
	    exit(1);
	    }
	 $to_specified = 1;
	 }
      } # end of "today" parameter processing
   elsif ($word eq "totime")
      {
      if ($#ARGV == -1)
         {
         printf("Keyword '%s' found, but no time value follows it.\n", $word);
         exit(1);
         }
      else
         {
	 #_______________________________Evaluate "totime time_value"_________________________________
         $arg_totime = shift(@ARGV);  # Take the next token as value.
         ARG_TOTIME:
	    {
	    $arg_totime =~ /(\d{1,2}):(\d{1,2}):(\d{1,2})/  &&  do { $arg_tohour = $1; $arg_tomin = $2;
								     $arg_tosec = $3; last ARG_TOTIME; };
	    $arg_totime =~ /(\d{1,2}):(\d{1,2})/  &&  do { $arg_tohour = $1; $arg_tomin = $2;
							   last ARG_TOTIME; };
	    $arg_totime =~ /(\d{1,2})/  &&  do { $arg_tohour = $1; last ARG_TOTIME; };
            # None of the above, so reject.
            printf("Totime value '%s' not recognized.\n", $arg_totime);   exit(1);
            }
	 # Assure validity:
	 if ($arg_tohour ne "any")
	    {
	    if ($arg_tohour  >  23)
	       {
	       printf(STDERR "totime hour value '%s' is invalid - quitting\n", $arg_tohour);   exit(1);
	       }
	    }
	 if ($arg_tomin ne "any")
	    {
	    if ($arg_tomin  >  59)
	       {
	       printf(STDERR "totime minute value '%s' is invalid - quitting\n", $arg_tomin);   exit(1);
	       }
	    }
	 if ($arg_tosec ne "any")
	    {
	    if ($arg_tosec  >  59)
	       {
	       printf(STDERR "totime second value '%s' is invalid - quitting\n", $arg_tosec);   exit(1);
	       }
	    }
	 $to_specified = 1;
         }
      } # end of "totime" parameter processing
   elsif ($word eq "sortby")
      {
      if ($#ARGV == -1)
         {
         printf("Keyword '%s' found, but neither 'name' nor 'size' follows it.\n", $word);
         exit(1);
         }
      else
         {
         $arg_sortby = shift(@ARGV);  # Take the next token as value.
         $sortby_name = 0;   $sortby_size = 0;   # Reset possibilities so that only
						 # one may prevail.
         ARG_SORTBY:
            {
            $arg_sortby =~ /^[Nn][Aa][Mm][Ee]/  &&  do {$sortby_name = 1; last ARG_SORTBY; };
            $arg_sortby =~ /^[Uu][Ss][Ee][Rr]/  &&  do {$sortby_name = 1; last ARG_SORTBY; };
            $arg_sortby =~ /^[Ss][Ii][Zz][Ee]/  &&  do {$sortby_size = 1; last ARG_SORTBY; };
            # None of the above, so reject.
            printf("Sortby value '%s' not recognized.\n",$arg_sortby);   exit(1);
            }
         }
      } # end of "sortby" parameter processing
   else
      {
      #_______________________________Process apparent file name_________________________________
      $arg_file = $word;  # Take the token as file name.
      printf("Processing file name '%s'.\n", $arg_file);
      if (! &Validate_Filename($arg_file))
	 {
	 printf("Quitting because of failed validation on file name '%s'.\n",
		$arg_file);
	 exit(1);		   # Cannot continue.
	 }
      push(@arg_files,$arg_file);	# Accumulate file names for later processing, after all
					# arguments have been received and evaluated.
      }
   }

#________________________Check consistency of some parameters___________________________
# If lower items in the time composite are specified, those above must be.
if ($arg_frommonth ne "any")
   {
   if ($arg_fromyear eq "any")
      {
      printf(STDERR "frommonth specified, but not encompassing year - quitting.\n");
      exit(1);
      }
   }
if ($arg_fromday ne "any")
   {
   if ($arg_frommonth eq "any")
      {
      printf(STDERR "fromday specified, but not encompassing month - quitting.\n");
      exit(1);
      }
   if ($arg_fromyear eq "any")
      {
      printf(STDERR "fromday specified, but not encompassing year - quitting.\n");
      exit(1);
      }
   }
if ($arg_fromhour ne "any")
   {
   if ($arg_fromday eq "any")
      {
      printf(STDERR "fromhour specified, but not encompassing day - quitting.\n");
      exit(1);
      }
   if ($arg_frommonth eq "any")
      {
      printf(STDERR "fromhour specified, but not encompassing month - quitting.\n");
      exit(1);
      }
   if ($arg_fromyear eq "any")
      {
      printf(STDERR "fromhour specified, but not encompassing year - quitting.\n");
      exit(1);
      }
   }
if ($arg_frommin ne "any")
   {
   if ($arg_fromhour eq "any")
      {
      printf(STDERR "frommin specified, but not encompassing hour - quitting.\n");
      exit(1);
      }
   if ($arg_fromday eq "any")
      {
      printf(STDERR "frommin specified, but not encompassing day - quitting.\n");
      exit(1);
      }
   if ($arg_frommonth eq "any")
      {
      printf(STDERR "frommin specified, but not encompassing month - quitting.\n");
      exit(1);
      }
   if ($arg_fromyear eq "any")
      {
      printf(STDERR "frommin specified, but not encompassing year - quitting.\n");
      exit(1);
      }
   }
if ($arg_fromsec ne "any")
   {
   if ($arg_frommin eq "any")
      {
      printf(STDERR "fromsec specified, but not encompassing minute - quitting.\n");
      exit(1);
      }
   if ($arg_fromhour eq "any")
      {
      printf(STDERR "fromsec specified, but not encompassing hour - quitting.\n");
      exit(1);
      }
   if ($arg_fromday eq "any")
      {
      printf(STDERR "fromsec specified, but not encompassing day - quitting.\n");
      exit(1);
      }
   if ($arg_frommonth eq "any")
      {
      printf(STDERR "fromsec specified, but not encompassing month - quitting.\n");
      exit(1);
      }
   if ($arg_fromyear eq "any")
      {
      printf(STDERR "fromsec specified, but not encompassing year - quitting.\n");
      exit(1);
      }
   }

if ($arg_tomonth ne "any")
   {
   if ($arg_toyear eq "any")
      {
      printf(STDERR "tomonth specified, but not encompassing year - quitting.\n");
      exit(1);
      }
   }
if ($arg_today ne "any")
   {
   if ($arg_tomonth eq "any")
      {
      printf(STDERR "today specified, but not encompassing month - quitting.\n");
      exit(1);
      }
   if ($arg_toyear eq "any")
      {
      printf(STDERR "today specified, but not encompassing year - quitting.\n");
      exit(1);
      }
   }
if ($arg_tohour ne "any")
   {
   if ($arg_today eq "any")
      {
      printf(STDERR "tohour specified, but not encompassing day - quitting.\n");
      exit(1);
      }
   if ($arg_tomonth eq "any")
      {
      printf(STDERR "tohour specified, but not encompassing month - quitting.\n");
      exit(1);
      }
   if ($arg_toyear eq "any")
      {
      printf(STDERR "tohour specified, but not encompassing year - quitting.\n");
      exit(1);
      }
   }
if ($arg_tomin ne "any")
   {
   if ($arg_tohour eq "any")
      {
      printf(STDERR "tomin specified, but not encompassing hour - quitting.\n");
      exit(1);
      }
   if ($arg_today eq "any")
      {
      printf(STDERR "tomin specified, but not encompassing day - quitting.\n");
      exit(1);
      }
   if ($arg_tomonth eq "any")
      {
      printf(STDERR "tomin specified, but not encompassing month - quitting.\n");
      exit(1);
      }
   if ($arg_toyear eq "any")
      {
      printf(STDERR "tomin specified, but not encompassing year - quitting.\n");
      exit(1);
      }
   }
if ($arg_tosec ne "any")
   {
   if ($arg_tomin eq "any")
      {
      printf(STDERR "tosec specified, but not encompassing minute - quitting.\n");
      exit(1);
      }
   if ($arg_tohour eq "any")
      {
      printf(STDERR "tosec specified, but not encompassing hour - quitting.\n");
      exit(1);
      }
   if ($arg_today eq "any")
      {
      printf(STDERR "tosec specified, but not encompassing day - quitting.\n");
      exit(1);
      }
   if ($arg_tomonth eq "any")
      {
      printf(STDERR "tosec specified, but not encompassing month - quitting.\n");
      exit(1);
      }
   if ($arg_toyear eq "any")
      {
      printf(STDERR "tosec specified, but not encompassing year - quitting.\n");
      exit(1);
      }
   }

# If any from- time specified, construct composite to override default from_timestamp.
if ($from_specified)
   {
   if ($debug)  { printf("arg_fromsec = %s, arg_frommin = %s, arg_fromhour = %s, arg_fromday = %s,\n"
		       ." arg_frommonth = %s, arg_fromyear = %s\n",
		        $arg_fromsec, $arg_frommin, $arg_fromhour, $arg_fromday, $arg_frommonth, $arg_fromyear); }
   # Note that timelocal() has had historic defects, the worse in having the hour base-1 instead of base-0, such
   # that it was previously necessary to subtract 1 from the clock hour for its processing to come out right.
   # It appears that the function's authors finally have it right, so I removed the compensations.
   $from_secssince1970 = &timelocal($arg_fromsec   eq "any" ? 0  : $arg_fromsec,		# Base value: 0
				    $arg_frommin   eq "any" ? 0  : $arg_frommin,		# Base value: 0
				    $arg_fromhour  eq "any" ? 0  : $arg_fromhour,		# Base value: 0
				    $arg_fromday   eq "any" ? 1  : $arg_fromday,		# Base value: 1
				    $arg_frommonth eq "any" ? 0  : $arg_frommonthnum,		# Base value: 0
				    $arg_fromyear  eq "any" ? 0  : $arg_fromyear - 1900);	# Base value: 1
   $from_datestamp = &Secs_to_YYYYMMDDhhmmss($from_secssince1970);
   $from_datestamp_text = &Sub_Secs_to_DateTime($from_secssince1970);
   if ($debug)  { printf("from_secssince1970 resolves to '%s'; interprets to %s\n",
			$from_secssince1970, $from_datestamp); }
   }
else
   {
   $from_datestamp = "Any_Time";   $from_datestamp_text = "Any_Time";
   }

# If any to- time specified, construct composite to override default to_timestamp.
if ($to_specified)
   {
   @days_in_month = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
   if ($debug)  { printf("arg_tosec = %s, arg_tomin = %s, arg_tohour = %s, arg_today = %s,\n"
		       ." arg_tomonth = %s, arg_toyear = %s\n",
		        $arg_tosec, $arg_tomin, $arg_tohour, $arg_today, $arg_tomonth, $arg_toyear); }
   # Note that the "to" values cannot be simply the maximum for the item: they must be
   # the maximum within the context of any chosen values.  For example, if 'tomonth'
   # were specified by the invoker, it would be inappropriate to let the day number become
   # 31; it should instead be the maximum value for that month.  Letting the day number be
   # 31 for February would result in timelocal returning a timestamp for March 3rd.
   if ($arg_toyear  eq "any")
      { $to_year = $timenow_year - 1900; }	# No year specified, so use this year.
   else  { $to_year = $arg_toyear - 1900; }	# Year specified, so use that.
   if ($arg_tomonth eq "any")
      {		# No month specified: use this month if no year specified; else use max month.
      if ($arg_toyear eq "any")  { $to_month = $timenow_mon; }
      else { $to_month = 11; }			# Maximum month number (range 0-11).
      }
   else { $to_month = $arg_tomonthnum; }	# Use specified month.
   if ($arg_today eq "any")
      {		# No day specified: use the maximum for the above-chosen month.
      # To do this we have to accommodate leap year...
      if ($to_month == 1)	# If February, 29 days on leap year, else 28...
	 {
	 if (&Is_Leapyear($to_year))  { $to_day = 29; }  else { $to_day = 28; }
	 }
      else  { $to_day = $days_in_month[$to_month]; }	# Else use last day in non-leapyear month.
      }
   else  { $to_day = $arg_today; }		# Day specified, so use it.
   # Note that timelocal() has had historic defects, the worse in having the hour base-1 instead of base-0, such
   # that it was previously necessary to subtract 1 from the clock hour for its processing to come out right.
   # It appears that the function's authors finally have it right, so I removed the compensations.
   if ($arg_tohour eq "any")  { $to_hour = 23; }  else  { $to_hour = $arg_tohour; }
   if ($arg_tomin  eq "any")  { $to_min  = 59; }  else  { $to_min  = $arg_tomin;  }
   if ($arg_tosec  eq "any")  { $to_sec  = 59; }  else  { $to_sec  = $arg_tosec;  }

   # Note that timelocal() seems to have a defect in making the hour base-1 instead of base-0, necessitating
   # subtracting 1 from the clock hour for its processing to come out right.
   $to_secssince1970 = &timelocal($to_sec, $to_min, $to_hour, $to_day, $to_month, $to_year);

   $to_datestamp = &Secs_to_YYYYMMDDhhmmss($to_secssince1970);
   $to_datestamp_text = &Sub_Secs_to_DateTime($to_secssince1970);
   if ($debug)  { printf("to_secssince1970 resolves to '%s'; interprets to %s",
		        $to_secssince1970, $to_datestamp); }
   }
else
   {
   $to_datestamp = "Any_Time";   $to_datestamp_text = "Any_Time";
   }

# Assure that to-time is not less than from-time:
if ($to_datestamp < $from_datestamp)
   {
   printf(STDERR "to-time (%s) is less than from-time (%s) - quitting\n",
	  $to_datestamp, $from_datestamp);
   exit(1);
   }


#_________________With all args now in and validated, process per data source________________
printf("\nReporting ADSM sessions, from %s to %s\n", $from_datestamp_text, $to_datestamp_text);

if (defined(@arg_files))
   {
   #_______________________Process file names from command line___________________________
   foreach $arg_file (@arg_files)   { &Absorb_Data_Source($arg_file); }
   }
else
   {
   #___________No names were not supplied on cmdline - see if Stdin has data____________
   # We'll take either redirected or piped Stdin.  (Stdin being the terminal is too
   # lame to be considered worthy.)
   if ((-f STDIN)  ||  (-p STDIN))
      {
      if (-f STDIN)  { printf("\nTaking data from file redirected to Stdin...\n"); }
      if (-p STDIN)  { printf("\nTaking data from pipe to Stdin...\n"); }

      #_____________________Use Stdin as the source of the data_____________________
      $arg_file = "-";
      # Getting to here means we got usable usernames via Stdin.
      # Variable $arg_file is now ready for later processing.
      $rc = &Absorb_Data_Source($arg_file);
      if ($rc == 0)
	 {
	 print(STDERR "No data supplied through Stdin - quitting.\n");
	 exit(1);
	 }
      }
   else
      {
      print(STDERR "Data not supplied via named files or Stdin - quitting.\n");
      exit(1);
      }
   }

&Summarize_Data();


#______________________________Program done________________________________
exit 0;



#=================================  Subroutine Absorb_Data_Source  =====================================
sub Absorb_Data_Source
#
# Subroutine to process data from one input file to output file.
#
# INVOCATION:  $? = &Absorb_Data_Source(FileName);
#
# RETURNS:  Number of data lines processed.
#
   {

   #___________________________________________Local definitions________________________________________________________
   local($arg_file) = scalar($_[0]);				# Grab the given file name.
   if (! defined($first_time_through))  { $first_time_through = 1; }	# Turned off after first time through.

   $nodename_column_title1      = " ";          $nodename_column_title2      = "NODENAME";
   $opsys_column_title1         = " ";          $opsys_column_title2         = "OPSYS";
   $sessions_column_title1      = " ";          $sessions_column_title2      = "SESSIONS";
   $backup_obj_column_title1    = "BACKUP";     $backup_obj_column_title2    = "OBJECTS";
   $backup_kb_column_title1     = "BACKUP";     $backup_kb_column_title2     = "KB";
   $restore_obj_column_title1   = "RESTORE";    $restore_obj_column_title2   = "OBJECTS";
   $restore_kb_column_title1    = "RESTORE";    $restore_kb_column_title2    = "KB";
   $archive_obj_column_title1   = "ARCHIVE";    $archive_obj_column_title2   = "OBJECTS";
   $archive_kb_column_title1    = "ARCHIVE";    $archive_kb_column_title2    = "KB";
   $retrieve_obj_column_title1  = "RETRIEVE";   $retrieve_obj_column_title2  = "OBJECTS";
   $retrieve_kb_column_title1   = "RETRIEVE";   $retrieve_kb_column_title2   = "KB";
   $hsmstore_obj_column_title1  = "HSM-IN";     $hsmstore_obj_column_title2  = "OBJECTS";
   $hsmstore_kb_column_title1   = "HSM-IN";     $hsmstore_kb_column_title2   = "KB";
   $hsmrecall_obj_column_title1 = "HSM-OUT";    $hsmrecall_obj_column_title2 = "OBJECTS";
   $hsmrecall_kb_column_title1  = "HSM-OUT";    $hsmrecall_kb_column_title2  = "KB";
   $nodeobjs_column_title1      = " ";          $nodeobjs_column_title2      = "OBJECTS";
   $nodesesskb_column_title1    = "SESSION";    $nodesesskb_column_title2    = "KB";
   $nodedatakb_column_title1    = "DATA";       $nodedatakb_column_title2    = "KB";
   $sesssecs_column_title1      = "SESSION";    $sesssecs_column_title2      = "SECONDS";
   $idlesecs_column_title1      = "IDLEWAIT";   $idlesecs_column_title2      = "SECONDS";
   $commsecs_column_title1      = "COMMWAIT";   $commsecs_column_title2      = "SECONDS";
   $mediasecs_column_title1     = "MEDIAWAIT";  $mediasecs_column_title2     = "SECONDS";

   # For aligned reporting later, we will be keeping track of the maximum width of any column element, and
   # here start with the column titles, determining which of the over-under pair is larger.
   $maxlen_nodename       = ($l1 = length($nodename_column_title1))      > ($l2 = length($nodename_column_title2))      ? $l1 : $l2;
   $maxlen_opsyses        = ($l1 = length($opsys_column_title1))	 > ($l2 = length($opsys_column_title2))         ? $l1 : $l2;
   $maxlen_sessions       = ($l1 = length($sessions_column_title1))      > ($l2 = length($sessions_column_title2))      ? $l1 : $l2;
   $maxlen_backup_objs    = ($l1 = length($backup_obj_column_title1))    > ($l2 = length($backup_obj_column_title2))    ? $l1 : $l2;
   $maxlen_backup_kb      = ($l1 = length($backup_kb_column_title1))     > ($l2 = length($backup_kb_column_title2))     ? $l1 : $l2;
   $maxlen_restore_objs   = ($l1 = length($restore_obj_column_title1))   > ($l2 = length($restore_obj_column_title2))   ? $l1 : $l2;
   $maxlen_restore_kb     = ($l1 = length($restore_kb_column_title1))    > ($l2 = length($restore_kb_column_title2))    ? $l1 : $l2;
   $maxlen_archive_objs   = ($l1 = length($archive_obj_column_title1))   > ($l2 = length($archive_obj_column_title2))   ? $l1 : $l2;
   $maxlen_archive_kb     = ($l1 = length($archive_kb_column_title1))    > ($l2 = length($archive_kb_column_title2))    ? $l1 : $l2;
   $maxlen_retrieve_objs  = ($l1 = length($retrieve_obj_column_title1))  > ($l2 = length($retrieve_obj_column_title2))  ? $l1 : $l2;
   $maxlen_retrieve_kb    = ($l1 = length($retrieve_kb_column_title1))   > ($l2 = length($retrieve_kb_column_title2))   ? $l1 : $l2;
   $maxlen_hsmstore_objs  = ($l1 = length($hsmstore_obj_column_title1))  > ($l2 = length($hsmstore_obj_column_title2))  ? $l1 : $l2;
   $maxlen_hsmstore_kb    = ($l1 = length($hsmstore_kb_column_title1))   > ($l2 = length($hsmstore_kb_column_title2))   ? $l1 : $l2;
   $maxlen_hsmrecall_objs = ($l1 = length($hsmrecall_obj_column_title1)) > ($l2 = length($hsmrecall_obj_column_title2)) ? $l1 : $l2;
   $maxlen_hsmrecall_kb   = ($l1 = length($hsmrecall_kb_column_title1))  > ($l2 = length($hsmrecall_kb_column_title2))  ? $l1 : $l2;
   $maxlen_node_objs      = ($l1 = length($nodeobjs_column_title1))      > ($l2 = length($nodeobjs_column_title2))      ? $l1 : $l2;
   $maxlen_nodesess_kb    = ($l1 = length($nodesesskb_column_title1))    > ($l2 = length($nodesesskb_column_title2))    ? $l1 : $l2;
   $maxlen_nodedata_kb    = ($l1 = length($nodedatakb_column_title1))    > ($l2 = length($nodedatakb_column_title2))    ? $l1 : $l2;
   $maxlen_sesssecs       = ($l1 = length($sesssecs_column_title1))      > ($l2 = length($sesssecs_column_title2))      ? $l1 : $l2;
   $maxlen_idlesecs       = ($l1 = length($idlesecs_column_title1))      > ($l2 = length($idlesecs_column_title2))      ? $l1 : $l2;
   $maxlen_commsecs       = ($l1 = length($commsecs_column_title1))      > ($l2 = length($commsecs_column_title2))      ? $l1 : $l2;
   $maxlen_mediasecs      = ($l1 = length($mediasecs_column_title1))     > ($l2 = length($mediasecs_column_title2))     ? $l1 : $l2;

   #_____________________________________________Preliminaries______________________________________________
   # Before getting into time-consuming stuff which would be wasted if we could not proceed, perform some
   # dependency checks now.
   if ($first_time_through)
      {
      # Initial first and last year,month,day,hour,minute,second values, to detect extremes.
      $earliest_datestamp = 99999999999999;   $latest_datestamp = 0;
      # Initialize other min, max values:
      $queue_stay_time_min = 9999999;   $queue_stay_time_max = 0;
      $file_size_min = 9999999;   $file_size_max = 0;

      if (length($arg_mday) < 2)
	 { $arg_mday = " ".$arg_mday; }	# Account for leading blank which actually
					# appears in record, for grep to find it.

      $first_time_through = 0;	# Reset it.
      }

   #_______________________________Assimilate the accounting records________________________________________
   # Refer to the ADSM System Administration Guide for layout.
   # In general, the record contains 30 double-quoted entries, separated by two spaces.

   $current_time = time();		# For detecting bogus time values.
   printf("Current timestamp is '%s' (%s)\n",$current_time,&Sub_Secs_to_DateTime($current_time));

   print("Now assimilating ADSM accounting records...\n");
   %all_printers = ();		# Init. array null.
   $line_number = 0;		# To count the lines.
   open(ACCTFILE,"<$arg_file") || die "Unable to open ADSM accounting file '$arg_file'";
   while (<ACCTFILE>)
      {
      # The ADSM accounting file line will contain fields as specified below,
      # each separated by a comma.
      chomp($line = $_);		# Take the line and remove line-end \n.
      $line_number++;		# Count each line.
      #printf("'%s'\n",$line);  # Diagnostic.

      if ($debug)  { printf("%s\n", $line); }
      # Watch out for the data record version having changed from what this script
      # was programmed to handle:
      if ((substr($line,0,2) ne "5,")		# What ADSMv2 had.
	  &&  (substr($line,0,2) ne "3,"))	# What ADSMv3 has.
	 {
	 printf("ADSM accounting record %u does not begin with a recognized product level identifier\n"
		." indicating that the record layout version differs from what this script was\n"
		." programmed to handle.  Needs investigation.  Quitting.\n\a",
		$line_number);
	 exit(1);
	 }

      # Parse the record...
      ($product_level,			# Field  1: Product level
       $product_sublevel,		# Field  2: Product sublevel
       $product_name,			# Field  3: Product name, 'ADSM'
       $acctg_date,			# Field  4: Date of accounting (mm/dd/yyyy).  Has leading zeroes.
       $acctg_time,			# Field  5: Time of accounting (hh:mm:ss).  Has leading zeroes.
       $client_nodename,		# Field  6: Node name of ADSM client
       $client_owner,			# Field  7: Client owner name (Unix).  Often empty, but otherwise will contain a
					#	    Unix username.
       $client_platform,		# Field  8: Client platform (actually, opsys, like "AIX", "Linux").
       $auth_method,			# Field  9: Authentication method used
       $comm_method,			# Field 10: Communication method used for the session
       $server_termination,		# Field 11: Server termination indicator (0 = abnormal; 1 = normal)
       $archive_objs_inserted,		# Field 12: Number of archive database objects inserted during session
       $archive_kb_inserted,		# Field 13: Amount of archive files, in kilobytes, sent by the client to server
       $archive_objs_retrieved,		# Field 14: Number of archive database objects retrieved during session
       $archive_kb_retrieved,		# Field 15: Amount of archive files, in kilobytes, retrieved by the client
       $backup_objs_inserted,		# Field 16: Number of backup database objects inserted during session
       $backup_kb_inserted,		# Field 17: Amount of backup files, in kilobytes, sent by the client to server
       $backup_objs_retrieved,		# Field 18: Number of backup database objects retrieved during session
       $backup_kb_retrieved,		# Field 19: Amount of backup files, in kilobytes, retrieved by the client
       $session_kb,			# Field 20: Amount of data, in kilobytes, communicated between client and server.
					#           Includes both data volume and session management overhead.
       $session_secs,			# Field 21: Duration of the session, in seconds
       $idlewait_secs,			# Field 22: Amount of idle wait time during the session, in seconds
       $commwait_secs,			# Field 23: Amount of communications wait time during session, in seconds
       $mediawait_secs,			# Field 24: Amount of media wait time during session, in seconds
       $client_session_type,		# Field 25: Client session type. A value of 1 or 4 indicates a general client
					#           session; a value of 5 indicates a client session that is running a schedule.
       $hsm_objs_stored,		# Field 26: Number of space-managed database objects inserted during the session
       $hsm_kb_stored,			# Field 27: Amount of space-managed data, in kilobytes, sent by the client to the server
       $hsm_objs_recalled,		# Field 28: Number of space-managed database objects retrieved during the session
       $hsm_kb_recalled			# Field 29: Amount of space-managed data, in kilobytes, retrieved by space-managed
					#	    objects.
       ) = split(/,/,$line);
      if ($debug)	# Turn on to sample data values.
	 {
	 printf("\nLine %u:\n '%s'\n", $line_number, $line);
	 printf("Product level = '%s'; Product sublevel = '%s', Product name = '%s', Date of accounting = '%s',\n"
	       ." Time of accounting = '%s', Node name of ADSM client = '%s', Client owner name = '%s'\n"
	       ." Client platform = '%s', Authentication method used = '%s', Communication method used = '%s'\n"
	       ." Server termina = '%s', Archived objs = '%s', Archived kb = '%s', Retrieved objs = '%s', Retrieved kb = '%s'\n"
	       ." Backup objs = '%s', Backup kb = '%s', Restore objs = '%s', Restore kb = '%s'\n"
	       ." HSM store objs = '%s', HSM store kb = '%s', HSM recall objs = '%s', HSM recall = '%s'\n"
	       ." Session kb = '%s', Session secs = '%s', Idlewait secs = '%s', Commwait secs = '%s', Mediawait secs = '%s'\n",
		$product_level, $product_sublevel, $product_name, $acctg_date,
		$acctg_time, $client_nodename, $client_owner,
		$client_platform, $auth_method, $comm_method, $server_termination,
		$archive_objs_inserted, $archive_kb_inserted, $archive_objs_retrieved, $archive_kb_retrieved,
		$backup_objs_inserted, $backup_kb_inserted, $backup_objs_retrieved, $backup_kb_retrieved,
		$hsm_objs_stored, $hsm_kb_stored, $hsm_objs_recalled, $hsm_kb_recalled,
		$session_kb, $session_secs, $idlewait_secs, $commwait_secs, $mediawait_secs);
	 }

      #_______________________________________Do some field validation______________________________________________
      # The Product level field should be numeric:
      if ($product_level =~ /\D/)
	 {
	 printf("Record %u is bogus: 'Product level' value in line below is '%s', non-numeric. Skipping it.\n  '%s'\n",
		$line_number, $product_level, $line);
	 next;			# Skip to next record.
	 }

      #________________________________________Determine date range___________________________________________
      # Compare datestamp against oldest-latest values in determining overall date-time range of all the data.
      # (Remember, the data may be from several files, and there is no surity of time sequence.)
      ($mm, $dd, $yyyy) = split(m|/|,$acctg_date);   ($hr, $min, $sec) = split(m|:|,$acctg_time);
      $timestamp = $yyyy.$mm.$dd.$hr.$min.$sec;
      if ($timestamp < $earliest_datestamp)
	 {
	 $earliest_datestamp = $timestamp;
	 if ($debug)  { printf("New earliest_datestamp = %s, from '%s'.\n",
			      $earliest_datestamp, $acctg_date); }
	 }
      elsif ($timestamp > $latest_datestamp)
	 {
	 $latest_datestamp = $timestamp;
	 if ($debug)  { printf("New latest_datestamp = %s, from '%s'.\n", $latest_datestamp, $acctg_date); }
	 }

      #_____________________________See if the data meets the selection criteria________________________________
      if (($from_datestamp ne "Any_Time"  && $timestamp < $from_datestamp)
	  ||  ($to_datestamp ne "Any_Time"  &&  $timestamp > $to_datestamp))
	 {
	 if ($debug)  { printf("Skipping record datestamped %s (%s %s) - out of time range: %s.\n",
			      $timestamp, $acctg_date, $acctg_time,
			      $timestamp < $from_datestamp ? "Time too low ($timestamp < $from_datestamp)"
			      : $timestamp > $to_datestamp ? "Time too high ($timestamp > $to_datestamp)" : "??"); }
	 next;			# Skip to next record.
	 }
      # The record meets selection criteria.
      if ($debug)  { printf("Accepting record timestamped %s (%s %s) - within time range\n",
			   $timestamp, $acctg_date, $acctg_time); }

      #__________________________________Capture data from the record_______________________________________
      $total_sessions++;				# Total number of sessions of all types.
							# (Note that not all sessions involve data in ADSM
							# storage pools.)
      if ($session_obj = $backup_objs_inserted + $backup_objs_retrieved
	  + $archive_objs_inserted + $archive_objs_retrieved + $hsm_objs_stored + $hsm_objs_recalled)
	 { $total_sessions_transferring_data++; }	# Total number of sessions which transferred data;
							# that is, involved storage pools.
      if ($archive_objs_inserted  &&  ($backup_objs_inserted == 0)  &&  ($backup_objs_retrieved == 0)
	  &&  ($archive_objs_retrieved == 0)  &&  ($hsm_objs_stored == 0)  &&  ($hsm_objs_recalled == 0))
	 {
	 #___________________________________It was a pure Archive session_________________________________________
	 $archive_sessions_count++;
	 if ($mediawait_secs)
	    {
	    $total_archives_directly_to_tape++;		# Total number of Archive sessions whose data transfer went
							# directly to tape, skipping the archive disk storage pool.
	    if ($debug)  { print("Session is an Archive which went directly to tape.\n"); }
	    }
	 else
	    {
	    $total_archives_to_disk++;			# Total number of Archive sessions whose data transfer went
							# to the archive disk storage pool.
	    if ($debug)  { print("Session is an Archive which went to disk.\n"); }
	    }
	 }
      if ($archive_objs_retrieved  &&  ($backup_objs_inserted == 0)  &&  ($backup_objs_retrieved == 0)
	  &&  ($archive_objs_inserted == 0)  &&  ($hsm_objs_stored == 0)  &&  ($hsm_objs_recalled == 0))
	 {	
	 #___________________________________It was a pure Retrieve session_________________________________________
	 $retrieve_sessions_count++;
	 if ($mediawait_secs)
	    {
	    $total_retrieves_directly_from_tape++;	# Total number of Retrieve sessions whose data transfer came
							# directly from tape, skipping the archive disk storage pool.
	    if ($debug)  { print("Session is a Retrieve which came directly from tape.\n"); }
	    }
	 else
	    {
	    $total_retrieves_from_disk++;		# Total number of Retrieve sessions whose data transfer came
							# from the archive disk storage pool.
	    if ($debug)  { print("Session is a Retrieve which came from disk.\n"); }
	    }
	 }

      $data_kb = $backup_kb_inserted + $backup_kb_retrieved
	 + $archive_kb_inserted + $archive_kb_retrieved + $hsm_kb_stored + $hsm_kb_recalled;
      $sessions_by_opsys{$client_platform}++;		# Total number of sessions by opsys.
      $sesskb_by_opsys{$client_platform} += $session_kb;	# Total number of KB by client opsys.  Note that
								# this includes overhead communication as well as
								# storage pool data.
      $datakb_by_opsys{$client_platform} += $data_kb;		# Data KB by client opsys.
      $sessions_by_nodename{$client_nodename}++;		# Total number of sessions by node.
      $session_secs_by_nodename{$client_nodename} += $session_secs;
      $idlewait_secs_by_nodename{$client_nodename} += $idlewait_secs;
      $commwait_secs_by_nodename{$client_nodename} += $commwait_secs;
      $mediawait_secs_by_nodename{$client_nodename} += $mediawait_secs;
      $session_kb_by_nodename{$client_nodename} += $session_kb;	# Total number of KB by client nodename.  Note that
								# this includes overhead communication as well as
								# storage pool data.
      $data_kb_by_nodename{$client_nodename} += $data_kb;	# Data KB by client nodename.
      $sessions_per_day{"$yyyy"."$mm"."$dd"}++;
      $sesskb_per_day{"$yyyy"."$mm"."$dd"} += $session_kb;	# Note that this includes overhead communication
								# as well as storage pool data.
      $datakb_per_day{"$yyyy"."$mm"."$dd"} += $data_kb;		# 
      $obj_per_day{"$yyyy"."$mm"."$dd"} += $session_obj;
      # We need to reexamine the maximum lengths seen for text strings as they come in, unlike
      # numerical values whose sum determines maximum overall length.
      if (($l = length($client_nodename)) > $maxlen_nodename)    { $maxlen_nodename = $l; }
      if (($l = length($client_owner)) > $maxlen_username)    { $maxlen_username = $l; }
      $opsyses_by_nodename{$client_nodename} = $client_platform;
      if (($l = length($client_platform)) > $maxlen_opsyses)  { $maxlen_opsyses = $l; }

      # Capture values by nodename...
      $backup_obj_by_nodename{$client_nodename} += $backup_objs_inserted;
      $backup_kb_by_nodename{$client_nodename} += $backup_kb_inserted;
      $restored_obj_by_nodename{$client_nodename} += $backup_objs_retrieved;
      $restored_kb_by_nodename{$client_nodename} += $backup_kb_retrieved;

      $archived_obj_by_nodename{$client_nodename} += $archive_objs_inserted;
      $archived_kb_by_nodename{$client_nodename} += $archive_kb_inserted;
      $retrieved_obj_by_nodename{$client_nodename} += $archive_objs_retrieved;
      $retrieved_kb_by_nodename{$client_nodename} += $archive_kb_retrieved;

      $hsmstore_obj_by_nodename{$client_nodename} += $hsm_objs_stored;
      $hsmstore_kb_by_nodename{$client_nodename} += $hsm_kb_stored;
      $hsmrecall_obj_by_nodename{$client_nodename} += $hsm_objs_recalled;
      $hsmrecall_kb_by_nodename{$client_nodename} += $hsm_kb_recalled;

      # Capture values by username...
      if ($client_owner)				# Omitting null entries...
	 {
         # Note that HSM data transfer is implicit, so no username associated with it.

	 $sessions_by_username{$client_owner}++;	# Total number of sessions by user.
	 $obj_by_username{$client_owner} += $session_obj;		# Sum of all types of objects.
	 $session_kb_by_username{$client_owner} += $session_kb;		# Total number of KB by username.  Note
									# that this includes overhead communication
									# as well as storage pool data.
	 $data_kb_by_username{$client_owner} += $data_kb;		# Data KB by client username.
	 $session_secs_by_username{$client_owner} += $session_secs;
	 $idlewait_secs_by_username{$client_owner} += $idlewait_secs;
	 $commwait_secs_by_username{$client_owner} += $commwait_secs;
	 $mediawait_secs_by_username{$client_owner} += $mediawait_secs;

	 $backup_obj_by_username{$client_owner} += $backup_objs_inserted;
	 $backup_kb_by_username{$client_owner} += $backup_kb_inserted;
	 $restored_obj_by_username{$client_owner} += $backup_objs_retrieved;
	 $restored_kb_by_username{$client_owner} += $backup_kb_retrieved;

	 $archived_obj_by_username{$client_owner} += $archive_objs_inserted;
	 $archived_kb_by_username{$client_owner} += $archive_kb_inserted;
	 $retrieved_obj_by_username{$client_owner} += $archive_objs_retrieved;
	 $retrieved_kb_by_username{$client_owner} += $archive_kb_retrieved;
	
	 $hsmstore_obj_by_username{$client_owner} += $hsm_objs_stored;
	 $hsmstore_kb_by_username{$client_owner} += $hsm_kb_stored;
	 $hsmrecall_obj_by_username{$client_owner} += $hsm_objs_recalled;
	 $hsmrecall_kb_by_username{$client_owner} += $hsm_kb_recalled;
	 }

      # For column totals:
      if ($archive_objs_inserted)   { $total_archive_operations++; }
      if ($archive_objs_retrieved)  { $total_retrieve_operations++; }
      if ($backup_objs_inserted)    { $total_backup_operations++; }
      if ($backup_objs_retrieved)   { $total_restore_operations++; }
      if ($hsm_objs_stored)	    { $total_hsmstore_operations++; }
      if ($hsm_objs_recalled)	    { $total_hsmrecall_operations++; }

      $total_backup_obj += $backup_objs_inserted;       $total_backup_kb += $backup_kb_inserted;
      $total_restore_obj += $backup_objs_retrieved;     $total_restore_kb += $backup_kb_retrieved;
      $total_archive_obj += $archive_objs_inserted;     $total_archive_kb += $archive_kb_inserted;
      $total_retrieve_obj += $archive_objs_retrieved;   $total_retrieve_kb += $archive_kb_retrieved;
      $total_hsmstore_obj += $hsm_objs_stored;          $total_hsmstore_kb += $hsm_kb_stored;
      $total_hsmrecall_obj += $hsm_objs_recalled;       $total_hsmrecall_kb += $hsm_kb_recalled;
      $total_sess_kb += $session_kb;                    $total_data_kb += $data_kb;

      $obj_by_nodename{$client_nodename} += $session_obj;      # Sum of all types of objects.

      # Examine job completion status...
      if    ($server_termination == 0)  { $sessions_completed_abnormally++; }
      elsif ($server_termination == 1)  { $sessions_completed_normally++; }

      }  # bottom of processing each record
   # Done absorbing all the data from this source.
   close(ACCTFILE);
   return $line_number;
   }
# End of subroutine Absorb_Data_Source


#=================================  Subroutine Summarize_Data  =====================================
sub Summarize_Data
#
# Subroutine to summarized all absorbed data to output file.
#
# INVOCATION:  $? = &Summarize_Data();
#
# RETURNS:  0
#
   {

   #____________________________________Preliminaries_____________________________________

   #_________________Save any pre-existing output report file______________________
   if (-e $report_filename)
      {
      printf("Output file '%s' already exists - will rename to preserve it...\n",
	     $report_filename);
      if (-x "/usr/local/bin/bkurfile $report_filename")  { # Our command to rename the file with a .YYYYMMDD suffix.
							    system("/usr/local/bin/bkurfile $report_filename"); }
      else  { system("/bin/mv $report_filename ${report_filename}.bak"); }
      }

   #___________________________________Normalize any overall data___________________________________________
   # We should convert the date back to slash- and colon-separated form, for reporting:
   if ($debug)  { printf("Earliest datestamp = %s; latest datestamp = %s.\n", $earliest_datestamp, $latest_datestamp); }
   ($yyyy, $mm, $dd, $hr, $min, $sec) = $earliest_datestamp =~ m|(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)|;
   $first_date = "$yyyy/$mm/$dd $hr:$min:$sec";
   ($yyyy, $mm, $dd, $hr, $min, $sec) = $latest_datestamp =~ m|(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)|;
   $last_date = "$yyyy/$mm/$dd $hr:$min:$sec";
   printf("\nData ranges from  %s  to  %s\n", $first_date, $last_date);

   #_____________________________Figure final numerical column widths based upon totals_______________________________
   # Format the numbers so as to base widths upon those constructs...
   $total_sessions_f      = &Format_Number($total_sessions);
   $total_archive_obj_f   = &Format_Number($total_archive_obj);
   $total_archive_kb_f    = &Format_Number($total_archive_kb);
   $total_retrieve_obj_f  = &Format_Number($total_retrieve_obj);
   $total_retrieve_kb_f   = &Format_Number($total_retrieve_kb);
   $total_backup_obj_f    = &Format_Number($total_backup_obj);
   $total_backup_kb_f     = &Format_Number($total_backup_kb);
   $total_restore_obj_f   = &Format_Number($total_restore_obj);
   $total_restore_kb_f    = &Format_Number($total_restore_kb);
   $total_hsmstore_obj_f  = &Format_Number($total_hsmstore_obj);
   $total_hsmstore_kb_f   = &Format_Number($total_hsmstore_kb);
   $total_hsmrecall_obj_f = &Format_Number($total_hsmrecall_obj);
   $total_hsmrecall_kb_f  = &Format_Number($total_hsmrecall_kb);
   $total_sess_kb_f       = &Format_Number($total_sess_kb);
   $total_data_kb_f       = &Format_Number($total_data_kb);
   # Remember to put numbers into double quotes when getting their length, else you get 0.
   if (($l = length("$total_sessions_f"))      > $maxlen_sessions)        { $maxlen_sessions       = $l; }
   if (($l = length("$total_backup_obj_f"))    > $maxlen_backup_objs)     { $maxlen_backup_objs    = $l; }
   if (($l = length("$total_backup_kb_f"))     > $maxlen_backup_kb)       { $maxlen_backup_kb      = $l; }
   if (($l = length("$total_restore_obj_f"))   > $maxlen_restore_objs)    { $maxlen_restore_objs   = $l; }
   if (($l = length("$total_restore_kb_f"))    > $maxlen_restore_kb)      { $maxlen_restore_kb     = $l; }
   if (($l = length("$total_archive_obj_f"))   > $maxlen_archive_objs)    { $maxlen_archive_objs   = $l; }
   if (($l = length("$total_archive_kb_f"))    > $maxlen_archive_kb)      { $maxlen_archive_kb     = $l; }
   if (($l = length("$total_retrieve_obj_f"))  > $maxlen_retrieve_objs)   { $maxlen_retrieve_objs  = $l; }
   if (($l = length("$total_retrieve_kb_f"))   > $maxlen_retrieve_kb)     { $maxlen_retrieve_kb    = $l; }
   if (($l = length("$total_hsmstore_obj_f"))  > $maxlen_hsmstore_objs)   { $maxlen_hsmstore_objs  = $l; }
   if (($l = length("$total_hsmstore_kb_f"))   > $maxlen_hsmstore_kb)     { $maxlen_hsmstore_kb    = $l; }
   if (($l = length("$total_hsmrecall_obj_f")) > $maxlen_hsmrecall_objs)  { $maxlen_hsmrecall_objs = $l; }
   if (($l = length("$total_hsmrecall_kb_f"))  > $maxlen_hsmrecall_kb)    { $maxlen_hsmrecall_kb   = $l; }
   if (($l = length("$total_sess_kb_f"))       > $maxlen_nodesess_kb)     { $maxlen_nodesess_kb    = $l; }
   if (($l = length("$total_data_kb_f"))       > $maxlen_nodedata_kb)     { $maxlen_nodedata_kb    = $l; }
   # Accommodate the sums of the right-hand columns in determining the max width of those columns:
   $total_obj = $total_backup_obj + $total_restore_obj + $total_archive_obj + $total_retrieve_obj
      + $total_hsmstore_obj + $total_hsmrecall_obj;
   $total_obj_f = &Format_Number($total_obj);
   if (($l = length("$total_obj_f"))  > $maxlen_node_objs)   { $maxlen_node_objs = $l; }
   $total_data_kb = $total_backup_kb + $total_restore_kb + $total_archive_kb + $total_retrieve_kb
      + $total_hsmstore_kb + $total_hsmrecall_kb;
   if (($l = length("$total_sesssecs"))    > $maxlen_sesssecs)    { $maxlen_sesssecs = $l; }
   if (($l = length("$total_idlesecs"))    > $maxlen_idlesecs)    { $maxlen_idlesecs = $l; }
   if (($l = length("$total_commsecs"))    > $maxlen_commsecs)    { $maxlen_commsecs = $l; }
   if (($l = length("$total_mediasecs"))   > $maxlen_mediasecs)   { $maxlen_mediasecs = $l; }

   #______________________________________Report ADSM sessions - part 1____________________________________________
   printf("\nNow creating report file '%s'...\n", $report_filename);
   open(REPORTOUT, ">$report_filename") || die "Unable to open $report_filename.";

   # Define printing characteristics for this report (the report format):
   # Note that Format Variables are set on a per-filehandle basis, so 'select' has to be used to set them...
   $save_filehandle = select(REPORTOUT);
   $= = 9999;				# Number of lines per page.  (I originally had this number '60' for
					# conventional reporting; but whereas we only view these reports online,
					# having the column headers repeatedly appear in a terminal window of
					# arbitrary size was pointless, so boosted the number high to have the
					# column headers appear only once for each report section.
   $- = 0;				# Set number of lines remaining on page 0 to cause initial headers.
   $^ = REPORTOUT_TOP;			# Report format for top-of-page.
   $~ = REPORTOUT;			# Report format for body.
   select($save_filehandle);		# Restore.
   $report_title .= sprintf("ADSM SESSIONS, FROM %s TO %s\n", $from_datestamp_text, $to_datestamp_text);
   $report_subtitle = "in accounting data ranging from $first_date through $last_date";
   if ($debug)  { printf("\$= lines per page = %d; \$- lines remaining on page = %d\n", $=, $-); }

   #__________Assign column headers
   # Assign values to the column header variables in the report formats.
   $report_colhdrs1  = sprintf("%-${maxlen_nodename}s"
			      ."  %${maxlen_backup_objs}s  %${maxlen_backup_kb}s"
			      ."  %${maxlen_restore_objs}s  %${maxlen_restore_kb}s"
			      ."  %${maxlen_archive_objs}s  %${maxlen_archive_kb}s"
			      ."  %${maxlen_retrieve_objs}s  %${maxlen_retrieve_kb}s"
			      ."  %${maxlen_hsmstore_objs}s  %${maxlen_hsmstore_kb}s"
			      ."  %${maxlen_hsmrecall_objs}s  %${maxlen_hsmrecall_kb}s",
			       $nodename_column_title1,
			       $backup_obj_column_title1, $backup_kb_column_title1,
			       $restore_obj_column_title1, $restore_kb_column_title1,
			       $archive_obj_column_title1, $archive_kb_column_title1,
			       $retrieve_obj_column_title1, $retrieve_kb_column_title1,
			       $hsmstore_obj_column_title1, $hsmstore_kb_column_title1,
			       $hsmrecall_obj_column_title1, $hsmrecall_kb_column_title1);
   $report_colhdrs2  = sprintf("%-${maxlen_nodename}s"
			      ."  %${maxlen_backup_objs}s  %${maxlen_backup_kb}s"
			      ."  %${maxlen_restore_objs}s  %${maxlen_restore_kb}s"
			      ."  %${maxlen_archive_objs}s  %${maxlen_archive_kb}s"
			      ."  %${maxlen_retrieve_objs}s  %${maxlen_retrieve_kb}s"
			      ."  %${maxlen_hsmstore_objs}s  %${maxlen_hsmstore_kb}s"
			      ."  %${maxlen_hsmrecall_objs}s  %${maxlen_hsmrecall_kb}s",
			       $nodename_column_title2,
			       $backup_obj_column_title2, $backup_kb_column_title2,
			       $restore_obj_column_title2, $restore_kb_column_title2,
			       $archive_obj_column_title2, $archive_kb_column_title2,
			       $retrieve_obj_column_title2, $retrieve_kb_column_title2,
			       $hsmstore_obj_column_title2, $hsmstore_kb_column_title2,
			       $hsmrecall_obj_column_title2, $hsmrecall_kb_column_title2);
   $report_colhdruls = sprintf("%s  %s  %s  %s  %s  %s  %s  %s  %s  %s  %s  %s  %s",
			       '-'x${maxlen_nodename},
			       '-'x${maxlen_backup_objs}, '-'x${maxlen_backup_kb},
			       '-'x${maxlen_restore_objs}, '-'x${maxlen_restore_kb},
			       '-'x${maxlen_archive_objs}, '-'x${maxlen_archive_kb},
			       '-'x${maxlen_retrieve_objs}, '-'x${maxlen_retrieve_kb},
			       '-'x${maxlen_hsmstore_objs}, '-'x${maxlen_hsmstore_kb},
			       '-'x${maxlen_hsmrecall_objs}, '-'x${maxlen_hsmrecall_kb});
   # The column headers are now set, and will print implicitly as we fill pages.

   #__________Report each node's usage
   foreach $nodename (sort keys(%sessions_by_nodename))
      {
      # Fill in each column, separated from each preceding column by two spaces...
      $report_line = sprintf("%-${maxlen_nodename}s"
			    ."  %${maxlen_backup_objs}s  %${maxlen_backup_kb}s"
                            ."  %${maxlen_restore_objs}s  %${maxlen_restore_kb}s"
                            ."  %${maxlen_archive_objs}s  %${maxlen_archive_kb}s"
                            ."  %${maxlen_retrieve_objs}s  %${maxlen_retrieve_kb}s"
			    ."  %${maxlen_hsmstore_objs}s  %${maxlen_hsmstore_kb}s"
			    ."  %${maxlen_hsmrecall_objs}s  %${maxlen_hsmrecall_kb}s",
			     $nodename,
			     &Format_Number($backup_obj_by_nodename{$nodename}),
			     &Format_Number($backup_kb_by_nodename{$nodename}),
			     &Format_Number($restored_obj_by_nodename{$nodename}),
			     &Format_Number($restored_kb_by_nodename{$nodename}),
			     &Format_Number($archived_obj_by_nodename{$nodename}),
			     &Format_Number($archived_kb_by_nodename{$nodename}),
			     &Format_Number($retrieved_obj_by_nodename{$nodename}),
			     &Format_Number($retrieved_kb_by_nodename{$nodename}),
			     &Format_Number($hsmstore_obj_by_nodename{$nodename}),
			     &Format_Number($hsmstore_kb_by_nodename{$nodename}),
			     &Format_Number($hsmrecall_obj_by_nodename{$nodename}),
			     &Format_Number($hsmrecall_kb_by_nodename{$nodename}));

      # Finally, write the line out:
      write(REPORTOUT);
      }
   # Bottom of reporting each node's usage.

   #__________Report column totals:
   # The initial spacer here must equal the number of characters reserved for the username.
   $report_line = $report_colhdruls;  # Underline each column to indicate summation.
   write(REPORTOUT);
   $report_line = sprintf("%-${maxlen_nodename}s"
			 ."  %${maxlen_backup_objs}s  %${maxlen_backup_kb}s"
                         ."  %${maxlen_restore_objs}s  %${maxlen_restore_kb}s"
                         ."  %${maxlen_archive_objs}s  %${maxlen_archive_kb}s"
                         ."  %${maxlen_retrieve_objs}s  %${maxlen_retrieve_kb}s"
			 ."  %${maxlen_hsmstore_objs}s  %${maxlen_hsmstore_kb}s"
			 ."  %${maxlen_hsmrecall_objs}s  %${maxlen_hsmrecall_kb}s",
			  "TOTALS",
			  $total_backup_obj_f, $total_backup_kb_f, $total_restore_obj_f, $total_restore_kb_f,
			  $total_archive_obj_f, $total_archive_kb_f, $total_retrieve_obj_f, $total_retrieve_kb_f,
			  $total_hsmstore_obj_f, $total_hsmstore_kb_f, $total_hsmrecall_obj_f, $total_hsmrecall_kb_f);
   write(REPORTOUT);

   # Reinterpret large values, to make visually clear:
   # (Remember that the file size values are already in KB, so are 1024x.)
   $report_line = sprintf("%-${maxlen_nodename}s"
			 ."  %${maxlen_backup_objs}s  %${maxlen_backup_kb}s"
                         ."  %${maxlen_restore_objs}s  %${maxlen_restore_kb}s"
                         ."  %${maxlen_archive_objs}s  %${maxlen_archive_kb}s"
                         ."  %${maxlen_retrieve_objs}s  %${maxlen_retrieve_kb}s"
			 ."  %${maxlen_hsmstore_objs}s  %${maxlen_hsmstore_kb}s"
			 ."  %${maxlen_hsmrecall_objs}s  %${maxlen_hsmrecall_kb}s",
			  "", "",
			  $total_backup_kb > $MB ? sprintf("%.1f GB", $total_backup_kb / $MB) :
			   $total_backup_kb > $KB ? sprintf("%.1f MB", $total_backup_kb / $KB) : $total_backup_kb." KB",
			  "",
			  $total_restore_kb > $MB ? sprintf("%.1f GB", $total_restore_kb / $MB) :
			   $total_restore_kb > $KB ? sprintf("%.1f MB", $total_restore_kb / $KB) : $total_restore_kb." KB",
			  "",
			  $total_archive_kb > $MB ? sprintf("%.1f GB", $total_archive_kb / $MB) :
			   $total_archive_kb > $KB ? sprintf("%.1f MB", $total_archive_kb / $KB) : $total_archive_kb." KB",
			  "",
			  $total_retrieve_kb > $MB ? sprintf("%.1f GB", $total_retrieve_kb / $MB) :
			   $total_retrieve_kb > $KB ? sprintf("%.1f MB", $total_retrieve_kb / $KB) : $total_retrieve_kb." KB",
			  "",
			  $total_hsmstore_kb > $MB ? sprintf("%.1f GB", $total_hsmstore_kb / $MB) :
			   $total_hsmstore_kb > $KB ? sprintf("%.1f MB", $total_hsmstore_kb / $KB) : $total_hsmstore_kb." KB",
			  "",
			  $total_hsmrecall_kb > $MB ? sprintf("%.1f GB", $total_hsmrecall_kb / $MB) :
			   $total_hsmrecall_kb > $KB ? sprintf("%.1f MB", $total_hsmrecall_kb / $KB) : $total_hsmrecall_kb." KB"
			  );
   write(REPORTOUT);
   $report_line_len = length($report_line);	# Reference width for later summary stats to observe.

   #______________________________________Report ADSM sessions - part 2____________________________________________
   $report_line = " ";   write(REPORTOUT);	# Spacer after previous report.

   #__________Assign column headers
   # Assign values to the column header variables in the report formats.
   $report_colhdrs1  = sprintf("%-${maxlen_nodename}s  %${maxlen_opsyses}s  %${maxlen_sessions}s"
			      ."  %${maxlen_node_objs}s  %${maxlen_nodesess_kb}s  %${maxlen_nodedata_kb}s"
			      ."  %${maxlen_sesssecs}s  %${maxlen_idlesecs}s  %${maxlen_commsecs}s  %${maxlen_mediasecs}s",
			       $nodename_column_title1, $opsys_column_title1, $sessions_column_title1,
			       $nodeobjs_column_title1, $nodesesskb_column_title1, $nodedatakb_column_title1,
			       $sesssecs_column_title1, $idlesecs_column_title1,
			       $commsecs_column_title1, $mediasecs_column_title1);
   $report_line = $report_colhdrs1;   write(REPORTOUT);

   $report_colhdrs2  = sprintf("%-${maxlen_nodename}s  %${maxlen_opsyses}s  %${maxlen_sessions}s"
			      ."  %${maxlen_node_objs}s  %${maxlen_nodesess_kb}s  %${maxlen_nodedata_kb}s"
			      ."  %${maxlen_sesssecs}s  %${maxlen_idlesecs}s  %${maxlen_commsecs}s  %${maxlen_mediasecs}s",
			       $nodename_column_title2, $opsys_column_title2, $sessions_column_title2,
			       $nodeobjs_column_title2, $nodesesskb_column_title2, $nodedatakb_column_title2,
			       $sesssecs_column_title2, $idlesecs_column_title2,
			       $commsecs_column_title2, $mediasecs_column_title2);
   $report_line = $report_colhdrs2;   write(REPORTOUT);

   $report_colhdruls = sprintf("%s  %s  %s  %s  %s  %s  %s  %s  %s  %s",
			       '-'x${maxlen_nodename}, '-'x${maxlen_opsyses}, '-'x${maxlen_sessions},
			       '-'x${maxlen_node_objs}, '-'x${maxlen_nodesess_kb}, '-'x${maxlen_nodedata_kb},
			       '-'x${maxlen_sesssecs}, '-'x${maxlen_idlesecs}, '-'x${maxlen_commsecs}, '-'x${maxlen_mediasecs});
   $report_line = $report_colhdruls;   write(REPORTOUT);

   # The column headers are now set, and will print implicitly as we fill pages.

   #__________Report each node's usage
   foreach $nodename (sort keys(%sessions_by_nodename))
      {
      # Fill in each column, separated from each preceding column by two spaces...
      $report_line = sprintf("%-${maxlen_nodename}s  %-${maxlen_opsyses}s  %${maxlen_sessions}u"
			    ."  %${maxlen_node_objs}s  %${maxlen_nodesess_kb}s  %${maxlen_nodedata_kb}s"
			    ."  %${maxlen_sesssecs}s  %${maxlen_idlesecs}s  %${maxlen_commsecs}s  %${maxlen_mediasecs}s",
			     $nodename, $opsyses_by_nodename{$nodename}, $sessions_by_nodename{$nodename},
			     &Format_Number($obj_by_nodename{$nodename}), &Format_Number($session_kb_by_nodename{$nodename}),
			     &Format_Number($data_kb_by_nodename{$nodename}),
			     $session_secs_by_nodename{$nodename}, $idlewait_secs_by_nodename{$nodename},
			     $commwait_secs_by_nodename{$nodename}, $mediawait_secs_by_nodename{$nodename});

      # Finally, write the line out:
      write(REPORTOUT);
      }
   # Bottom of reporting each node's usage.

   #__________Report column totals:
   # The initial spacer here must equal the number of characters reserved for the username.
   $report_line = $report_colhdruls;  # Underline each column to indicate summation.
   write(REPORTOUT);
   $report_line = sprintf("%-${maxlen_nodename}s  %${maxlen_opsyses}s  %${maxlen_sessions}s"
			 ."  %${maxlen_node_objs}s  %${maxlen_nodesess_kb}s  %${maxlen_nodesess_kb}s",
			  "TOTALS", "", $total_sessions_f, $total_obj_f, $total_sess_kb_f, $total_data_kb_f);
   write(REPORTOUT);

   # Reinterpret large values, to make visually clear:
   # (Remember that the file size values are already in KB, so are 1024x.)
   $report_line = sprintf("%-${maxlen_nodename}s  %${maxlen_opsyses}s  %${maxlen_sessions}s"
			 ."  %${maxlen_node_objs}s  %${maxlen_nodesess_kb}s  %${maxlen_nodedata_kb}s",
			  "", "", "", "",
			  $total_data_kb > $MB ? sprintf("%.1f GB", $total_data_kb / $MB) :
			   $total_data_kb > $KB ? sprintf("%.1f MB", $total_data_kb / $KB) : $total_data_kb." KB",
			  $total_sess_kb > $MB ? sprintf("%.1f GB", $total_sess_kb / $MB) :
			   $total_sess_kb > $KB ? sprintf("%.1f MB", $total_sess_kb / $KB) : $total_sess_kb." KB"
			  );
   write(REPORTOUT);

   #__________Some explanatory notes:
   # Nullify column headers that were used above, to prevent the following notes from inappropriately appearing under
   # column headers in a report page eject.
   #if ($debug)  { printf("\$= lines per page = %d; \$- lines remaining on page = %d\n", $=, $-); }
   $report_colhdrs1  = "";   $report_colhdrs2 = "";   $report_colhdruls = "";

   $report_line = " ";   write(REPORTOUT);   $report_line = " ";   write(REPORTOUT);
   $report_line = "NOTES:";   write(REPORTOUT);
   $report_line = " ";   write(REPORTOUT);
   $report_line = "Idle Wait time is the time that the server is waiting for the client to respond.";
   write(REPORTOUT);
   $report_line = " This is often seen in Backup operations, where the server has send the client a full list of the";
   write(REPORTOUT);
   $report_line = " files that it has for the filespace being backed up, and the client is examining them against its";
   write(REPORTOUT);
   $report_line = " file system content to see what is eligible for backup today.";
   write(REPORTOUT);
   $report_line = "Communications Wait time is the time that the client is waiting for the server to respond.";
   write(REPORTOUT);
   $report_line = "Media Wait time is the time spent awaiting completion of a tape mount, either because all drives";
   write(REPORTOUT);
   $report_line = " are in use or because the desired volume is in use.  (When collocation is by node, multiple sessions";
   write(REPORTOUT);
   $report_line = " from one client node will want to write to the same output volume, and so secondary sessions will";
   write(REPORTOUT);
   $report_line = " wait for the first session's use of it to end.)";
   write(REPORTOUT);


   #__________________________________________________Present summary statistics________________________________________________
   printf(REPORTOUT "\fSUMMARY STATISTICS:\n\n");

   printf(REPORTOUT "Total number of sessions = %u;  Number involving data in ADSM storage pools = %u.\n",
	  $total_sessions, $total_sessions_transferring_data);

   @sorted_nodenames = sort(keys(%sessions_by_nodename));   $nodes_count = scalar(@sorted_nodenames);
   @sorted_usernames = sort(keys(%sessions_by_username));   $usernames_count = scalar(@sorted_usernames);
   @sorted_sessions_by_nodename = sort ascending(values(%sessions_by_nodename));
   @sorted_opsyses = sort(keys(%sessions_by_opsys));   $opsyses_count = scalar(@sorted_opsyses);
   @sorted_sessions_by_opsys = sort ascending(values(%sessions_by_opsys));
   @sorted_session_kb_by_nodename = sort ascending(values(%session_kb_by_nodename));
   @sorted_data_kb_by_nodename = sort ascending(values(%data_kb_by_nodename));
   @sorted_sesskb_by_opsys = sort ascending(values(%sesskb_by_opsys));
   @sorted_datakb_by_opsys = sort ascending(values(%datakb_by_opsys));
   @sorted_sesskb_per_day = sort ascending(values(%sesskb_per_day));
   @sorted_datakb_per_day = sort ascending(values(%datakb_per_day));
   @sorted_obj_per_day = sort ascending(values(%obj_per_day));
   @sorted_sessions_per_day = sort ascending(values(%sessions_per_day));

   # Report node-based statistics:
   if ($nodes_count)
      {
      print(REPORTOUT "\n");
      $t = sprintf(" %u Nodes active during this period:", $nodes_count);   $lt = length("$t");
      print(REPORTOUT $t);			# Indent the list.
      $l = $lt;
      foreach $element (@sorted_nodenames)
         {
         if (($l += ($maxlen_nodename + 1)) > $report_line_len)
	    { printf(REPORTOUT "\n%${lt}s", " ");   $l = $lt + length($element) + 1; }
         printf(REPORTOUT " %-${maxlen_nodename}s", $element);
         }
      print(REPORTOUT "\n");
      }
   $n = scalar(@sorted_sessions_by_nodename);   $rmdr = $n % 2;
   if ($rmdr)  { $median = @sorted_sessions_by_nodename[($n/2)]; }
   else        { $median = @sorted_sessions_by_nodename[(($n/2)-1)]; }
   printf(REPORTOUT " Average sessions/node = %.2f; median = %d\n", $total_sessions / $nodes_count, $median);

   # Report user-based statistics:
   if ($usernames_count)
      {
      print(REPORTOUT "\n");
      $t = " Usernames active during this period:";   $lt = length("$t");
      print(REPORTOUT $t);			# Indent the list.
      $l = $lt;
      foreach $element (@sorted_usernames)
         {
         if (($l += ($maxlen_username + 1)) > $report_line_len)
	    { printf(REPORTOUT "\n%${lt}s", " ");   $l = $lt + length($element) + 1; }
         printf(REPORTOUT " %-${maxlen_username}s", $element);
         }
      print(REPORTOUT "\n");
      }
 
   # Report opsys-based statistics:
   printf(REPORTOUT "\n %u Operating systems active during this period: %s\n", $opsyses_count, "@sorted_opsyses");
   $n = scalar(@sorted_sessions_by_opsys);   $rmdr = $n % 2;
   if ($rmdr)  { $median = @sorted_sessions_by_opsys[($n/2)]; }
   else        { $median = @sorted_sessions_by_opsys[(($n/2)-1)]; }
   printf(REPORTOUT " Average sessions/opsys = %s; median = %s\n",
          &Format_Number(sprintf("%.2f",($total_sessions / $opsyses_count))), &Format_Number($median));

   # Report KB-based statistics, by overall session communication volume:
   print(REPORTOUT "\n");
   $n = scalar(@sorted_session_kb_by_nodename);   $rmdr = $n % 2;
   if ($rmdr)  { $median = @sorted_session_kb_by_nodename[($n/2)]; }
   else        { $median = @sorted_session_kb_by_nodename[(($n/2)-1)]; }
   $avg_session_kb_by_nodename = sprintf("%.2f", $total_sess_kb / $nodes_count);
   $maxlen_kb_f = $maxlen_nodesess_kb + 3;	# Width of largest KB values plus ".nn" decimal.
   printf(REPORTOUT " Average session KB/nodename = %${maxlen_kb_f}s (%9s); median = %${maxlen_nodesess_kb}s (%9s)\n",
          &Format_Number($avg_session_kb_by_nodename),
	  $avg_session_kb_by_nodename > $MB ? sprintf("%.1f GB", $avg_session_kb_by_nodename / $MB) :
	  $avg_session_kb_by_nodename > $KB ? sprintf("%.1f MB", $avg_session_kb_by_nodename / $KB) : $avg_session_kb_by_nodename." KB",
	  &Format_Number($median),
	  $median > $MB ? sprintf("%.1f GB", $median / $MB) :
	  $median > $KB ? sprintf("%.1f MB", $median / $KB) : $median." KB"
	  );

   # Report KB-based statistics, by volume of data transmitted:
   $n = scalar(@sorted_data_kb_by_nodename);   $rmdr = $n % 2;
   if ($rmdr)  { $median = @sorted_data_kb_by_nodename[($n/2)]; }
   else        { $median = @sorted_data_kb_by_nodename[(($n/2)-1)]; }
   $avg_data_kb_by_nodename = sprintf("%.2f", $total_data_kb / $nodes_count);
   printf(REPORTOUT " Average data    KB/nodename = %${maxlen_kb_f}s (%9s); median = %${maxlen_nodesess_kb}s (%9s)\n",
          &Format_Number($avg_data_kb_by_nodename),
	  $avg_data_kb_by_nodename > $MB ? sprintf("%.1f GB", $avg_data_kb_by_nodename / $MB) :
	  $avg_data_kb_by_nodename > $KB ? sprintf("%.1f MB", $avg_data_kb_by_nodename / $KB) : $avg_data_kb_by_nodename." KB",
	  &Format_Number($median),
	  $median > $MB ? sprintf("%.1f GB", $median / $MB) :
	  $median > $KB ? sprintf("%.1f MB", $median / $KB) : $median." KB"
	  );

   $n = scalar(@sorted_sesskb_by_opsys);   $rmdr = $n % 2;
   if ($rmdr)  { $median = @sorted_sesskb_by_opsys[($n/2)]; }
   else        { $median = @sorted_sesskb_by_opsys[(($n/2)-1)]; }
   $avg_sesskb_by_opsys = sprintf("%.2f", $total_sess_kb / $opsyses_count);
   printf(REPORTOUT " Average session KB/opsys    = %${maxlen_kb_f}s (%9s); median = %${maxlen_nodesess_kb}s (%9s)\n",
          &Format_Number($avg_sesskb_by_opsys),
	  $avg_sesskb_by_opsys > $MB ? sprintf("%.1f GB", $avg_sesskb_by_opsys / $MB) :
	  $avg_sesskb_by_opsys > $KB ? sprintf("%.1f MB", $avg_sesskb_by_opsys / $KB) : $avg_sesskb_by_opsys." KB",
	  &Format_Number($median),
	  $median > $MB ? sprintf("%.1f GB", $median / $MB) :
	  $median > $KB ? sprintf("%.1f MB", $median / $KB) : $median." KB"
	  );

   $n = scalar(@sorted_datakb_by_opsys);   $rmdr = $n % 2;
   if ($rmdr)  { $median = @sorted_datakb_by_opsys[($n/2)]; }
   else        { $median = @sorted_datakb_by_opsys[(($n/2)-1)]; }
   $avg_datakb_by_opsys = sprintf("%.2f", $total_data_kb / $opsyses_count);
   printf(REPORTOUT " Average data    KB/opsys    = %${maxlen_kb_f}s (%9s); median = %${maxlen_nodesess_kb}s (%9s)\n",
          &Format_Number($avg_datakb_by_opsys),
	  $avg_datakb_by_opsys > $MB ? sprintf("%.1f GB", $avg_datakb_by_opsys / $MB) :
	  $avg_datakb_by_opsys > $KB ? sprintf("%.1f MB", $avg_datakb_by_opsys / $KB) : $avg_datakb_by_opsys." KB",
	  &Format_Number($median),
	  $median > $MB ? sprintf("%.1f GB", $median / $MB) :
	  $median > $KB ? sprintf("%.1f MB", $median / $KB) : $median." KB"
	  );

   print(REPORTOUT "\n");	# Separate from prior collective.
   printf(REPORTOUT " Number of sessions completed: normally = %s; abnormally = %s\n",
	  &Format_Number($sessions_completed_normally), &Format_Number($sessions_completed_abnormally));

   if ($archive_sessions_count > $retrieve_sessions_count)  { $l = length("$archive_sessions_count"); }
   else							    { $l = length("$retrieve_sessions_count"); }
   printf(REPORTOUT " Number of Archive  sessions which went:  to   disk = %${l}u;  directly to   tape = %${l}u\n",
	  $total_archives_to_disk, $total_archives_directly_to_tape);
   printf(REPORTOUT " Number of Retrieve sessions which came:  from disk = %${l}u;  directly from tape = %${l}u\n",
	  $total_retrieves_from_disk, $total_retrieves_directly_from_tape);

   #______________________________________________By-day reports_________________________________________________________
   # Begin on a separate page.  For column widths we will use the $maxlen_nodesess_kb value, which is the largest
   # of all previously reported, and works best here.
   printf(REPORTOUT "BY-DAY REPORTS\n\n");
   printf(REPORTOUT "%-10s  %${maxlen_sessions}s     %${maxlen_node_objs}s              %${maxlen_nodesess_kb}s"
	  ."                %${maxlen_nodesess_kb}s\n\n",
	  "DAY", "SESSIONS", "OBJECTS", "SESSION KB", "DATA KB");

   $l_sess = $maxlen_sessions + 3;	# Integer plus .2f .
   $l_objs = $maxlen_node_objs + 3;	# Integer plus .2f .
   $l_kb   = $maxlen_nodesess_kb + 3;	# Integer plus .2f .
   # Note that all the *_per_day arrays have the same keys, so our choice to control this loop is arbitrary.
   foreach $day (sort(keys(%sessions_per_day)))
      {
      # Remember that the day key is of form YYYYMMDD.
      printf(REPORTOUT "%-10s  %${maxlen_sessions}s     %${maxlen_node_objs}s      %${maxlen_nodesess_kb}s    (%s)      %${maxlen_nodesess_kb}s    (%s)\n",
	     substr($day,0,4)."/".substr($day,4,2)."/".substr($day,6,2),
	     &Format_Number($sessions_per_day{$day}), &Format_Number($obj_per_day{$day}),
             &Format_Number($sesskb_per_day{$day}),
	      $sesskb_per_day{$day} > $MB ? sprintf("%5.1f GB", $sesskb_per_day{$day} / $MB) :
	       $sesskb_per_day{$day} > $KB ? sprintf("%5.1f MB", $sesskb_per_day{$day} / $KB) : sprintf("%5.1f KB", $sesskb_per_day{$day}),
	     &Format_Number($datakb_per_day{$day}),
	      $datakb_per_day{$day} > $MB ? sprintf("%5.1f GB", $datakb_per_day{$day} / $MB) :
	       $datakb_per_day{$day} > $KB ? sprintf("%5.1f MB", $datakb_per_day{$day} / $KB) : sprintf("%5.1f KB", $datakb_per_day{$day}));
      }

   $n = scalar(@sorted_sessions_per_day);   $rmdr = $n % 2;
   if ($rmdr)  { $median_sessions_per_day = @sorted_sessions_per_day[($n/2)]; }
   else        { $median_sessions_per_day = @sorted_sessions_per_day[(($n/2)-1)]; }
   $average_sessions_per_day = sprintf("%.2f", $total_sessions / $n);

   $n = scalar(@sorted_obj_per_day);   $rmdr = $n % 2;
   if ($rmdr)  { $median_obj_per_day = @sorted_obj_per_day[($n/2)]; }
   else        { $median_obj_per_day = @sorted_obj_per_day[(($n/2)-1)]; }
   $average_obj_per_day = sprintf("%.2f", $total_obj / $n);

   $n = scalar(@sorted_sesskb_per_day);   $rmdr = $n % 2;
   if ($rmdr)  { $median_sesskb_per_day = @sorted_sesskb_per_day[($n/2)]; }
   else        { $median_sesskb_per_day = @sorted_sesskb_per_day[(($n/2)-1)]; }
   $average_sesskb_per_day = sprintf("%.2f", $total_sess_kb / $n);

   $n = scalar(@sorted_datakb_per_day);   $rmdr = $n % 2;
   if ($rmdr)  { $median_datakb_per_day = @sorted_datakb_per_day[($n/2)]; }
   else        { $median_datakb_per_day = @sorted_datakb_per_day[(($n/2)-1)]; }
   $average_datakb_per_day = sprintf("%.2f", $total_data_kb / $n);

#  printf(REPORTOUT "\n%-10s  %${maxlen_sessions}s  %${maxlen_node_objs}s      %${maxlen_nodesess_kb}s    (%s)      %${maxlen_nodesess_kb}s    (%s)\n",
#  printf(REPORTOUT "\n%-10s     %${l_sess}s  %${l_objs}s      %${l_kb}s    (%s)      %${l_kb}s    (%s)\n",
   printf(REPORTOUT "\n%-10s  %${maxlen_sessions}s     %${maxlen_node_objs}s      %${maxlen_nodesess_kb}s    (%s)      %${maxlen_nodesess_kb}s    (%s)\n",
	  "Median", &Format_Number($median_sessions_per_day), &Format_Number($median_obj_per_day),
          &Format_Number($median_sesskb_per_day),
	  $median_sesskb_per_day > $MB ? sprintf("%5.1f GB", $median_sesskb_per_day / $MB) :
	   $median_sesskb_per_day > $KB ? sprintf("%5.1f MB", $median_sesskb_per_day / $KB) :
	   sprintf("%5.1f KB", $median_sesskb_per_day),
	  &Format_Number($median_datakb_per_day),
	  $median_datakb_per_day > $MB ? sprintf("%5.1f GB", $median_datakb_per_day / $MB) :
	   $median_datakb_per_day > $KB ? sprintf("%5.1f MB", $median_datakb_per_day / $KB) :
	   sprintf("%5.1f KB", $median_datakb_per_day));

   printf(REPORTOUT "\n%-10s     %${maxlen_sessions}s  %${l_objs}s   %${l_kb}s (%s)      %${l_kb}s (%s)\n",
	  "Average", &Format_Number($average_sessions_per_day), &Format_Number($average_obj_per_day),
          &Format_Number($average_sesskb_per_day),
	  $average_sesskb_per_day > $MB ? sprintf("%5.1f GB", $average_sesskb_per_day / $MB) :
	   $average_sesskb_per_day > $KB ? sprintf("%5.1f MB", $average_sesskb_per_day / $KB) :
	   sprintf("%5.1f KB", $average_sesskb_per_day),
	  &Format_Number($average_datakb_per_day),
	  $average_datakb_per_day > $MB ? sprintf("%5.1f GB", $average_datakb_per_day / $MB) :
	   $average_datakb_per_day > $KB ? sprintf("%5.1f MB", $average_datakb_per_day / $KB) :
	   sprintf("%5.1f KB", $average_datakb_per_day));

   # End of by-day report.

   #__________________________________________By-user reports_____________________________________________________
   # Begin on a separate page.  For column widths we will use the $maxlen_nodesess_kb value, which is the largest
   # of all previously reported, and works best here.
   # Note that HSM data transfer is implicit, so no username associated with it.
   printf(REPORTOUT "BY-USER REPORTS\n\n");

   #__________Assign column headers
   # Assign values to the column header variables in the report formats.
   $report_colhdrs1  = sprintf("%-8s"
			      ."  %${maxlen_backup_objs}s  %${maxlen_backup_kb}s"
			      ."  %${maxlen_restore_objs}s  %${maxlen_restore_kb}s"
			      ."  %${maxlen_archive_objs}s  %${maxlen_archive_kb}s"
			      ."  %${maxlen_retrieve_objs}s  %${maxlen_retrieve_kb}s"
			      ."  %${maxlen_hsmstore_objs}s  %${maxlen_hsmstore_kb}s"
			      ."  %${maxlen_hsmrecall_objs}s  %${maxlen_hsmrecall_kb}s",
			       "",
			       $backup_obj_column_title1, $backup_kb_column_title1,
			       $restore_obj_column_title1, $restore_kb_column_title1,
			       $archive_obj_column_title1, $archive_kb_column_title1,
			       $retrieve_obj_column_title1, $retrieve_kb_column_title1,
			       $hsmstore_obj_column_title1, $hsmstore_kb_column_title1,
			       $hsmrecall_obj_column_title1, $hsmrecall_kb_column_title1);
   $report_line = $report_colhdrs1;   write(REPORTOUT);
   $report_colhdrs2  = sprintf("%-8s"
			      ."  %${maxlen_backup_objs}s  %${maxlen_backup_kb}s"
			      ."  %${maxlen_restore_objs}s  %${maxlen_restore_kb}s"
			      ."  %${maxlen_archive_objs}s  %${maxlen_archive_kb}s"
			      ."  %${maxlen_retrieve_objs}s  %${maxlen_retrieve_kb}s"
			      ."  %${maxlen_hsmstore_objs}s  %${maxlen_hsmstore_kb}s"
			      ."  %${maxlen_hsmrecall_objs}s  %${maxlen_hsmrecall_kb}s",
			       "USERNAME",
			       $backup_obj_column_title2, $backup_kb_column_title2,
			       $restore_obj_column_title2, $restore_kb_column_title2,
			       $archive_obj_column_title2, $archive_kb_column_title2,
			       $retrieve_obj_column_title2, $retrieve_kb_column_title2,
			       $hsmstore_obj_column_title2, $hsmstore_kb_column_title2,
			       $hsmrecall_obj_column_title2, $hsmrecall_kb_column_title2);
   $report_line = $report_colhdrs2;   write(REPORTOUT);
   $report_colhdruls = sprintf("%s  %s  %s  %s  %s  %s  %s  %s  %s  %s  %s  %s  %s  %s  %s  %s  %s",
			       '-'x${maxlen_nodename},
			       '-'x${maxlen_backup_objs}, '-'x${maxlen_backup_kb},
			       '-'x${maxlen_restore_objs}, '-'x${maxlen_restore_kb},
			       '-'x${maxlen_archive_objs}, '-'x${maxlen_archive_kb},
			       '-'x${maxlen_retrieve_objs}, '-'x${maxlen_retrieve_kb},
			       '-'x${maxlen_hsmstore_objs}, '-'x${maxlen_hsmstore_kb},
			       '-'x${maxlen_hsmrecall_objs}, '-'x${maxlen_hsmrecall_kb});
   $report_line = $report_colhdruls;   write(REPORTOUT);
   # The column headers are now set, and will print implicitly as we fill pages.

   #__________Report each user's usage
   foreach $username (sort keys(%sessions_by_username))
      {
      # Fill in each column, separated from each preceding column by two spaces...
      $report_line = sprintf("%-8s"
			    ."  %${maxlen_backup_objs}s  %${maxlen_backup_kb}s"
                            ."  %${maxlen_restore_objs}s  %${maxlen_restore_kb}s"
                            ."  %${maxlen_archive_objs}s  %${maxlen_archive_kb}s"
                            ."  %${maxlen_retrieve_objs}s  %${maxlen_retrieve_kb}s"
			    ."  %${maxlen_hsmstore_objs}s  %${maxlen_hsmstore_kb}s"
			    ."  %${maxlen_hsmrecall_objs}s  %${maxlen_hsmrecall_kb}s",
			     $username,
			     &Format_Number($backup_obj_by_username{$username}),
			     &Format_Number($backup_kb_by_username{$username}),
			     &Format_Number($restored_obj_by_username{$username}),
			     &Format_Number($restored_kb_by_username{$username}),
			     &Format_Number($archived_obj_by_username{$username}),
			     &Format_Number($archived_kb_by_username{$username}),
			     &Format_Number($retrieved_obj_by_username{$username}),
			     &Format_Number($retrieved_kb_by_username{$username}),
			     &Format_Number($hsmstore_obj_by_username{$username}),
			     &Format_Number($hsmstore_kb_by_username{$username}),
			     &Format_Number($hsmrecall_obj_by_username{$username}),
			     &Format_Number($hsmrecall_kb_by_username{$username}) );
      # Finally, write the line out:
      write(REPORTOUT);
      }
   # Bottom of reporting each user's usage.

   #______________________________________Report user sessions - part 2____________________________________________
   $report_line = " ";   write(REPORTOUT);	# Spacer after previous report.

   #__________Assign column headers
   # Assign values to the column header variables in the report formats.
   $report_colhdrs1  = sprintf("%-8s  %${maxlen_sessions}s"
			      ."  %${maxlen_node_objs}s  %${maxlen_nodesess_kb}s  %${maxlen_nodedata_kb}s"
			      ."  %${maxlen_sesssecs}s  %${maxlen_idlesecs}s  %${maxlen_commsecs}s  %${maxlen_mediasecs}s",
			       "", $sessions_column_title1,
			       $nodeobjs_column_title1, $nodesesskb_column_title1, $nodedatakb_column_title1,
			       $sesssecs_column_title1, $idlesecs_column_title1,
			       $commsecs_column_title1, $mediasecs_column_title1);
   $report_line = $report_colhdrs1;   write(REPORTOUT);

   $report_colhdrs2  = sprintf("%-8s  %${maxlen_sessions}s"
			      ."  %${maxlen_node_objs}s  %${maxlen_nodesess_kb}s  %${maxlen_nodedata_kb}s"
			      ."  %${maxlen_sesssecs}s  %${maxlen_idlesecs}s  %${maxlen_commsecs}s  %${maxlen_mediasecs}s",
			       "USERNAME", $sessions_column_title2,
			       $nodeobjs_column_title2, $nodesesskb_column_title2, $nodedatakb_column_title2,
			       $sesssecs_column_title2, $idlesecs_column_title2,
			       $commsecs_column_title2, $mediasecs_column_title2);
   $report_line = $report_colhdrs2;   write(REPORTOUT);

   $report_colhdruls = sprintf("%s  %s  %s  %s  %s  %s  %s  %s  %s",
			       '-' x 8, '-'x${maxlen_sessions},
			       '-'x${maxlen_node_objs}, '-'x${maxlen_nodesess_kb}, '-'x${maxlen_nodedata_kb},
			       '-'x${maxlen_sesssecs}, '-'x${maxlen_idlesecs}, '-'x${maxlen_commsecs}, '-'x${maxlen_mediasecs});
   $report_line = $report_colhdruls;   write(REPORTOUT);

   # The column headers are now set, and will print implicitly as we fill pages.

   #__________Report each user's usage
   foreach $username (sort keys(%sessions_by_username))
      {
      # Fill in each column, separated from each preceding column by two spaces...
      $report_line = sprintf("%-8s  %${maxlen_sessions}u"
			    ."  %${maxlen_node_objs}s  %${maxlen_nodesess_kb}s  %${maxlen_nodedata_kb}s"
			    ."  %${maxlen_sesssecs}s  %${maxlen_idlesecs}s  %${maxlen_commsecs}s  %${maxlen_mediasecs}s",
			     $username, $sessions_by_username{$username},
			     &Format_Number($obj_by_username{$username}), &Format_Number($session_kb_by_username{$username}),
			     &Format_Number($data_kb_by_username{$username}),
			     $session_secs_by_username{$username}, $idlewait_secs_by_username{$username},
			     $commwait_secs_by_username{$username}, $mediawait_secs_by_username{$username});

      # Finally, write the line out:
      write(REPORTOUT);
      }

   # End of by-user report.
   }
# End of subroutine Summarize_Data


#=======================  Subroutine Sub_Secs_to_DateTime  ===========================
sub Sub_Secs_to_DateTime
# Subroutine to convert seconds since 1970 into a date-time value in the form:
#			YYYY/MM/DD (DAY_OF_WEEK) HH:MM:SS
# INVOCATION:  $? = &Sub_Secs_to_DateTime(SECS_SINCE_1970)
   {
   # Associative array for translating month name to a number.
   %mon_to_num = ( "Jan","01", "Feb","02", "Mar","03", "Apr","04", "May","05", "Jun","06",
                   "Jul","07", "Aug","08", "Sep","09", "Oct","10", "Nov","11", "Dec","12");

   $secs_value = $_[0];
   # To get time in human-readable string like the date command does, use the
   # Perl Library ctime() function (rather than localtime()).
   # Sample of returned value:  Mon Jun  7  9:53:16 EDT 1993
   require "ctime.pl";
   ($ct_wkday,$ct_mon,$ct_mday,$ct_time,$ct_tz,$ct_yyyy) =
    split(' ',&ctime($secs_value));
   # Compensate for Perl defect in responding with year 2069 if fed value 0:
   if (($secs_value == 0)  &&  ($ct_yyyy == 2069))  { $ct_yyyy = 1969; }
   @ctime_output = &ctime($secs_value);   # print("ctime output: ","@ctime_output","\n");
   # Some systems return a timezone name ("EST") before the year, and some don't,
   # so if year is null, it's in the timezone field.
   if ($ct_year eq "")  { $ct_year = $ct_tz; }
   # The month comes back as an alphabetic value (e.g., "Jul"), so has to be
   # translated to a 2-digit number for our purposes.
   $ct_mm = $mon_to_num{$ct_mon};
   # We want the day-of-month number to be two digits (add leading zero if
   # necessary).
   $ct_dd = sprintf("%02d",$ct_mday);
   # We want the time value's hour to have a leading blank if less than 10,
   # to have report values line up.
   $ct_time = sprintf("%08s",$ct_time);
   return $ct_yyyy."/".$ct_mm."/".$ct_dd." (".$ct_wkday.") ".$ct_time;
   }  # end of Sub_Secs_to_DateTime


#=======================  Subroutine Secs_to_YYYYMMDDhhmmss  ===========================
sub Secs_to_YYYYMMDDhhmmss
# Subroutine to convert seconds since 1970 into a date-time value in the form:
#			YYYYMMDDhhmmss
# which is suitable for sorting and relative comparison.
#
# INVOCATION:  $? = &Secs_to_YYYYMMDDhhmmss(SECS_SINCE_1970)
#
   {
   $secs_value = $_[0];
   ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($secs_value);
   return ($year+1900).sprintf("%02d",$mon+1).sprintf("%02d",$mday).sprintf("%02d",$hour)
         .sprintf("%02d",$min).sprintf("%02d",$sec);
   }  # end of Secs_to_YYYYMMDDhhmmss


#=======================  Subroutine Validate_Filename  ===========================
sub Validate_Filename
#
# Subroutine to validate a given file name.
#
# INVOCATION:  $? = &Validate_Filename(FILE_NAME)
#
# RETURNS:  1  if the file name exists;
#           0  if not, or no file name supplied.  An error message will have been
#              produced.
#
   {
   local($arg_filename) = scalar($_[0]);	# Grab the given file name.
   if (! -f $arg_filename)
      {
      # The file does not exist as a regular file.  Say what gives.
      printf("File name '%s' ", $arg_filename);   # Beginning of message; finish below.
      # Check for specifics before generalities for more helpful reporting:
      if (-d $arg_filename)
	 { printf("is a directory, not a file.\n"); }
      elsif (-l $arg_filename)
	 { printf("is a symbolic link without a target.\n"); }
      elsif (! -e $arg_filename)
	 { printf("does not exist.\n"); }
      else
	 { printf("is not a regular file.\n"); }
      return(0);		# Return failure indication.
      }
   return(1);			# Return success indication.
   }  # end of Validate_Filename subroutine


#######################################  PRINTING FORMATS  #####################################

# For the top of the report page:
#  - There will be a major title (centered), followed by a sub title (centered), followed by a blank line.
#  - The width of each column space before "@*" must equal space reserved for user name.
#  - The "|||||" sequence below defines the width of the top titles, in which their text will be centered.
#  - The period in column 1 marks the end of the "format".

format REPORTOUT_TOP =
@|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
$report_title
@|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
$report_subtitle

@*
$report_colhdrs1
@*
$report_colhdrs2
@*
$report_colhdruls
.

# Use angle brackets rather than pound signs in the following format to avoid having
# zeroes in unused columns.
format REPORTOUT =
@*
$report_line
.


#===================================  Subroutine Format_Number  ========================================
sub Format_Number
#
# Subroutine to format a number into comma-separated form, for easier visual absorption.
# Examples:  12345		--->		12,345
#	     12345.99876	--->		12,345.99876
#
# ENVIRONMENT:  Any
#
# INVOCATION:  $formatted_number = &Format_Number(number);
#
#	where:  Number	Is an integer or decimal number
#
# RETURNS:  Success:  Formatted number.
#	    Failure:  Empty string.
#
# HISTORY:
#
#     1999/06/13  Created.  Richard Sims
   {
   #_______________________________Define local and other variables____________________________________
   # Remember that local variables are known only within their enclosing block.
   #local(@args) = split(/\s+/,"@_");	# Secure the args from the volatile @_ array variable.
					# Note that the args come in as a space-separates string rather
   local(@args) = @_;			# Secure the args from the volatile @_ array variable.
   local($func_name) = "Format_Number";
   local($args_count) = scalar(@args);	# Number of supplied arguments.
   local($arg_number);   local($decimal);   local($number_left);   local($formatted_number);

   #________________________________Validate arguments____________________________________
   if ($args_count > 1)  { return ""; }
   if ($args[0] !~ m|^\d*\.*\d*$|)  { return ""; }
   $arg_number = $args[0];

   #________________________________Format the number____________________________________
   for ($number_left = $arg_number; ; )
      {
      if ($number_left =~ m|(\d*)(\d{3})(\.*\d*)$|)
	 {
	 if ($formatted_number)  { $formatted_number = "$2,$formatted_number"; }
	 else  { $formatted_number = "$2"; }
	 if ($3)  { $decimal = $3; }	# One-time capture of decimal portion.
	 #printf("x = '%s'; decimal = '%s'\n", $formatted_number, $decimal);  # Debugging.
	 $number_left = $1;
	 }
      elsif ($number_left =~ m|(\d*)(\.*\d*)$|)
	 {
	 if ($formatted_number)
	    {
	    if ($1)  { $formatted_number = "$1,$formatted_number"; }
	    }
	 else  { $formatted_number = "$1"; }
	 if ($2)  { $decimal = $2; }	# One-time capture of decimal portion.
	 #printf("x = '%s'; decimal = '%s'\n", $formatted_number, $decimal);  # Debugging.
	 last;
	 }
      }
   if ($formatted_number eq "")		# If got something like ".99", want "0.99" returned.
      { $formatted_number = "0"; }
   if ($decimal)		# If any decimal portion, reattach it to the result...
      { $formatted_number .= "$decimal"; }
   return $formatted_number;
   }  # bottom of Format_Number


#=============================  Subroutine Is_Leapyear  ==================================
#
# Boolean subroutine to determine if the specified year is a leap year.
#
# A leapyear is, of course, a year which is divisible by 4, and a centenary
# divisible by 400.  (So the year 2000 is a leapyear, but 1900 and 2100 are not.)
#
# INVOCATION:  if (&Is_Leapyear(Year)) {...}
#
# RETURNS:  1  if the given year is a leap year
#	    0  if the given year is not a leap year
#
# HISTORY:
#
#     1997/02/05  Written by Richard Sims
#========================================================================================
sub Is_Leapyear
   {
   local($arg_year) = scalar($_[0]);	# Grab the given year number.

   # A year which is not divisible by 4 cannot be a leap year...
   if ($arg_year % 4)  { return(0); }

   # The year is divisible by 4 so the year given is potentially a leapyear.
   # Now see if it is a centenary; and if so, is evenly divisible by 400.
   if ($arg_year % 100)  { return(1); }	# It is a leap year not at the end of a century.

   # The year is evenly divisible by 100 so is the turn of a century.
   # If also evenly divisible by 400, then it is a centenary leap year.
   if ($arg_year % 400)  { return(0); }
   else  { return(1); }
   }  # end of Is_Leapyear


#=======================  Subroutine Sub_Secs_to_DateTime  ===========================
sub Sub_Secs_to_DateTime
# Subroutine to convert seconds since 1970 into a date-time value in the form:
#			YYYY/MM/DD (DAY_OF_WEEK) HH:MM:SS
# INVOCATION:  $? = &Sub_Secs_to_DateTime(SECS_SINCE_1970)
   {
   # Associative array for translating month name to a number.
   %mon_to_num = ( "Jan","01", "Feb","02", "Mar","03", "Apr","04", "May","05", "Jun","06",
                   "Jul","07", "Aug","08", "Sep","09", "Oct","10", "Nov","11", "Dec","12");

   $secs_value = $_[0];
   # To get time in human-readable string like the date command does, use the
   # Perl Library ctime() function (rather than localtime()).
   # Sample of returned value:  Mon Jun  7  9:53:16 EDT 1993
   require "ctime.pl";
   ($ct_wkday,$ct_mon,$ct_mday,$ct_time,$ct_tz,$ct_yyyy) =
    split(' ',&ctime($secs_value));
   # Compensate for Perl defect in responding with year 2069 if fed value 0:
   if (($secs_value == 0)  &&  ($ct_yyyy == 2069))  { $ct_yyyy = 1969; }
   @ctime_output = &ctime($secs_value);   # print("ctime output: ","@ctime_output","\n");
   # Some systems return a timezone name ("EST") before the year, and some don't,
   # so if year is null, it's in the timezone field.
   if ($ct_year eq "")  { $ct_year = $ct_tz; }
   # The month comes back as an alphabetic value (e.g., "Jul"), so has to be
   # translated to a 2-digit number for our purposes.
   $ct_mm = $mon_to_num{$ct_mon};
   # We want the day-of-month number to be two digits (add leading zero if
   # necessary).
   $ct_dd = sprintf("%02d",$ct_mday);
   # We want the time value's hour to have a leading blank if less than 10,
   # to have report values line up.
   $ct_time = sprintf("%08s",$ct_time);
   return $ct_yyyy."/".$ct_mm."/".$ct_dd." (".$ct_wkday.") ".$ct_time;
   }  # end of Sub_Secs_to_DateTime



#=======================  Subroutine Secs_to_YYYYMMDDhhmmss  ===========================
sub Secs_to_YYYYMMDDhhmmss
# Subroutine to convert seconds since 1970 into a date-time value in the form:
#			YYYYMMDDhhmmss
# which is suitable for sorting and relative comparison.
#
# INVOCATION:  $? = &Secs_to_YYYYMMDDhhmmss(SECS_SINCE_1970)
#
   {
   $secs_value = $_[0];
   ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($secs_value);
   return ($year+1900).sprintf("%02d",$mon+1).sprintf("%02d",$mday).sprintf("%02d",$hour)
         .sprintf("%02d",$min).sprintf("%02d",$sec);
   }  # end of Secs_to_YYYYMMDDhhmmss


#=======================  Subroutine Show_Usage_From_Prolog  ============================
#
# Subroutine to show program usage from prolog comments.
#
# Read the invocation information from the head of this script, as contained
# within a hanging-indented section lead off by one of the following:
#	"USAGE:", "Usage:", "SYNTAX", "Syntax", "INVOCATION", "Invocation"
# and ending with some following line which has text in the same position where
# that lead-off keyword begins.
#
# Each line from that help section will be output with a blank replacing the leading "#"
# so to avoid misaligning text arranged with tabs in the prolog.
#
# INVOCATION:  &Show_Usage_From_Prolog();
#
# RETURNS:  0
#========================================================================================
sub Show_Usage_From_Prolog
   {
   @help_keywords = ("USAGE:", "Usage:", "SYNTAX", "Syntax", "INVOCATION", "Invocation");
   open(HELP,$0) || die "Help unable to open file '$0'.";
   $in_help_section = 0;	# Will be set once we encounter the help section.
   print("\n");			# Visual spacer before help info.
   while (<HELP>)		# Read this file continuously (should not get to end).
      {
      $line = $_;		# Next line from this file.
      # Convert tabs to proper number of positioning spaces so as to not throw off finding
      # position of character to stop on.
      while (($index = index($line,"\t")) > -1)	 { $line =~ s|\t|' ' x (8 - ($index % 8))|e; }

      if ($in_help_section)
	 {
	 #_______________________Watch for end of help section_______________________
	 # Look for any text in the same column position as the keyword which started
	 # off this help section, which signals the end of the help section.
	 if (substr($line,$left_pos,1) =~ m|\S|)
	    { close(HELP);   return(0); }
	 else
	    { printf(" %s", substr($line,1)); }
	 }
      else
	 {
	 #______________Not in the help section yet - seek its keyword_______________
	 foreach $help_keyword (@help_keywords)
	    {
	    if (grep(/$help_keyword/,$line))
	       {
	       $left_pos = index($line,$help_keyword);   $in_help_section = 1;
	       printf(" %s", substr($line,1));
	       }
	    }
	 }
      }
   return(0);
   }  # end of Show_Usage_From_Prolog subroutine