#!/usr/bin/perl
#  $Id: peed 22408 2011-04-04 16:38:57Z teetov $
# -----------------------------------------------------------------------------
#  PEED - Policy Enforcment, Event Disposal 
# -----------------------------------------------------------------------------
#  Author: Alex Titov
#  QA by:  Christopher C Gettings
#  Copyright: videoNEXT LLC
# -----------------------------------------------------------------------------

use strict;
use Time::HiRes;
use SKM::DB;
use Data::Dumper;
use Log::Log4perl "get_logger";
use POSIX qw/ceil/;
require "$ENV{APL}/common/bin/logger.patrol";

# CONST ========================================================================
my $packetSize = 10000;
my $timeout = 9;
my $recordsCountLimit = 500000;
my $recordsDaysLimit = 30;

my $startTs = [Time::HiRes::gettimeofday()];
my $log=get_logger('NEXTCAM::SDI::AUDIT');
my $dbh=DBMaster({PrintError=>1, 'RaiseError' => 0}) || $log->logdie($DBI::errstr);
$dbh->{FetchHashKeyName} = 'NAME_uc';
$dbh->{RaiseError}=1;
$dbh->{ShowErrorStatement}=1;


#
# Delete audit records created before $endTs. Parameters $startTs and $statCount helps
# to determine number of iterations
#
sub deleteRecords {
	my ($startTs, $endTs, $statCount) = @_;

	my $markTs = [Time::HiRes::gettimeofday()];
	my $iterCount = (($endTs - $startTs) < 60*60) ? ceil($statCount/$packetSize) : 1;
	my $iterLen = ($endTs - $startTs) / $iterCount;

	if ($log->is_debug()) {
		my $msg = sprintf "deleteRecords() call: startTs=%.3f, endTs=%.3f, statCount=%.3f, iterCount=%d",
				$startTs, $endTs, $statCount, $iterCount;
		$log->debug($msg);
	}

	my $rowsDeleted = 0;
	for(my $i = $iterCount - 1; $i >= 0; $i--) {
		my$ ts = $endTs - $iterLen * $i;

		eval {
			my $result = $dbh->do("DELETE FROM audit WHERE date <= to_timestamp($ts)");
			my $rows = ($result eq '0E0') ? 0 : $result;
			$rowsDeleted += $rows;
		};
		if ($@) {
			$log->error($@);
			die 'Failed to delete records';
		}

		sleep($timeout) if ($i > 0);
	}

	my $elapsed = Time::HiRes::tv_interval($markTs);
	my $msg = sprintf "deleteRecords() completed. Deleted: %d, iterations: %d elapsed time: %.3f sec",
			$rowsDeleted, $iterCount, $elapsed;
	$log->info($msg);

	return $rowsDeleted;
}



# MAIN =======================================================================
$log->info('starting "Audit Cleaner"');

#
# Get configuration
#
eval {
	my $rows = $dbh->selectall_arrayref(
		"SELECT attr, val FROM _obj_attr WHERE obj=53 AND attr IN ('AUDIT_LOG_RECS', 'AUDIT_LOG_STORE_TIME')");
	if ($rows) {
		foreach my $row (@$rows) {
			$recordsCountLimit = $row->[1] if ($row->[0] eq 'AUDIT_LOG_RECS');
			$recordsDaysLimit = $row->[1] if ($row->[0] eq 'AUDIT_LOG_STORE_TIME');
		}
	}
};
if ($@) {
	$log->error($@);
	die 'Failed to get Audit configuration';
}
$log->debug(sprintf('AUDIT_LOG_RECS=%d records, AUDIT_LOG_STORE_TIME=%d days', $recordsCountLimit, $recordsDaysLimit));


#
# Get current state
#
my ($recordCount, $recordMinDate, $nowDate, $expirationDate) = (0, 0, 0, 0);
eval {
	my $rows = $dbh->selectall_arrayref(
		"SELECT COUNT(*), 
			TRUNC(EXTRACT(EPOCH FROM MIN(date))),
			TRUNC(EXTRACT(EPOCH FROM now() at time zone 'UTC')), 
			EXTRACT(EPOCH FROM date_trunc('day',now() at time zone 'UTC' - ($recordsDaysLimit || 'day')::interval))
		   FROM audit");
	if ($rows) {
		$recordCount = $rows->[0][0];
		$recordMinDate = $rows->[0][1];
		$nowDate = $rows->[0][2];
		$expirationDate = $rows->[0][3];
	}
};
if ($@) {
	$log->error($@);
	die 'Failed to get Audit records statistics';
}
$log->info(sprintf('Total records=%d, oldest record ts=%d, expiration ts=%d', $recordCount, $recordMinDate, $expirationDate));


if ($recordCount <= $recordsCountLimit && $expirationDate <= $recordMinDate) {
	my $elapsed = Time::HiRes::tv_interval($startTs);
	my $msg = sprintf "Audit Clean completed (No records deleted). Elapsed time: %.3f sec", $elapsed;
	$log->info($msg);
	exit(0);
}


#
# Delete expired records
#
my $recordsDeleted = 0;
if ($recordMinDate < $expirationDate) {
	$recordsDeleted = deleteRecords($recordMinDate, $expirationDate, 
		($expirationDate - $recordMinDate)/($nowDate - $recordMinDate) * $recordCount);
}


#
# If number of records still exceeds $recordsCountLimit - delete oldest records
#
if ($recordCount - $recordsDeleted > $recordsCountLimit) {
	my $cutOffDate = 0;
	my $toDelete = $recordCount - $recordsDeleted - $recordsCountLimit;
	eval {
		my $rows = $dbh->selectall_arrayref("SELECT EXTRACT(EPOCH FROM date) FROM audit a ORDER BY a.date LIMIT 1 OFFSET $toDelete");
		if ($rows) {
			$cutOffDate = $rows->[0][0];
		}
	};
	if ($@) {
		$log->error($@);
		die 'Failed to execute SQL statement';
	}

	$log->debug("Extra records=$toDelete, cutoff ts=$cutOffDate");

	if ($cutOffDate == 0) {
            $log->error("Failed to determine cut off timestamp");
	} else {
		my $startTs = ($expirationDate > $recordMinDate) ? $expirationDate : $recordMinDate;
		$recordsDeleted += deleteRecords($startTs, $cutOffDate, $toDelete);
	}
}

my $elapsed = Time::HiRes::tv_interval($startTs);
my $msg = sprintf "Audit Clean completed (%d records deleted). Elapsed time: %.3f sec", $recordsDeleted, $elapsed;
$log->info($msg);
