#!/usr/bin/perl
#
# Перекладывает трафик таблицы текущего трафика 'traffic_cur'
# в архивные таблицы 'traffic_YEAR'.
#

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 = '';
# размер хеша для буфиризирования данных
our $h_size = 10000;

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

# Получим дату по которую будут выгружаться данные.
# Данные за эту дату не попадают в архивную таблицу.
my ($d, $m, $y) = (localtime)[3..5];
$m++; $y += 1900;
my $endDate = sprintf("%04d-%02d-%02d", $y, $m, $d);

# получим пользователя и пароль для доступа к базе
my ($db_name, $db_user, $db_pass) = ("", "", "");
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;};
	}
}
close(CONFIG);

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

# получим дату когда последний раз выгружался трафик
my $query = "select value from config where name = 'last_unload_to_archive'";
my $beginDate = $dbh->selectrow_array($query);
error("Failed execute query '$query': " . $dbh->errstr) if $dbh->err;

if (!$beginDate) {
	# Данные еще не выгружались. Получим самую маленькую дату из
	# таблицы с трафиком.
	$query = "select min(date) from traffic_cur";
	$beginDate = $dbh->selectrow_array($query);
	error("Failed execute query '$query': " . $dbh->errstr) if $dbh->err;

	# если 'beginDate' пустая, то трафика нет в таблице
	if (!$beginDate) {
		$dbh->disconnect();
		exit;
	}

	# подготовим 'beginDate'
	($beginDate) = $beginDate =~ /^([\d-]+)/;
}

# Сформируем список годов и дат по которым будет выгружаться трафик.
# Если выгрузка придет на конец одного года и начало другого,
# то трафик должен быть разложен по разным таблицам.
#
# Например, если мы имеем даты: 2007-02-28 <> 2009-03-17
# То даты будут разбиты на следующие интервалы:
# 2007: 2007-02-28 <> 2008-01-01
# 2008: 2008-01-01 <> 2009-01-01
# 2009: 2009-01-01 <> 2009-03-17
#
my (@years, @beginDates, @endDates);
my ($beginYear) = $beginDate =~ /^(\d+)/;
my ($endYear) = $endDate =~ /^(\d+)/;

$years[0] = $beginYear++;
$beginDates[0] = $beginDate;
for (my $i = 1; $beginYear <= $endYear; $beginYear++, $i++) {
	$years[$i] = $beginYear;
	$beginDates[$i] = "$beginYear-01-01";
	$endDates[$i - 1] = "$beginYear-01-01";
}
$endDates[@years - 1] = $endDate;

# Проверим что таблицы в которые будут выгружаться данные
# существуеют. Если их нет, то создадим их.
$query = 'select year from list_traffic';
my $sth = $dbh->prepare($query) or error("Failed prepare query '$query': " . $dbh->errstr);
$sth->execute() or error("Failed execute query '$query': " . $dbh->errstr);
my %tabList;
while (my @val = $sth->fetchrow_array()) {
	$tabList{$val[0]} = 1;
}
$sth->finish();

foreach my $year (@years) {
	if (!exists($tabList{$year})) {
		# таблица для выгрузки не существует, создадим ее
		$query = "create table traffic_$year (" .
			"`date` date not null, " .
			"`srcIp` int unsigned not null, " .
			"`dstIp` int unsigned not null, " .
			"`dstPort` smallint unsigned not null, " .
			"`pId` tinyint not null, " .
			"`inBytes` int unsigned not null, " .
			"`outBytes` int unsigned not null, " .
			"primary key (`date`, `srcIp`, `dstIp`, `dstPort`, `pId`) " .
			") engine = MyISAM default charset = utf8";
		$dbh->do($query) or error("Failed execute query '$query': " . $dbh->errstr);

		$query = "insert into list_traffic (year) values ($year)";
		$dbh->do($query) or error("Failed execute query '$query': " . $dbh->errstr);
	}
}

# Переложим данные в архивные таблицы.
for (my $i = 0; $years[$i]; $i++) {
	transfer_data($dbh, $years[$i], $beginDates[$i], $endDates[$i]);
}

# сохраним дату последней выгрузки данных
$query = "update config set value = '$endDate' where name = 'last_unload_to_archive'";
$dbh->do($query) or error("Failed execute query '$query': " . $dbh->errstr);

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

# перемещает трафик в архивную таблицу
sub transfer_data {
	my ($dbh, $year, $beginDate, $endDate) = @_;
	my $inQuery = "select date, srcIp, dstIp, dstPort, pId, inBytes, outBytes " .
		"from traffic_cur where `date` >= '$beginDate' && `date` < '$endDate'";
	my $outQuery = "insert into traffic_$year (date, srcIp, dstIp, dstPort, pId, inBytes, outBytes) " .
		"values (?, ?, ?, ?, ?, ?, ?) " .
		"on duplicate key update inBytes = inBytes + ?, outBytes = outBytes + ?";

	my $outSth = $dbh->prepare($outQuery) or error("Failed prepare query '$outQuery': " . $dbh->errstr);

	# перенесем данные
	my $count = 0;
	LOOP_: {
		my $i = 0;
		my %traffic = ();

		my $tmpQuery = $inQuery . " limit $count, $h_size";
		my $inSth = $dbh->prepare($tmpQuery) or error("Failed prepare query '$tmpQuery': " . $dbh->errstr);
		$inSth->execute();
		while (my @val = $inSth->fetchrow_array()) {
			# округлим дату
			my ($y, $m) = $val[0] =~ /^(\d+)-(\d+)/;
			$val[0] = "$y-$m-1";

			# добавим данные в буфер
			my $key = join '@', @val[0..4];
			if (exists $traffic{$key}) {
				$traffic{$key}[0] += $val[5];
				$traffic{$key}[1] += $val[6];
			} else {
				$traffic{$key} = [$val[5], $val[6]];
			}

			$i++;
		}
		$inSth->finish();

		# перенесем данные в архивную таблицу
		foreach my $key (sort keys %traffic) {
			my ($inBytes, $outBytes) = ($traffic{$key}[0], $traffic{$key}[1]);
			$outSth->execute(split('@', $key), $inBytes, $outBytes, $inBytes, $outBytes) or
				error("Failed execute query '$outQuery': " . $dbh->errstr);
		}

		if ($i == $h_size) {
			$count += $h_size;
			redo LOOP_;
		}
	}

	$outSth->finish();
}

# вывод сообщения об ошибке
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" .
		"Unloads traffic in archive table.\n" .
		"\n" .
		"  --config=NAME  use a configuration file\n" .
		"  --daemon       run in daemon mode\n" .
		"  --help         display this help and exit\n" .
		"\n");
}
