Статистика при помощи snmp+mrtg+rrdtool во FreeBSD


Автор: Morbid


Вступление

Начнем с того, что клиенты часто просят помочь настроить статистику на FreeBSD. И задача, в основном, сводится к тому, чтобы посчитать все с точностью до мегабайта. Для "гуру", естественно, не составит труда снять статистику с того же ipfw, записать данные в базу, сделать выборку из нее и выдать информацию клиенту через веб. Но согласитесь, не совсем это простой путь и ко всему нужно вешать слишком много дополнительных сервисов. Для того чтобы как-то облегчить жизнь админам и конечным пользователям, я и решил написать, как легче всего организовать подсчет статистики.
Установка и настройка SNMP

Нам понадобится Несколько готовых решений. Во-первых, вебсервер Apache в связке с PHP. Во-вторых, MRTG последней версии. И от того же производителя чудный rrdtool. В-третьих, то, с помощью чего мы будем собирать статистику - net-snmp.

Предполагается, что Apache уже установлен и настроен, поэтому оставим его в покое на какое-то время и перейдем к сборке net-snmp:

% cd /usr/ports/net-mgmt/net-snmp
% make install clean

Затем, чтобы не сильно утруждать себя его конфигурированием, могу посоветовать сделать следущее:

% cd /usr/local/share/snmp
% ee snmpd.conf

и вписываем в конфиг следущие строки
syslocation Russia
sysservices 31region
syscontact admin@mehost.ru
rocommunity superpublic
com2sec public default public
group public v2c public
access public "" any noauth exact all none none

Нажимаем Esc, сохраняем сделанные изменения. В общем-то все. С snmpd мы справились, осталось его запустить и сделать так, чтобы он после перезагрузки мог функционировать нормально. Для этого проделываем следущее:

% cd /etc
% ee rc.conf

и добавляем в него строку
snmpd_enable="YES"

После этого запускаем сервис:

/usr/local/etc/rc.d/snmpd.sh start

и смотрим, есть ли snmpd в списке выполняемых процессов:

ps ax | grep snmp
742 ?? I 0:04.68 /usr/local/sbin/snmpd -p /var/run/snmpd.pid

Если snmpd отсутсвует в списке процессов, то нужно заменить в стартовом скрипте /usr/local/etc/rc.d/snmpd.sh строку
snmpd_enable=${snmpd_enable-"NO"}

на
snmpd_enable=${snmpd_enable-"YES"}

И после этого снова запускаем сервис /usr/local/etc/rc.d/snmpd.sh start

Видим, что snmpd запущен. Запоминаем, какую вы прописали community и приступаем к следующему этапу - сборке mrtg и rrdtool.
Установка и настройка MRTG и RRDTool

% cd /usr/ports/net-mgmt/mrtg
% make install clean

затем

% cd /usr/ports/net/rrdtool
% make install clean

После того как все собрано и установлено приступаем к правке конфигов.

Cразу подумаем, куда мы будем складывать логи. Я предпочитаю /var/spool/mrtg, так что

% mkdir /var/spool/mrtg

и правим права на доступ

chmod 775 /var/spool/mrtg

далее идем и правим конфиг mrtg

% cd /usr/local/etc/mrtg
% ee mrtg.cfg

Нам нужно исправить Global Config Options на следующее:
WorkDir: /var/spool/mrtg
LogFormat: rrdtool
RunAsDaemon: no
PathAdd: /usr/local/bin
EnableIPv6: no

Сохраним файл и приступим собственно к созданию конфига для учета статистики. Для создания простого конфига существует утилита cfgmaker. Допустим ваш сервер имеет внутренний IP 192.168.0.1, тогда делаем следущее:

% cd /usr/local/etc/mrtg
% cfgmaker superpublic@192.168.0.1 > router.cfg

Будет создан необходимый конфиг. Открываем его и проверяем; если все в порядке, то открываем файл mrtg.cfg и вписываем в него следущее
include: /usr/local/etc/mrtg/router.cfg

