#!/usr/bin/perl
#
# Выполняет оптимизацию таблиц базы, т.е. выполняет
# вызовы 'analyze table' и 'optimize table'.
#

use warnings;
use strict;

use DBI;
use Sys::Syslog qw(:standard :macros);
use File::Basename;
use Getopt::Long;

# параметры командной строки
my $config = "/etc/trafstat/trafstat.conf";
our $daemon = '';
my $help = '';

# получим параметры командной строки
GetOptions("config=s" => \$config, "help" => \$help, "daemon" => \$daemon);
if ($help) {help(); exit;}

# служебные параметры
my ($db_name, $db_user, $db_pass) = ("", "", "");
my $fileStatus =  "";

# количество дней через которое необходимо выполнить обслуживание таблицы
my (%tables, $archiveTable);

# получим служебные параметры
open(CONFIG, $config) or error("Failed open cofig file: $config");
while (<CONFIG>) {
	next if /^#|^$|^\[/;
	SWITCH: {
		/^db_name\s*=\s*(\w+)/ && do {$db_name = $1; last SWITCH;};
		/^db_user\s*=\s*(\w+)/ && do {$db_user = $1; last SWITCH;};
		/^db_pass\s*=\s*(\w+)/ && do {$db_pass = $1; last SWITCH;};
		/^trafserve_file_status\s*=\s*([\w|\/]+)/ && do {$fileStatus = $1; last SWITCH;};
		/^trafserve_archive_table\s*=\s*(\d+)/ && do {$archiveTable = $1; last SWITCH;};
		/^trafserve_table_(\w+)\s*=\s*(\d+)/ && do {$tables{$1} = $2; last SWITCH;};
	}
}
close(CONFIG);

# проверим что задан файл сохранения статуса
error("Is not defined 'trafserve_file_status' in the configuration file.") unless $fileStatus;

# подключимся к базе
our $dbh = DBI->connect("DBI:mysql:database=$db_name", $db_user, $db_pass,
	{PrintError => 0}) or error("Connect to database failed: $DBI::errstr");

# добавим список архивных таблиц
do {
	my $curYear = (localtime)[5] + 1900;
	my $query = "select year from list_traffic";
	my $sth = $dbh->prepare($query) or error("Failed prepare query '$query': " . $dbh->errstr);
	$sth->execute();
	while (my ($year) = $sth->fetchrow_array()) {
		$tables{"traffic_$year"} = ($year == $curYear) ? $archiveTable : 1500;
	}
	$sth->finish();
};

# дата последнего обслуживания таблицы
our %optimizeDates;

# загрузим даты последнего обслуживания
if (open(OPT_DATE, $fileStatus)) {
	while(<OPT_DATE>) {/^(\w+):\s([\d-]+)/ && do {$optimizeDates{$1} = $2};}
	close(OPT_DATE);
} else {
	warning("File status service ($fileStatus) does not exist.");
}

# выполним оптимизацию таблиц
optimize_table($_, $tables{$_}) foreach keys(%tables);

# сохраним текущий статус оптимизации таблиц
open(OPT_DATE, ">$fileStatus") or error("Failed open status file: $fileStatus");
print (OPT_DATE "$_: $optimizeDates{$_}\n") foreach sort(keys(%tables));
close(OPT_DATE);

$dbh->disconnect();
exit(0);

# возвращает дату в формате 'YYYY-MM-DD'
sub get_date {
	my $day = @_ ? shift : 0;
	my ($d, $m, $y) = (localtime(time - ($day * 24 * 60 * 60)))[3..5];
	$m++; $y += 1900;
	return sprintf("%04d-%02d-%02d", $y, $m, $d);
}

# проверяет, пора ли проводить обслуживание таблицы
sub check_date {
	my ($tabName, $day) = @_;

	if (exists $optimizeDates{$tabName}) {
		if (get_date($day) ge $optimizeDates{$tabName}) {
			return 1;
		} else {
			return 0;
		}
	} else {
		return 1;
	}
}

# выполняет обслуживание таблицы
sub optimize_table {
	my ($tabName, $day) = @_;

	if (check_date($tabName, $day)) {
		# выполним оптимизацию
		my $query = "analyze table $tabName";
		$dbh->do($query) or error("Error running query '$query': " . $dbh->errstr);
		$query = "optimize table $tabName";
		$dbh->do($query) or error("Error running query '$query': " . $dbh->errstr);
		# сохраним время текущей оптимизации
		$optimizeDates{$tabName} = get_date();
		info("Optimization of the table '$tabName'.");
	}
}

# выводит информационное сообщение
sub info {
	my $str = shift;
	my $msg = "INFO: $str";

	if ($daemon) {
		openlog(basename($0), "ndelay,pid", LOG_LOCAL0);
		syslog(LOG_INFO, $msg);
		closelog();
	} else {
		print "$msg\n";
	}
}

# вывод сообщения об ошибке
sub warning {
	my $str = shift;
	my $msg = "WARNING: $str";

	if ($daemon) {
		openlog(basename($0), "ndelay,pid", LOG_LOCAL0);
		syslog(LOG_WARNING, $msg);
		closelog();
	} else {
		warn("$msg\n");
	}
}

# вывод сообщения об ошибке и завершение работы
sub error {
	my $str = shift;
	my $msg = "ERROR: $str";

	if ($daemon) {
		openlog(basename($0), "ndelay,pid", LOG_LOCAL0);
		syslog(LOG_ERR, $msg);
		closelog();
		exit(1);
	} else {
		die("$msg\n");
	}
}

# отображает справку
sub help {
	my $progName = basename($0);
	print("Usage: $progName [OPTIONS]...\n" .
		"Performs database maintenance.\n" .
		"\n" .
		"  --config=NAME  use a configuration file\n" .
		"  --daemon       run in daemon mode\n" .
		"  --help         display this help and exit\n" .
		"\n");
}
