Скорее всего я изобретаю велосипед, но тем не менее, хочу поделиться своим опытом по созданию сабжа.
Предыстория
Дело в том что возникла необходимость в написании собственного веб-интерфейса к UTM5, именно личного кабинета.
Соответственно, пользователям нужно нужно её как-то отображать. Вариант использовать URFA в нашем случае не подошёл по причине ОЧЕНЬ долгого формирования статистики. Ну и, опять же, некоторым пользователям нужен не сводный отчёт по классам трафика за период, а "обычную" статистику по каждому дню.
Как это решить средствами UTM я не нашёл (возможно, плохо искал?), и мной было принято решение о написании собственной системы подсчёта.
История
Для сбора информации о трафике мы используем данные NetFlow. Предварительно, была изучена эта статья и было принято решение обрабатывать данные непосредственно из "сырых" логов UTM'а, и писать их уже в свою отдельную таблицу в БД.
Спешу поделиться своим решением с вами - вдруг, кому-то пригодится.
Итак, за основу был взят Perl-скрипт raw_fd_script от пользователя Magnum72 (за что ему огромное спасибо).
Думаю, на просторах этого форума можно найти эти скрипты.
В оригинале этот скрипт осуществлял бинарное чтение из лога UTM, группировал данные по месяцам и лиц.счетам и и записывал уже в сжатом (gzip) на файловую систему - один лиц.счёт=один файл.
На основании этих файлов, мы выдавали пользователям (по заказу) детализированную статистику по трафику.
После некоторого "рефакторинга", я научил этот скрипт ещё и писать обработанные (разбитые на даты) данные в БД, для последующего использования их в личном кабинете.
Системные требования
Для корректной работы мне потребовалось установить следующие perl-модули: Также, Вам потребуется добавить в конфигурационный файл UTM (обычно, /netup/utm5/utm5.cfg) строку, указывающую на Вашу дополнительную БД - в моём случае это UTM5_opt
Код: Выделить всё
database_optional=UTM5_opt
Для начала Вам потребуется создать таблицу под статистические данные в вашей БД (той, что Вы указали в database_optional):
Код: Выделить всё
CREATE TABLE `stat__daily` (
`date` date NOT NULL,
`ip` int(11) NOT NULL,
`utm_account_id` int(11) default NULL,
`packets` int(11) default NULL,
`bytes` bigint(20) NOT NULL default '0',
UNIQUE KEY `ip` (`ip`,`date`),
KEY `account` (`utm_account_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Ежедневная статистика';
Код: Выделить всё
# cat raw_fd_script
#!/usr/bin/perl
use DBI;
use IO::File;
use IO::Compress::Gzip;
use Config::IniFiles;
use Class::Date;
use strict;
my $datadir = '/var/flowstat';
my $log = IO::File->new('/var/log/fd.log','a') or die('Unable to open/create log-file!');
my $cfg = Config::IniFiles->new(-file=>'/netup/utm5/utm5.cfg',-fallback=>'main') or do{ print $log "Unable to load UTM config\n"; die(); };
my $dbh = DBI->connect(sprintf('dbi:%s:%s',$cfg->val('main','database_type'),$cfg->val('main','database_optional')),$cfg->val('main','database_login'),$cfg->val('main','database_password')) or do{ print $log "Unable to connect DB!\n"; die(); };
my %traf;
printf $log "[%s] [!] PID %d started...\n", (Class::Date->new(time())), $$;
if ( !-d($datadir) )
{
printf $log "[%s] [>] Creating data-dir \'%s\'...\n", (Class::Date->new(time())), $datadir;
mkdir($datadir,0755) or do{ printf $log "Failed! Error %d, exiting...\n", $!; die($!); };
}
# 1. Processing UTM RAW file(s)
foreach my $utmLogFile ( @ARGV )
{
if ( -s($utmLogFile)%76 == 0 )
{
my $raw = IO::File->new($utmLogFile) or printf $log 'Unable to fopen() UTM data-file \'%s\'', $utmLogFile;
my $i;
my $start = time();
printf $log "[%s] [<] Processing %s...\n", (Class::Date->new(time())), $utmLogFile;
until ( $raw->eof() )
{
$raw->read( undef, 4 ); # 1-4
$raw->read( my $src_ip, 4 ); # 5-8
$raw->read( my $dst_ip, 4 ); # 9-12
$raw->read( undef, 8 ); # 13-20
$raw->read( my $packets,4 ); # 21-24
$raw->read( my $bytes, 4 ); # 25-28
$raw->read( undef, 4 ); # 29-32
$raw->read( undef, 4 ); # 33-36
$raw->read( my $src_p, 2 ); # 37-38
$raw->read( my $dst_p, 2 ); # 39-40
$raw->read( undef, 16); # 41-56
$raw->read( my $acc_id, 4 ); # 57-60
$raw->read( undef, 4 ); # 61-64
$raw->read( my $tclass, 4 ); # 65-68
$raw->read( my $tstamp, 4 ); # 69-72
$raw->read( undef, 4 ); # 73-76
my $ts = Class::Date->new(unpack('%32I',$tstamp));
my $month = $ts->strftime('%Y%m');
my $date = $ts->strftime('%Y-%m-%d');
$acc_id = unpack('%32I',$acc_id);
$tclass = unpack('%32I',$tclass);
if ( $tclass == 0 )
{
$acc_id = 'NULL';
}
# 26 = 4 4 4 4 2 2 2 4
if ( my $rec = join(undef, $src_ip, $dst_ip, $packets, $bytes, $src_p, $dst_p, pack('S',$tclass), $tstamp) )
{
push @{$traf{'monthly'}->{$month}->{$acc_id}}, $rec if ( length($rec) == 26 );
}
$i++;
$src_ip = unpack("%32l" ,$src_ip);
$dst_ip = unpack("%32l" ,$dst_ip);
$traf{'daily'}->{$date}->{$acc_id}->{$dst_ip}->{packets} += unpack('%32I',$packets);
$traf{'daily'}->{$date}->{$acc_id}->{$dst_ip}->{bytes} += unpack('%32I',$bytes);
}
$raw->close();
printf $log "[%s] [!] Records completed: %d in %d seconds\n", (Class::Date->new(time())), $i, (time()-$start);
}
else
{
printf $log "[%s] [E] Log-file '%s' has incorrect format!\n", (Class::Date->new(time())), $utmLogFile;
}
}
# 2. Writing optimized GZipped DS-files into data-dir
printf $log "[%s] [>] Saving traffic log into gzipped binary DS-files...\n", (Class::Date->new(time()));
foreach my $period ( sort keys %{$traf{'monthly'}} )
{
my $path = sprintf '%s/%s', $datadir, $period;
if ( !-d($path) )
{
printf $log "[%s] [>] Creating data-dir \'%s\'...\n", (Class::Date->new(time())), $path;
mkdir($path,0755) or do{ printf $log "Failed! Error %d, exiting...\n", $!; die($!); };
}
foreach my $acc_id ( sort keys %{$traf{'monthly'}->{$period}} )
{
my $out = IO::Compress::Gzip->new(sprintf('%s/%s.ds.gz',$path,$acc_id),Append=>1);
foreach my $rec ( @{$traf{'monthly'}->{$period}->{$acc_id}} )
{
$out->write($rec);
}
$out->close();
}
}
# 3. Writing daily user statistics into database
printf $log "[%s] [>] Saving traffic into DB......\n", (Class::Date->new(time()));
my $start = time();
my $i;
foreach my $d ( sort keys %{$traf{'daily'}} )
{
foreach my $a ( sort keys %{$traf{'daily'}->{$d}} )
{
foreach my $ip ( sort keys %{$traf{'daily'}->{$d}->{$a}} )
{
$i++;
my $query = sprintf 'INSERT INTO `stat__daily` (`date`,`ip`,`utm_account_id`,`packets`,`bytes`)'
.' VALUES (\'%s\',%d,%s,%d,%d)'
.' ON DUPLICATE KEY'
.' UPDATE `utm_account_id`=%3$s,'
.'`packets`=`packets`+%4$d,'
.'`bytes`=`bytes`+%5$d;',
$d,
$ip,
$a,
$traf{'daily'}->{$d}->{$a}->{$ip}->{packets},
$traf{'daily'}->{$d}->{$a}->{$ip}->{bytes};
$dbh->do($query);
$dbh->do(sprintf('UPDATE `stat__daily` SET `utm_account_id`=%d WHERE `ip`=%d `utm_account_id` IS NULL AND `date`>=\'2011-01-01\'',$a,$ip)) if ($a ne 'NULL');
}
}
}
printf $log "[%s] [!] DB queries completed: %d in %d seconds\n", (Class::Date->new(time())), $i, (time()-$start);
printf $log "[%s] [!] PID %d completed...\n--\n", (Class::Date->new(time())), $$;
$log->close();
Использование
Итак, если Вы всё сделали правильно, Вам остаётся только ждать - наполнения таблицы - статистическими данными.
Далее, чтобы получить статистику по какому-то пользователю Вы можете использовать SQL-запросы
Получение статистики по IP за период:
Код: Выделить всё
SELECT `date`,`bytes` FROM `stat__daily` WHERE `ip`=inet_aton('127.0.0.1') AND `date` BETWEEN '2011-07-01' AND '2011-07-31' ORDER BY `date`
Если интересует, в последующих постах могу рассказать об анализе и подготовки детализированной статистики из полученных в результате обработки скриптом данных.
Если у Вас возникли вопросы/пожелания/предложения - пишите. Сюда или в личку.
Проект на GitHub, присоединяйтесь
