
#
# This perl script searches the specified fixed-length record ascii
# database for records between $searchStartDate and $searchEndDate.  
# It outputs the indices of the first and last record in this range. 
# Records are assumed ascii, with date in mm/dd/yyyy format, where the
# date is at an offset of 10 bytes from the beginning of each record.
#

use DB_File;

$ANON = 0;
if($ARGV[0] eq "-anon") {
    $searchStartDate = $ARGV[1]; 
    $searchEndDate   = $ARGV[2]; 
    $ANON=1;
} else {
    $searchStartDate = $ARGV[0]; 
    $searchEndDate   = $ARGV[1];
} 
$fileName        = "$ENV{SERVER_VAR}/databases/master"; 
$recordSize      = 200;
$offsetToDate    = 10;
$dateLength      = 10;
$first           = 0;
$last            = 1;
%lookups         = ();

unless ($searchStartDate =~ /\d+\/\d+\/\d+/) {
  print "usage: [options] <start date> <end date> (in mm/dd/yyyy format)\noption -anon removes user and host addresses from the report\n";
  exit(1);
}
unless ($searchEndDate =~ /\d+\/\d+\/\d+/) {
  print "usage: [options] <start date> <end date> (in mm/dd/yyyy format)\noption -anon removes user and host addresses from the report\n";
  exit(1);
}

#
# Open database
#

unless (open(handle, $fileName)) {
  print "Master database ($fileName) does not yet exist.\n"; 
  exit(0);
}