Далее добавляем к заданиям cron следущее
* * * * /usr/local/bin/mrtg /usr/local/etc/mrtg/mrtg.cfg >/dev/null

Перестартовываем cron, ждем 5 минут, идем в /var/spool/mrtg и смотрим что там есть, если появились файлы, то все ворядке идет опрос, статистика считается и остается только одно сделать выборку из rrd-файлов и выложить это в веб для наглядности.

Теперь возвращаемся к вебсерверу. Те, кому не лень, могут сделать виртуальный хост, чтобы было что-то вроде stats.myhost.ru. Кому лень, могут просто сделает папку на сушествующем сервере и создать в этой папке пустой файл index.php. Как-то я наткнулся на замечательный скрипт (см. Приложение), который делает выборку из rrd-файлов. К сожалению ни страницу автора, ни самого автора я не запомнил.

Добавляем весь этот код в файл index.php, заходим на свой вебсервер в необходимый каталог и наслаждаемся статистикой.
Приложение
<?php

/* The directory where the rrd files are located */
$dir = '/var/spool/mrtg';

/* List all devices that MRTS should'n display, */
$exclude = array('secret', 'topsecret');

/* RRDtool path - where are the the executable located */
$rrdcommand = '/usr/local/bin/rrdtool';

/* Change this to get another top on the site */
function top($name)
{ ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title><?php echo $GLOBALS['title']; ?></title>
<style type="text/css">
<!--
.datahead,.total1head,.total2head,.data,.total1,.total2
{
border: 1px solid black;
width: 50px;
}
.datahead,.total1head,.total2head
{
font-size: 12px;
font-weight: bold;
}
.data,.total1,.total2
{
font-size: 10px;
}
.total1,.total1head
{
background-color: #dddddd;
}
.total2,.total2head
{
background-color: #bbbbbb;
}
h1,h2,h3
{
text-align: center;
}
-->
</style>
</head>
<body>
<h1><?php echo $GLOBALS['title']; ?></h1>
<h2><?php echo $name; ?></h2>
<?php }

/* Change this to get another bottom on the site */
function bottom()
{ ?>
<hr>
Created by <a href="??????"><?php echo $GLOBALS['title'] ?></a>
</body>
</html>
<?php }

/***************************************************************************
* Variables *
***************************************************************************/

/* File extension of the MRTG-RRD-files */
$extension = '.rrd';

/* This version */
$version = 'ru';

/* The title */
$title = "me.host.$version";

/***************************************************************************
* Functions *
***************************************************************************/
/* Checks if a name is a valid device name */
function validname($name)
{
return in_array($name, $GLOBALS['legalnames']);
} //function validname($name)

/* Convert a device name to a file name */
function filename($name)
{
return $GLOBALS['dir'].'/'.$name.$GLOBALS['extension'];
} //function filename($name)

/* Formats a number with KB, MB etc. */
function humanreadable($size)
{
$names = array('B', 'KB', 'MB', 'GB', 'TB');
$times = 0;
while($size>1024)
{
$size = round(($size*100)/1024)/100;
$times++;
}
return "$size " . $names[$times];
} //function humanreadable($size)

