Video Screencast Help
Symantec to Separate Into Two Focused, Industry-Leading Technology Companies. Learn more.
Backup and Recovery Community Blog

NetBackup Scheduling Timelines - Part 2

Created: 19 Oct 2011
AlanTLR's picture
0 0 Votes
Login to vote

Last time, we looked at how to create our drive usage using dummy data.  Now, we need to get some real data.  To start with, I've installed OpsCenter and I created a custom report based on the Tabular Backup Report:

I customized the report by clicking on "Edit Report" and changing the filters so I only have the policies and clients that I know use drives.  I set the range to be the past 60 days.

Once done, I saved the report to a custom report in my Public Reports folder, so I can access it easily whenever I want to generate the data.  To export the data, I click on the export button and export it to a CSV.  I can then open the CSV and remove the first and last lines (headers and footer) of the CSV.  We're now ready to process it.

Now that we have the real data, there's an obvious problem: our time isn't in military time.  There's also a not-so-obvious problem: our dates have commas in them and we've made our commas be our separator.  If we just split on the commas, we'll get some extra fields.

First, let's take care of the comma problem.  All the commas are within double quotes.  If we split the line by double quotes, the even-numbered fields will be the values between double quotes.  We can operate on each of those to remove the commas, then recreate the line without the commas inside the quotes.  The code looks something like this:

 sub remove_commas_from_fields
{
  my $LINE=$_[0];
  my $RETURNLINE="";
  my $LINDEX=2;
  my @SLINE=split ("\"",$LINE);
  for ($LINDEX=1;$LINDEX<$#SLINE;$LINDEX+=2)
  {
    $SLINE[$LINDEX] =~ s/,//g;
  }

  foreach $LINE (@SLINE)
  {
    $RETURNLINE .= "$LINE";
  }
  return $RETURNLINE;
}  

 Note that this also removes all commas from the numeric values (730,520,210
becomes 730520210) and removes all double quotes.  We then add this
preprocessor function to our main program, just after grabbing the current
line:

   #  We grab the first line of the file and extract the values.
  $CURR_LINE=<FD>;
  $CURR_LINE=remove_commas_from_fields($CURR_LINE);
...
      $CURR_LINE=<FD>;
      $CURR_LINE=remove_commas_from_fields($CURR_LINE);
      ($DATA_START, $DATA_END, $DATA_CLIENT, $DATA_POLICY) = split(',',$CURR_LINE); 

Now that takes care of the first problem.  Let's take a look at the second:
time is in the wrong format.  I have two options.  I can either rewrite my
round_to_incr program to adjust for the change or I can make the change before
calling the round_to_incr.  The first is fairly simple, but causes me to have
to rewrite a subroutine I already know is working well.  Within this
subroutine, I'll also lose any date data, as the subroutine only accounts for
time.  The second will allow me to preserve the integrity of the round_to_incr subroutine and I can save the full data by moving it to another field.  Thus, I write another subroutine to rewrite the date/time field:

 sub convert_time
{
  my $HHMM=$_[0];
  my @HHMMARRAY = split('[ :]',$HHMM);
  my $INDEX0 = $#HHMMARRAY;
  my $INDEX1 = $#HHMMARRAY - 1;
  my $INDEX2 = $#HHMMARRAY - 2;
  my $AMPM = $HHMMARRAY[$INDEX0];
  my $MIN = $HHMMARRAY[$INDEX1];
  my $HOUR = $HHMMARRAY[$INDEX2];

  if ( $AMPM eq "PM")
  {
    $HOUR += 12;
  }
  my $RETURNVAL = "$HOUR" . "$MIN";
}

sub convert_date
{
  my $LINE=$_[0];
  my @SITEM;
  my $RETURNLINE="";
  my $LITEM;
  my @SDATE;

  my @SLINE=split(",",$LINE);

  my @SDATE = split(" ",$SLINE[0]);
  push(@SLINE,"$SDATE[0] $SDATE[1] $SDATE[2]");   # Preserve the first field; append it to the list.
  my @SDATE = split(" ",$SLINE[1]);
  push(@SLINE,"$SDATE[0] $SDATE[1] $SDATE[2]");   # Preserve the second field; append it to the list.

  $SLINE[0] = convert_time($SLINE[0]);
  $SLINE[1] = convert_time($SLINE[1]);

  foreach $LITEM (@SLINE)
  {
    $RETURNLINE .= "$LITEM,";
  }
  return $RETURNLINE;
} 

Note that I also save the date field in "Month Day Year" format.  This will
allow me to add the date to the output line:

 [...]
  #  We grab the first line of the file and extract the values.
  $CURR_LINE=<FD>;
  $CURR_LINE=remove_commas_from_fields($CURR_LINE);
  $CURR_LINE=convert_date($CURR_LINE);
  ($DATA_START, $DATA_END, $DATA_CLIENT, $DATA_POLICY) =
split(',',$CURR_LINE);
   $DATA_START = round_to_incr($DATA_START);              # added 2010-07-02
   $DATA_END = round_to_incr($DATA_END);              # added 2010-07-02
   $DATA_STARTDATE = (split(',',$CURR_LINE))[18];
[...]
    #  Second, we need to check if our data line's start time matches
    # the current time.
    #  If it is, we claim a drive, and go to the next line.
    #  If it isn't, we print the output and increment the time.
    if ( $TIME == $DATA_START )
    {
      $NEXT=nextempty;
      $DRIVE[$NEXT] = $DATA_CLIENT;
      $DRIVE_START[$NEXT] = $DATA_START;
      $DRIVE_END[$NEXT] = $DATA_END;
      $CURR_LINE=<FD>;
      $CURR_LINE=remove_commas_from_fields($CURR_LINE);
      $CURR_LINE=convert_date($CURR_LINE);
      ($DATA_START, $DATA_END, $DATA_CLIENT, $DATA_POLICY) =
split(',',$CURR_LINE);
      $DATA_START = round_to_incr($DATA_START);              # added
2010-07-02
      $DATA_END = round_to_incr($DATA_END);              # added 2010-07-02
      $DATA_STARTDATE = (split(',',$CURR_LINE))[18];
[...] 

Making the above changes, our final program looks like this:

But we can't just run this against our export.  Remember: we still have some
parent policies and policies that didn't back up.  We want to skip these.  And
looking at the data, it looks like we have some "Image Cleanup" policies
running that didn't use a drive.  I can write a quick awk command to only grab
the lines that backed up data and put them into a new CSV file:

 $ head -10 OpsCenter_Use_With_Generate_Drive_Usage_17_10_2011_03_13_PM.csv
"Aug 18, 2011 3:22 PM","Aug 18, 2011 3:30 PM",helga,Linux_OS,0,0,48432,-,Differential Incremental,snert,snert,Backup,1, ,0,Successful,0,
"Aug 18, 2011 3:22 PM","Aug 18, 2011 3:30 PM",helga,Linux_OS,"73,661",281,48433,-,Differential Incremental,snert,snert,Backup,1,Daily_Incremental,90.094,Successful,0,
"Aug 18, 2011 6:00 PM","Aug 18, 2011 6:04 PM",hamlet,Linux_OS,0,0,48434,-,Differential Incremental,snert,snert,Backup,1, ,0,Successful,0,
"Aug 18, 2011 6:00 PM","Aug 18, 2011 6:09 PM",helga,Linux_OS,0,0,48440,-,Differential Incremental,snert,snert,Backup,1, ,0,Successful,0,
"Aug 18, 2011 6:00 PM","Aug 18, 2011 6:04 PM",hagar,Linux_OS,0,0,48436,-,Differential Incremental,snert,snert,Backup,1, ,0,Successful,0,
"Aug 18, 2011 6:00 PM","Aug 18, 2011 6:05 PM",honi,Linux_OS,0,0,48439,-,Differential Incremental,snert,snert,Backup,1, ,0,Partially Successful,1,
"Aug 18, 2011 6:00 PM","Aug 18, 2011 6:04 PM",luckyeddie,Linux_OS,0,0,48435,-,Differential Incremental,snert,snert,Backup,1, ,0,Successful,0,
"Aug 18, 2011 6:00 PM","Aug 18, 2011 6:04 PM",hamlet,Linux_OS,"19,613","1,916",48441,-,Differential Incremental,snert,snert,Backup,1,Daily_Incremental,153.062,Successful,0,
"Aug 18, 2011 6:00 PM","Aug 18, 2011 6:04 PM",luckyeddie,Linux_OS,"22,919","4,950",48442,-,Differential Incremental,snert,snert,Backup,1,Daily_Incremental,530,Successful,0,
"Aug 18, 2011 6:00 PM","Aug 18, 2011 6:04 PM",hagar,Linux_OS,"17,261","1,293",48443,-,Differential Incremental,snert,snert,Backup,1,Daily_Incremental,45.156,Successful,0, 

$ awk -F\, '/Backup|Restore|Duplicate/{ if ($7 != 0) {print;} }' OpsCenter_Use_With_Generate_Drive_Usage_17_10_2011_03_13_PM.csv > NetBackup_Export.csv

$ head -10 NetBackup_Export.csv
"Aug 18, 2011 3:22 PM","Aug 18, 2011 3:30 PM",helga,Linux_OS,"73,661",281,48433,-,Differential Incremental,snert,snert,Backup,1,Daily_Incremental,90.094,Successful,0,
"Aug 18, 2011 6:00 PM","Aug 18, 2011 6:04 PM",hamlet,Linux_OS,"19,613","1,916",48441,-,Differential Incremental,snert,snert,Backup,1,Daily_Incremental,153.062,Successful,0,
"Aug 18, 2011 6:00 PM","Aug 18, 2011 6:04 PM",luckyeddie,Linux_OS,"22,919","4,950",48442,-,Differential Incremental,snert,snert,Backup,1,Daily_Incremental,530,Successful,0,
"Aug 18, 2011 6:00 PM","Aug 18, 2011 6:04 PM",hagar,Linux_OS,"17,261","1,293",48443,-,Differential Incremental,snert,snert,Backup,1,Daily_Incremental,45.156,Successful,0,
"Aug 18, 2011 6:00 PM","Aug 18, 2011 6:05 PM",honi,Linux_OS,"75,343","1,047",48445,-,Differential Incremental,snert,snert,Backup,1,Daily_Incremental,130.062,Partially Successful,1,
"Aug 18, 2011 6:00 PM","Aug 18, 2011 6:09 PM",helga,Linux_OS,"73,519",266,48446,-,Differential Incremental,snert,snert,Backup,1,Daily_Incremental,86.781,Successful,0,
"Aug 18, 2011 9:00 PM","Aug 18, 2011 9:27 PM",zook,Exchange_t-z,"31,900","2,106",48449,-,Differential Incremental,snert,zook,Backup,1,Exchange-Mailbox,"3,103.531",Partially Successful,1,
"Aug 18, 2011 9:19 PM","Aug 18, 2011 9:21 PM",hernia-1,Windows_OS-File-RootDrives,"7,031","2,054",48452,-,Differential Incremental,snert,hernia-1,Backup,1,Daily_Differential,106.635,Successful,0,
"Aug 18, 2011 9:19 PM","Aug 18, 2011 9:24 PM",hernia-1,Windows_OS-File-RootDrives,"8,662","1,565",48453,-,Differential Incremental,snert,hernia-1,Backup,1,Daily_Differential,272.293,Successful,0,
"Aug 18, 2011 9:19 PM","Aug 18, 2011 9:24 PM",hernia-2,Windows_OS-File-RootDrives,"7,108","1,600",48455,-,Differential Incremental,snert,hernia-1,Backup,1,Daily_Differential,187.156,Successful,0,

$ ./generate_drive_usage.pl | head -100
Aug 18 2011,0,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,30,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,100,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,130,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,200,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,230,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,300,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,330,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,400,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,430,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,500,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,530,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,600,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,630,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,700,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,730,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,800,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,830,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,900,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,930,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1000,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1030,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1100,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1130,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1200,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1230,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1300,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1330,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1400,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1430,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1500,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1530,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1600,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1630,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1700,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1730,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1800,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1830,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1900,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,1930,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,2000,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,2030,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,2100,zook,,,,,,,,,,,,,,,,,,,
Aug 18 2011,2130,,,,,,,,,,,,,,,,,,,,
Aug 18 2011,2200,giggles04,giggles04,giggles05,,,,,,,,,,,,,,,,,
Aug 18 2011,2230,,giggles04,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,2300,hernia,giggles04,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,2330,hernia,giggles04,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,0,hernia,giggles04,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,30,hernia,giggles04,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,100,hernia,giggles04,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,130,,giggles04,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,200,,giggles04,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,230,,giggles04,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,300,,giggles04,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,330,,giggles04,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,400,,giggles04,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,430,,giggles04,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,500,,giggles04,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,530,,,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,600,,,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,630,,,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,700,,,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,730,,,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,800,,,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,830,,,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,900,,,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,930,,,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,1000,,,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,1030,,,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,1100,,,giggles05,,,,,,,,,,,,,,,,,
Aug 19 2011,1130,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,1200,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,1230,prod2p,prod1p,prod1p,prod2p,,,,,,,,,,,,,,,,
Aug 19 2011,1300,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,1330,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,1400,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,1430,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,1500,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,1530,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,1600,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,1630,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,1700,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,1730,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,1800,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,1830,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,1900,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,1930,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,2000,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,2030,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,2100,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,2130,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,2200,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,2230,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,2300,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,2330,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,0,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,30,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,100,,,,,,,,,,,,,,,,,,,,
Aug 19 2011,130,fearless,lutep,,,,,,,,,,,,,,,,,,

$ 

So there it is.  I hope this helps, especially if you have tape drives in your environment.  At the very least, it should give you a nice spreadsheet you can give to your boss and say, "Here's what our tape drives are doing all day."  You should be able to find the new script and sample data in the Downloads section.  The script has one bug in that it doesn't end cleanly (it keeps trying to parse), so you will have to 'CTRL-C' out of it when it's reached the end.

Here's the full script if you'd rather copy/paste it:

 
#!/bin/perl
#####################################################################################
#                                                                                   #
# generate_drive_usage.pl()                                                         #
#                                                                                   #
#   Written By: Alan T. Landucci-Ruiz                                               #
#               http://solarisdeveloper.blogspot.com       ... #
#                                                                                   #
#   Abstract: This program generates CSV output of Tape drive usage, based on CSV   #
#             input.  It is designed to help facilitate scheduling of tape drive    #
#             allocations when creating and moving backup schedules.                #
#                                                                                   #
#                                                                                   #
#   Args: none                                                                      #
#                                                                                   #
#   Variables:                                                                      #
#              $TIME          - The time component of our output CSV.               #
#              $TINC          - The increment component of our CSV.                 #
#              $N_DRIVES      - The number of drives we have.                       #
#              @DRIVE         - Our "DRIVE" array: holds the string of allocation.  #
#              @DRIVE_START   - Time that the drive is allocated.                   #
#              @DRIVE_END     - Time that the drive is unallocated.                 #
#              $INFILE        - The input file csv.                                 #
#              $CURR_LINE     - The line being processed from the input CSV.        #
#              $NEXT          - Index of our next empty drive.                      #
#              $IDX           - Index counter.                                      #
#              $DATA_CLIENT   - Client that is allocating the drive.                #
#              $DATA_START    - Start time for the client.                          #
#              $DATA_END      - End time for the client.                            #
#              $DATA_POLICY   - Policy of the client that is allocating the drive.  #
#              $LAST_END_TIME - The latest end time of all drives.                  #
#              $DATA_STARTDATE- The date/time pair of the start of the backup       #
#                                                                                   #
#    Known Issues:                                                                  #
#       Currently, if a last end time is the next day's time, but earlier than the  #
#     currently known last end time, then it will use the currently known last end  #
#     time instead of the earlier one the next day.                                 #
#          e.g., 0200 tomorrow will be considered earlier than 1400 today.          #
#                                                                                   #
#####################################################################################
use strict;

# Global variables
my $TIME=0000;
my $TINC=30;
my $N_DRIVES=20;
my @DRIVE;
my @DRIVE_START;
my @DRIVE_END;
my $INFILE='NetBackup_Export.csv';
my $CURR_LINE;
my $NEXT=0;
my $IDX;
my $DATA_CLIENT;
my $DATA_START;
my $DATA_END;
my $DATA_POLICY;
my $LAST_END_TIME=-1;
my $DATA_STARTDATE;

#####################################################################################
# init_drives()                                                                     #
#   A routine to find the next empty drive                                          #
#                                                                                   #
# Args: none                                                                        #
#                                                                                   #
# Pseudocode:                                                                       #
#  - Starting with the lowest index (0),                                            #
#  - For each index, blank out the indexed drive (unallocating it), and set start   #
#    and end times to '-1'.                                                         #
#####################################################################################
sub init_drives()
{
  foreach $IDX (0..$N_DRIVES-1)
  {
    $DRIVE[$IDX] = "";
    $DRIVE_START[$IDX] = -1;
    $DRIVE_END[$IDX] = -1;
  }
}

#####################################################################################
# nextempty()                                                                       #
#   A routine to find the next empty drive                                          #
#                                                                                   #
# Args: none                                                                        #
#                                                                                   #
# Pseudocode:                                                                       #
#  - Starting with the lowest index (0),                                            #
#  - For each index, if the indexed drive is blank (i.e., empty), return that       #
#    that number.                                                                   #
#####################################################################################
sub nextempty
{
  foreach $IDX (0..$N_DRIVES-1)
  {
    if ( $DRIVE[$IDX] eq "" ) { return $IDX; }
  }
}

#####################################################################################
# round_to_incr()                                                                   #
#   Rounds the argument to the next '$TINC' minute mark off the hour (up or down)   #
#                                                                                   #
# Args:                                                                             #
#   $time - This is the time that needs to be rounded in HHMM format.               #
#                                                                                   #
# Pseudocode:                                                                       #
#  - Grab the minutes by getting the modulus of $time and 100                       #
#      e.g. $time = 1422 --> 1400 = (1422 % 100) = 22                               #
#  - Grab the hour by subtracting the minutes from the time.                        #
#                            1400 = 1422 - (22)                                     #
#  - Find out how close we are to the $TINC minute mark by creating our $rem        #
#    variable: our minutes modulus $TINC (e.g., above, 22 %30 = 22).                #
#  - If our remainder is less than 15, we round down, by subtracting the remainder  #
#    of our modulus from the actual minutes.                                        #
#      e.g. $minutes = 44 --> 44 - (44 % $TINC) = 44 - 14 = $TINC.                  #
#  - Otherwise, we round up by adding the difference of ther remainder and $TINC.   #
#           $minutes = 22 --> 22 + 30 - (22 % 30) = 22 + 30 - 22 = 30               #
#           $minutes = 48 --> 48 + 30 - (48 % 30) = 48 + 30 - 18 = 60               #
#  - If our minutes component is less than 60 (less than 1 hour), we add that to    #
#    our hour component.  Otherwise, we just ad 100 to the hour component to get    #
#    the rounded time.                                                              #
#  - If our time has exceeded or is at 2400, we subtract 2400.                      #
#                                                                                   #
#####################################################################################
sub round_to_incr
{
  my ( $time ) = @_;

  my $minutes = $time % 100;
  my $hour = $time - $minutes;

  my $rem = $minutes % $TINC;

  if ( $rem < 15 ) { $minutes = $minutes - $rem; }
  else { $minutes = $minutes + $TINC - $rem; }

  if ( $minutes < 60 ) { $time = $hour + $minutes; }
  else { $time = $hour + 100; }

  if ($time >= 2400) { $time = $time - 2400 ;}

  return $time;
}


#####################################################################################
# print_row()                                                                       #
#   The main printing function of our program; prints the time and drive allocations#
#   for each drive.                                                                 #
#                                                                                   #
# Args: none                                                                        #
#                                                                                   #
# Pseudocode:                                                                       #
#  - Print the time in HHMM format                                                  #
#  - Starting with the lowest index (0),                                            #
#    For each index, print a comma, then the indexed drive allocation.              #
#  - Print carriage return.                                                         #
#####################################################################################
sub print_row()
{
  print "$DATA_STARTDATE,$TIME";
  foreach $IDX (0..$N_DRIVES-1)
  {
    print ",$DRIVE[$IDX]";
  }
  print "\n";
}

#####################################################################################
# get_last_end_time()                                                               #
#   Finds the latest end time for the current drive allocations.  At this point,    #
#   does not factor if the end time is the next day.                                #
#                                                                                   #
# Args: none                                                                        #
#                                                                                   #
# Pseudocode:                                                                       #
#  - Starting with the lowest index (0),                                            #
#    For each index, if the drive end time is greater than the last one we checked, #
#    store that value to return.                                                    #
#  - Return value stored.                                                           #
#####################################################################################
sub get_last_end_time()
{
  my $RETURNVAL=0;

  foreach $IDX (0..$N_DRIVES-1)
  {
    if ( $DRIVE_END[$IDX] > $RETURNVAL ) { $RETURNVAL=$DRIVE_END[$IDX]; }
  }
  return $RETURNVAL;
}

#####################################################################################
# print_debug()                                                                     #
#   A simple subroutine to print out the current variables.                         #
#                                                                                   #
# Args: none                                                                        #
#                                                                                   #
#####################################################################################
sub print_debug()
{
  print "TIME: $TIME\n";
  print "DATA_START: $DATA_START\n";
  print "DATA_END: $DATA_END\n";
  print "DATA_CLIENT: $DATA_CLIENT\n";
  print "LAST END TIME: $LAST_END_TIME\n";
  print "CURR_LINE: $CURR_LINE\n";
}

#####################################################################################
# remove_commas_from_fields()                                                       #
#   This subroutine removes commas and double-quotes from fields that have commas   #
#   within double quotes.                                                           #
#                                                                                   #
# Args: Comma-delineated line (data)                                                #
#                                                                                   #
#####################################################################################
sub remove_commas_from_fields
{
  my $LINE=$_[0];
  my $RETURNLINE;
  my $LINDEX=2;
  my @SLINE=split ("\"",$LINE);
  for ($LINDEX=1;$LINDEX<$#SLINE;$LINDEX+=2)
  {
    $SLINE[$LINDEX] =~ s/,//g;
  }

  foreach $LINE (@SLINE)
  {
    $RETURNLINE .= "$LINE";
  }
  return $RETURNLINE;
}


#####################################################################################
# convert_time()                                                                    #
#   This subroutine converts standard time format into military time.               #
#                                                                                   #
# Args: "HH:MM AM/PM" time string                                                   #
# Returns: "HHMM" military time string                                              #
#                                                                                   #
#####################################################################################
sub convert_time
{
  my $HHMM=$_[0];
  my @HHMMARRAY = split('[ :]',$HHMM);
  my $INDEX0 = $#HHMMARRAY;
  my $INDEX1 = $#HHMMARRAY - 1;
  my $INDEX2 = $#HHMMARRAY - 2;
  my $AMPM = $HHMMARRAY[$INDEX0];
  my $MIN = $HHMMARRAY[$INDEX1];
  my $HOUR = $HHMMARRAY[$INDEX2];

  if ( $AMPM eq "PM")
  {
    $HOUR += 12;
  }
  my $RETURNVAL = "$HOUR" . "$MIN";
}

#####################################################################################
# convert_date()                                                                    #
#   This subroutine converts the first two date/time strings of a comma-delineated  #
#   string into military time, and appends the date to the end.                     #
#                                                                                   #
# Args: Comma-delineated string with the first and second field having a string     #
#       date/time format of "MONTH DAY YEAR HH:MM AM/PM"                            #
# Returns: Comma-delineated string with the first and second field having a string  #
#          time format of "HHMM" in military time, with the date in the format      #
#          "MONTH DAY YEAR" appended to the end.                                    #
#                                                                                   #
#####################################################################################
sub convert_date
{
  my $LINE=$_[0];
  my @SITEM;
  my $RETURNLINE="";
  my $LITEM;
  my @SDATE;

  my @SLINE=split(",",$LINE);

  my @SDATE = split(" ",$SLINE[0]);
  push(@SLINE,"$SDATE[0] $SDATE[1] $SDATE[2]");   # Preserve the first field; append it to the list.
  my @SDATE = split(" ",$SLINE[1]);
  push(@SLINE,"$SDATE[0] $SDATE[1] $SDATE[2]");   # Preserve the second field; append it to the list.

  $SLINE[0] = convert_time($SLINE[0]);
  $SLINE[1] = convert_time($SLINE[1]);

  foreach $LITEM (@SLINE)
  {
    $RETURNLINE .= "$LITEM,";
  }
  return $RETURNLINE;
}


#####################################################################################
# main()                                                                            #
#   The main body of the program.                                                   #
#                                                                                   #
# Args: none                                                                        #
#                                                                                   #
# Pseudocode:                                                                       #
#   - Initialize the drive variables (call sub init_drives).                        #
#   - Open the $INFILE for reading.                                                 #
#   - Read in the first line.                                                       #
#   - Split that line into our fields:                                              #
#       $DATA_START $DATA_END, $DATA_CLIENT, $DATA_POLICY                           #
#   - Start processing within a do-while-loop:                                      #
#     - For each index of drives, if the drive is claimed and the drive end time    #
#       is at the current time, then we release the drive (reset indexed variables).#
#     - If the current time is the same as the start time from the line we just got,#
#       then claim the drive, by storing the client name, start, and end times.     #
#       We also grab the next line from our input file $INFILE.                     #
#       Otherwise, we print out our row and increment the time by $TINC minutes.    #
#     - We find out when our latest end time for allocation is and we store it.     #
#   - processing ends when (1) we've reached the end of the file, and (2) the time  #
#     has passed the latest end time.                                               #
#   - We close our input file.                                                      #
#                                                                                   #
#####################################################################################
sub main()
{

  init_drives;

  # Things we need before we can start processing:
  #    1.  Current Time "" got it (above)
  #    2.  Current Line "" Need to start on first line

  my $FD=open(FD,"<$INFILE") or die "Cannot open: $!";

  #  We grab the first line of the file and extract the values.
  $CURR_LINE=<FD>;
  $CURR_LINE=remove_commas_from_fields($CURR_LINE);
  $CURR_LINE=convert_date($CURR_LINE);
  ($DATA_START, $DATA_END, $DATA_CLIENT, $DATA_POLICY) = split(',',$CURR_LINE);
   $DATA_START = round_to_incr($DATA_START);              # added 2010-07-02
   $DATA_END = round_to_incr($DATA_END);              # added 2010-07-02
   $DATA_STARTDATE = (split(',',$CURR_LINE))[18];

  # Now we can start our processing.  We do this until the end of the file.
  do
  {
    #  First, we'll check to see if each drive is claimed and if it is, we check if
    # it's reached its end time.  If so, we release that drive.
    foreach $IDX (0..$N_DRIVES-1)
    {
      if ( $DRIVE[$IDX] ne "" && $DRIVE_END[$IDX] == $TIME )
      {
        # To release the drive, we reset all values.
        $DRIVE[$IDX] = "";
        $DRIVE_END[$IDX] = -1;
        $DRIVE_START[$IDX] = -1;
      }
    }

    #  Second, we need to check if our data line's start time matches
    # the current time.
    #  If it is, we claim a drive, and go to the next line.
    #  If it isn't, we print the output and increment the time.
    if ( $TIME == $DATA_START )
    {
      $NEXT=nextempty;
      $DRIVE[$NEXT] = $DATA_CLIENT;
      $DRIVE_START[$NEXT] = $DATA_START;
      $DRIVE_END[$NEXT] = $DATA_END;
      $CURR_LINE=<FD>;
      $CURR_LINE=remove_commas_from_fields($CURR_LINE);
      $CURR_LINE = convert_date($CURR_LINE);
      ($DATA_START, $DATA_END, $DATA_CLIENT, $DATA_POLICY) = split(',',$CURR_LINE);
      $DATA_START = round_to_incr($DATA_START);              # added 2010-07-02
      $DATA_END = round_to_incr($DATA_END);              # added 2010-07-02
      $DATA_STARTDATE = (split(',',$CURR_LINE))[18];
    } else
    {
      print_row();
      $TIME = round_to_incr($TIME + $TINC);
    }

    $LAST_END_TIME = round_to_incr(get_last_end_time());

  } until ( eof(FD) && $TIME > $LAST_END_TIME && $CURR_LINE eq "" );
  # We want to stop processing after (a) we've reached the end of the file, and (b)
  # and (b) we've gone past the last end time.

  close(FD);
}

main;