@all = <handle>;
close(handle);
$all = "";
$corrupt = 0;
foreach $all (@all)
{
    if($all =~ //)
    {
	$corrupt = 1;
	last;
    }
}
if($corrupt != 0)
{	
    $failed=0;
    open(WRITE, ">$fileName.tmp") or (print "Could not clean database\n" and $failed = 1);	
    if ($failed == 0)
    {
	foreach (@all)
	{
	    $_ =~ s///g;
	    print WRITE $_;
	}
	close(WRITE);
	system("cp $fileName.tmp $fileName") and  (print "Could not move $fileName.tmp to $fileName\n");
    }
}

$DB = tie %lookups, "DB_File", "$ENV{SERVER_VAR}/databases/nslooks.db", O_RDWR|O_CREAT, 0644, $DB_HASH;


unless (open(handle, $fileName)) {
  print "Master database ($fileName) does not exist.\n"; 
  exit(0);
}

#
# note on perl flock:  this may fail if the database
# is being accessed across an nfs
#

$LOCK_EX = 2;
$LOCK_NB = 4;
$LOCK_UN = 8;

#
#get an exclusive lock on the database
#wait if necessary
#
$locked = flock handle, $LOCK_EX | $LOCK_NB;
$wait = 0;
while(!($locked))
{
    $wait++;
    if ($wait > 60)
    {
	# break out of loop
	# and warn in database
	print "Flock FAILED\n";
	last;
    }
    sleep(1);
    $locked = flock handle, $LOCK_EX | $LOCK_NB;    
}

#
# Get size of file, and then number of records
#

($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
 $atime,$mtime,$ctime,$blksize,$blocks) = stat($fileName);
$numRecords = $size/$recordSize;

#
# Binary search for first record >= $searchStartDate
#

$startRecord = &search(1,$numRecords,$searchStartDate,$first);

#
# Binary search for last record <= $searchEndDate
#

$endRecord   = &search(1,$numRecords,$searchEndDate,$last);

#
# Traverse range of records to compile stats.
#

$totalSent = 0;
if (($startRecord != -1)&&($endRecord!=-1)) {
  for ($i=$startRecord;$i<=$endRecord;$i++) {
    seek(handle, ($i-1)*$recordSize, 0);
    read(handle, $record, 192);
    ($number, $startDate, $startTime, $endDate, $endTime, 
     $interface, $solver, $address, $forward) 
     = unpack("A10 A10 A8 A10 A8 A16 A30 A50 A50", $record);
    $INTERFACE{$interface}++;
    $SOLVER{$solver}++;
    if ($address =~ /(.+)\@(.+)/) {
      $host = $2;
      $user = $1;
    } else {
      $host = $address;
      $user = "";
    }
    if (!($host =~ /\S+/)) {
      $host = "(null)";
    } 
#
# create a hash of nslookup hosts found 
# so we don't keep looking for the same host
#
    if($tmpups{$host})
    {
        $host = $tmpups{$host};
    } elsif ($lookups{$host}) {
        $oldhost = $host;
        $host = $lookups{$host};
        $tmpups{$oldhost} = $host;
    } elsif ($host =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/){
        $oldhost = $host;
        $host = &lookupName($host);
        if($host ne $oldhost) {
            $lookups{$oldhost} = $host;
        } 
        $tmpups{$oldhost} = $host;
    }

    $HOST{$host}++;
    $domain = &getDomainExtension($host);
    if ($domain =~ /\S+/) {
      $DOMAIN{$domain}++;
    }
    $totalSent++;
    if($user =~ /\w+/)
    {
	$address = $user."\@".$host;
    }
    $ADDRESS{$address}++;
  }
}

flock handle, $LOCK_UN;
close(handle);


untie %lookups;

#
# Output distribution stats.
#

print "\n";
print "$ENV{SERVER_NAME} Report ($searchStartDate - $searchEndDate)\n";
print "Total jobs sent: $totalSent\n";
if ($totalSent == 0) {
  exit(0);
}
print "\n==============================================================\n\n";
printf("%50s   %8s\n", "Interface Distribution", "#jobs");
printf("%50s   %8s\n", "----------------------", "-----");
foreach $key (sort(keys %INTERFACE)) {
  printf("%50s   %8d\n", $key, $INTERFACE{$key});
}
print "\n==============================================================\n\n";

printf("%50s   %8s\n", "Solver Distribution", "#jobs");
printf("%50s   %8s\n", "-------------------", "-----");
foreach $key (sort(keys %SOLVER)) {
  printf("%50s   %8d\n", $key, $SOLVER{$key});
}
print "\n==============================================================\n\n";

if(!$ANON) {
    printf("%50s   %8s\n", "User Distribution", "#jobs");
    printf("%50s   %8s\n", "-----------------", "-----");
    foreach $key (sort (keys %ADDRESS)) {
	printf("%50s   %8d\n", $key, $ADDRESS{$key});
    }
    print "\n==============================================================\n\n";
    printf("%50s   %8s\n", "Host Distribution", "#jobs");
    printf("%50s   %8s\n", "-----------------", "-----");
    foreach $key (sort (keys %HOST)) {
	printf("%50s   %8d\n", $key, $HOST{$key});
    }
    print "\n==============================================================\n\n";
}

printf("%50s   %8s\n", "Domain Distribution", "#jobs");
printf("%50s   %8s\n", "-------------------", "-----");
foreach $key (sort(keys %DOMAIN)) {
  $domain  = &DescribeDomain($key);
  $domain .= "($key)"; 
  printf("%50s   %8d\n", $domain, $DOMAIN{$key});
}
print "\n==============================================================\n\n";

#
# sub to compare two dates. -1 = less, 0 = equal, 1 = greater
#

sub dateCompare {
  $_[0] =~ /(\d+)\/(\d+)\/(\d+)/;
  $m1 = $1; $d1 = $2; $y1 = $3;
  $_[1] =~ /(\d+)\/(\d+)\/(\d+)/;
  $m2 = $1; $d2 = $2; $y2 = $3;

  if ($y2 > $y1) {
    return -1;
  }
  if ($y2 < $y1) {
    return 1;
  }
  if ($m2 > $m1) {
    return -1;
  }
  if ($m2 < $m1) {
    return 1;
  }
  if ($d2 > $d1) {
    return -1;
  }
  if ($d2 < $d1) {
    return 1;
  }
  return 0;
}

#
# Binary search subroutine
#

sub search {
  local($p,$r,$v,$pdate,$qdate,$last,$compare);
 
  $p    = $_[0];
  $r    = $_[1];
  $v    = $_[2]; 
  $last = $_[3];

  if ($p == $r) {
    $pdate = getStartDateFromRecord($p);
    $compare = &dateCompare($pdate,$v);
    if ($last) {
      if ($compare == 1) {
        return -1;
      } else {
        return $p;
      }
    } else {
      if ($compare == -1) {
        return -1;
      } else {
        return $p;
      }
    }
  }
  if ($last) {
    $q = int(($p+$r)/2+0.5);
  } else {
    $q = int(($p+$r)/2);
  } 
  $qdate = getStartDateFromRecord($q);
  $compare = &dateCompare($qdate,$v);
  if ($last) {
    $rightTest = ($compare != 1);
  } else {
    $rightTest = ($compare == -1);
  }
  if ($last) {
    if ($rightTest) {
      return &search($q, $r, $v, $last);
    } else {
      return &search($p, $q-1, $v, $last);
    }
  } else {
    if ($rightTest) {
      return &search($q+1, $r, $v, $last);
    } else {
      return &search($p, $q, $v, $last);
    }
  }
}

sub getStartDateFromRecord {
  local($startDate); 
  $i = $_[0];
  seek(handle, ($i-1)*$recordSize+$offsetToDate, 0);
  read(handle, $startDate, $dateLength);
  return $startDate;
}

sub lookupName {
    $hname = $_[0];
    if($hname =~ m/^\d+\.\d+\.\d+\.\d+$/)
    {
	chomp($looker = `which nslookup`);
	if(defined($looker))
	{
	    @out = `$looker $hname`;
	    foreach $line (@out)
	    {
		chomp($line);
		if($line =~ /^\s*Name:\s*(\S+)\s*$/)
		{
		    $hname = $1;
		}
	    }
	}
    }
    return $hname;
}

sub getDomainExtension {
    local($domainExt) = $_[0];
    $domainExt =~ s/.*\.//g;
    $domainExt = "ip address" if ($domainExt =~ /[0-9].*/);
    $domainExt =~ tr/[A-Z]/[a-z]/;
    return $domainExt;
}

sub DescribeDomain {
    local($description) = "Unknown"; # Default domain description
    local($domain) = $_[0];          # Domain extension (passed parameter)
    $domaincodes = "$ENV{SERVER_HOME}/server/lib/domain.desc";

    if ($domain ne "ip address") {
        if (!(keys %domainDesc)) {
            open (CODES, "<$domaincodes") || die "Couldn't open $domaincodes\n";
            while (<CODES>) {
                    chomp;
                    ($code, $description) = split('   ');
                    $code =~ tr/[A-Z]/[a-z]/;
                    $domainDesc{$code} = $description;
            }
            close(CODES);
            $description = "Unknown";
        }
        if ($domainDesc{$domain}) {
            $description = $domainDesc{$domain};
        }
    }
    return $description;
}