/* Convert a month number to a month name */
function monthname($no)
{
$names = array(1 => 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
return $names[$no];
} //function monthname($no)

/* Convert year and month number to a string useful for rrdtool */
function monthstartend($year, $month)
{
$start = mktime(0, 0, 0, $month, 1, $year);
if($month==12)
$end = mktime(0, 0, 0, 1, 1, $year+1);
else
$end = mktime(0, 0, 0, $month+1, 1, $year);
return " -s $start -e $end ";
}

/* Output HTML for a year */
function showyear($year, $months)
{
$sumyear = array();
$sumquater = array();

printf("<h3>Year: %s</h3> ", $year);
printf("<table><tr><td></td> ");

for($quater=1; $quater<=4; $quater++)
{
/* month */
for($i=($quater-1)*3+1; $i<=($quater-1)*3+3; $i++)
{
printf("<td class="datahead"><a href="%s?name=%s&amp;year=%s&amp;month=%s">%s (%s)</a></td> ", $_SERVER['SCRIPT_NAME'], $_GET['name'], $year, $i, monthname($i), $i);
}
/* quater */
printf("<td class="total1head">Quater %s</td> ", $quater);
}
/* year */
printf("<td class="total2head">Year</td> ");

printf("</tr><tr><td class="datahead">In</td> ");

for($quater=1; $quater<=4; $quater++)
{
/* month */
for($i=($quater-1)*3+1; $i<=($quater-1)*3+3; $i++)
{
printf("<td class="data">%s</td> ", humanreadable($months[$i]['in']));
$sumyear['in'] += $months[$i]['in'];
$sumquater[$quater]['in'] += $months[$i]['in'];
}
/* quater */
printf("<td class="total1">%s</td> ", humanreadable($sumquater[$quater]['in']));
}
/* year */
printf("<td class="total2">%s</td> ", humanreadable($sumyear['in']));

printf("</tr><tr><td class="datahead">Out</td> ");

for($quater=1; $quater<=4; $quater++)
{
/* month */
for($i=($quater-1)*3+1; $i<=($quater-1)*3+3; $i++)
{
printf("<td class="data">%s</td> ", humanreadable($months[$i]['out']));
$sumyear['out'] += $months[$i]['out'];
$sumquater[$quater]['out'] += $months[$i]['out'];
}
/* quater */
printf("<td class="total1">%s</td> ", humanreadable($sumquater[$quater]['out']));
}
/* year */
printf("<td class="total2">%s</td> ", humanreadable($sumyear['out']));

printf("</tr><tr><td class="datahead">Max</td> ");

for($quater=1; $quater<=4; $quater++)
{
/* month */
for($i=($quater-1)*3+1; $i<=($quater-1)*3+3; $i++)
{
printf("<td class="data">%s</td> ", humanreadable(max($months[$i]['in'], $months[$i]['out'])));
}
/* quater */
printf("<td class="total1">%s</td> ", humanreadable(max($sumquater[$quater]['in'], $sumquater[$quater]['out'])));
}
/* year */
printf("<td class="total2">%s</td> ", humanreadable(max($sumyear['in'], $sumyear['out'])));

printf("</tr><tr><td class="datahead">Sum</td> ");

for($quater=1; $quater<=4; $quater++)
{
/* month */
for($i=($quater-1)*3+1; $i<=($quater-1)*3+3; $i++)
{
printf("<td class="data">%s</td> ", humanreadable($months[$i]['in'] + $months[$i]['out']));
}
/* quater */
printf("<td class="total1">%s</td> ", humanreadable($sumquater[$quater]['in'] + $sumquater[$quater]['out']));
}
/* year */
printf("<td class="total2">%s</td> ", humanreadable($sumyear['in'] + $sumyear['out']));

printf("</tr></table> ");

} //function showyear($year, $months)

/* Output HTML for a month */
function showmonth($year, $month, $days)
{
$summonth = array();
$daysinmonth = date("t", mktime(0, 0, 0, $month, 1, $year));

printf("<h3>Month: %s %s</h3> ", $year, monthname($month));
for($j=1; $j<=2; $j++)
{
if($j==1)
{
$start = 1;
$end = 16;
}
else
{
$start = 17;
$end = $daysinmonth;
}

printf("<table><tr><td></td> ");

for($i=$start; $i<=$end; $i++)
{
printf("<td class="datahead">%s</td> ", $i);
}
if($j==2)
{
printf("<td class="total2head">Month</td> ");
}

printf("</tr><tr><td class="datahead">In</td> ");

for($i=$start; $i<=$end; $i++)
{
printf("<td class="data">%s</td> ", humanreadable($days[$i]['in']));
$summonth['in'] += $days[$i]['in'];
}
if($j==2)
{
printf("<td class="total2">%s</td> ", humanreadable($summonth['in']));
}

printf("</tr><tr><td class="datahead">Out</td> ");

for($i=$start; $i<=$end; $i++)
{
printf("<td class="data">%s</td> ", humanreadable($days[$i]['out']));
$summonth['out'] += $days[$i]['out'];
}
if($j==2)
{
printf("<td class="total2">%s</td> ", humanreadable($summonth['out']));
}

printf("</tr><tr><td class="datahead">Max</td> ");

for($i=$start; $i<=$end; $i++)
{
printf("<td class="data">%s</td> ", humanreadable(max($days[$i]['in'], $days[$i]['out'])));
}
if($j==2)
{
printf("<td class="total2">%s</td> ", humanreadable(max($summonth['in'], $summonth['out'])));
}

printf("</tr><tr><td class="datahead">Sum</td> ");

for($i=$start; $i<=$end; $i++)
{
printf("<td class="data">%s</td> ", humanreadable($days[$i]['in'] + $days[$i]['out']));
}
if($j==2)
{
printf("<td class="total2">%s</td> ", humanreadable($summonth['in'] + $summonth['out']));
}

printf("</tr></table> ");

} //for($j=1; $j<=2; $j++)

} //showmonth($year, $month, $days)

/***************************************************************************
* All the rest *
***************************************************************************/

/* Find legalnames */
$legalnames = array();
if($dirhandler = @opendir($dir))
{
while(($filename = readdir($dirhandler)) !== false)
{
if(ereg("$extension$", $filename))
{
$filename = substr($filename, 0, -strlen($extension));
if(!in_array($filename, $exclude))
{
$legalnames[] = $filename;
}
}
}
closedir($dirhandler);
}

/* If a device have been chosen */
if(isset($_GET['name']))
{
/* If the device name is valid */
if(validname($_GET['name']))
{
/* If the script should generate a picture */
if(isset($_GET['picture']))
{
$name = filename($_GET['name']);

header("content-type: image/png");

$rrdcommand = "$rrdcommand graph - -v 'Bytes/s' -b 1024 -w 390 DEF:avgin=$name:ds0:AVERAGE AREA:avgin#00CC00:'Traffic in' DEF:avgout=$name:ds1:AVERAGE LINE2:avgout#0000FF:'Traffic out'";
/* Last day */
if($_GET['period']=='day')
{
$rrdcommand .= ' -t "Traffic the last day" -s -86400';
}
/* Last week */
else if($_GET['period']=='week')
{
$rrdcommand .= ' -t "Traffic the last week" -s -604800';
}
/* Last month */
else if($_GET['period']=='month')
{
$rrdcommand .= ' -t "Traffic the last month" -s -2678400';
}
/* Last year */
else if($_GET['period']=='year')
{
$rrdcommand .= ' -t "Traffic the last year" -s -31622400';
}
/* If year and month is supplied, then generate picture for that month */
else if(is_numeric($_GET['year']) && is_numeric($_GET['month']))
{
$name = monthname($_GET['month']) . ' ' . $_GET['year'];
$rrdcommand .= " -t 'Traffic for $name' " . monthstartend($_GET['year'], $_GET['month']);
$rrdcommand .= " -x DAY:1:WEEK:1:DAY:1:86400:%d ";
}

echo `$rrdcommand`;

} //if(isset($_GET['picture']))
/* If year and month is supplied, then generate page for that month */
else if(is_numeric($_GET['year']) && is_numeric($_GET['month']))
{
echo top($_GET['name']);

$name = monthname($_GET['month']) . ' ' . $_GET['year'];

printf("<img src="%s?name=%s&amp;year=%s&amp;month=%s&amp;picture=yes" alt="%s">", $_SERVER['SCRIPT_NAME'], $_GET['name'], $_GET['year'], $_GET['month'], $name);

$lastdate = 0;
$days = array();

/* Get statistics for the selected month */
if($fp = popen("$rrdcommand fetch " . filename($_GET['name']) . " AVERAGE -r 864000 ".monthstartend($_GET['year'], $_GET['month']), 'r'))
{
fgets($fp, 4096);
while(!feof($fp))
{
$line = trim(fgets($fp, 4096));

if($line != '')
{
list($date, $in, $out) = split('( )+', $line);
list($date) = split(':', $date);
if($lastdate != 0)
{
if(!is_numeric($in))
$in = 0;
if(!is_numeric($out))
$out = 0;

$in = $in*($date-$lastdate);
$out = $out*($date-$lastdate);

if($_GET['month'] == date('n', $lastdate) && $_GET['year'] == date('Y', $lastdate))
{
$day = date('j', $lastdate);
$days[$day]['in'] += $in;
$days[$day]['out'] += $out;
}

} //if($lastdate != 0)

$lastdate = $date;

} //if($line != '')
} //while(!feof($fp))

showmonth($_GET['year'], $_GET['month'], $days);

pclose($fp);

} //if($fp = popen($test, 'r'))

echo bottom();
}
/* Else generate main device page */
else
{
echo top($_GET['name']);

/* Find out when the database was last updated */
if($fp = popen("$rrdcommand info " . filename($_GET['name']), 'r'))
{
$key = '';
while(!feof($fp))
{
list($key, $value) = split(' = ', trim(fgets($fp, 4096)));
if($key == 'last_update')
{
printf("Last updated: %s<br> ", date("Y-m-d H:i:s", $value));
break;
}
}
pclose($fp);
}

printf("<img src="%s?name=%s&period=day&picture=yes" alt="Dayly">", $_SERVER['SCRIPT_NAME'], $_GET['name']);
printf("<img src="%s?name=%s&period=week&picture=yes" alt="Weekly">", $_SERVER['SCRIPT_NAME'], $_GET['name']);
printf("<img src="%s?name=%s&period=month&picture=yes" alt="Monthly">", $_SERVER['SCRIPT_NAME'], $_GET['name']);
printf("<img src="%s?name=%s&period=year&picture=yes" alt="Yearly"> ", $_SERVER['SCRIPT_NAME'], $_GET['name']);

$lastdate = 0;
$months = array();

/* Get statistics for the last two year */
if($fp = popen("$rrdcommand fetch " . filename($_GET['name']) . " AVERAGE -s -63331200 -e +31622400", 'r'))
{
fgets($fp, 4096);
while(!feof($fp))
{
$line = trim(fgets($fp, 4096));
if($line != '')
{

list($date, $in, $out) = split('( )+', $line);
list($date) = split(':', $date);
if($lastdate != 0)
{

if(!is_numeric($in))
$in = 0;
if(!is_numeric($out))
$out = 0;

$in = $in*($date-$lastdate);
$out = $out*($date-$lastdate);

$year = date('Y', $lastdate);
$month = date('n', $lastdate);
$months[$year][$month]['in'] += $in;
$months[$year][$month]['out'] += $out;

} //if($lastdate != 0)

$lastdate = $date;

} //if($line != '')
} //while(!feof($fp))

$year = date('Y');
showyear($year, $months[$year]);

$year = date('Y')-1;
showyear($year, $months[$year]);

pclose($fp);

} //if($fp = popen($test, 'r'))

echo bottom();

} //else

} //if(validname($_GET['name']))
/* If device name has been provided, but it is not valid */
else
{
printf("Don't do that");
} //else

} //if(isset($_GET['name']))
/* If device name has been given, show the main page */
else
{
echo top('All devices');

foreach($legalnames as $name)
{
printf("<a href="%s?name=%s">%s</a><br> ", $_SERVER['SCRIPT_NAME'], $name, $name);
}

echo bottom();

} //else

?>

http://wiki.bsdportal.ru/doc:mrtg2

Обновлено: 12.03.2015