User Tools

Site Tools


obtener_parametros_por_telnet_y_graficarlos_con_rrdtool

Graficando respuestas Telnet de dispositivo con RRDtool

Esta guía se basa en capturar datos que únicamente estén disponibles por Telnet, ya que de estar disponibles los datos por SNMP, se puede utilizar MRTG que es más directo.

La idea en este caso es monitorear el estado de un modem ADSL Tp-Link (TD-8816) que tiene un chipset Trendchip. Al no tener disponibles por SNMP la información del estado de la conexión ADSL, se puede obtener esta información (SNR, atenuación, potencia, bitrate) automatizando consultas por telnet para luego con un script en Perl, tomar los valores de interés y utilizando RRDtool almacenar estos datos en una base round-robin que se utilizará para graficarlos en función del tiempo en el período que resulte conveniente con un script CGI. La base de datos mantiene siempre el mismo tamaño, por lo que puede utilizarse en equipos con poco almacenamiento.

Me basé en un tutorial para modems SpeedTouch muy parecido: https://www.dslreports.com/forum/r21063176-Line-stats-monitoring-with-RRDTool-or-MRTG que me pareció que valía la pena ampliar y explicar más detalladamente algunas cosas.

Requisitos previos

Posibilidad de login y enviar comandos telnet a un dispositivo desde el dispositivo de interrogación (probablemente el mismo web server).

Instalación del software

Para que funcione es necesario tener instalados Perl y RRDtool (ya trae Net::Telnet que hace de interfaz telnet).

~# apt-get install perl
~# apt-get install rrdtool

Script en Perl

El script realiza los siguientes pasos:

  1. abre una conexión telnet al dispositivo que interesa monitorear
  2. inicia una sesión con la contraseña del dispositivo
  3. envía los comandos necesarios
  4. guarda el resultado en una variable por cada resultado obtenido
  5. utiliza expresiones regulares para capturar y guardar los valores que después se van a graficar
  6. actualiza la base de datos round-robin
#!/usr/bin/perl
  
use Net::Telnet ();
use RRDs;
  
$host = '192.168.100.1';

$pass = '[administrator password, "admin" by default]';
  
$RRD_FILE="/mnt/hdd/web/html/rrdtool/TL8816.rrd";

$STEP = 300;

$HEARTBEAT = 600;
  
$t = new Net::Telnet (  Timeout => 10,
			Errmode => 'die',
			Dump_log => '/mnt/hdd/web/html/rrdtool/dump.log',
			Input_log  => '/mnt/hdd/web/html/rrdtool/input.log');
  
# Open and login
$t->open($host);
$t->waitfor('/Password: $/i');
$t->print($pass);
$t->waitfor('/TP-LINK> $/i');
# make lists with the responses and close connection  
@result_line_near = $t->cmd('wan adsl linedata near');
@result_line_far = $t->cmd('wan adsl linedata far');
@result_chan = $t->cmd('wan adsl chandata');
$t->close();
  
# initialize vars  
$snr_down="U";
$snr_up="U";
$att_down="U";
$att_up="U";
$pwr_down="U";
$bitrate_up="U";
$bitrate_down="U";


# take the values matching the conditions

while(@result_line_near)
{
	if (@result_line_near[0] =~ /noise margin downstream:\s+(\d*.\d*)\s+/)
	{
		$snr_down=$1;
	}
	if (@result_line_near[0] =~ /attenuation downstream:\s+(\d*.\d*)\s+/)
        {
        	$att_down=$1;
        }
shift(@result_line_near)
}

while(@result_line_far)
{
        if (@result_line_far[0] =~ /noise margin upstream:\s+(\d*.\d*)\s+/)
        {
        	$snr_up=$1;
        }
        if (@result_line_far[0] =~ /attenuation upstream:\s+(\d*.\d*)\s+/)
        {
        	$att_up=$1;
        }
	if (@result_line_far[0] =~ /output power downstream:\s+(\d*.\d*)\s+/)
        {
        	$pwr_down=$1;
        }
shift(@result_line_far)
}

while(@result_chan)
{

	if (@result_chan[0] =~ /near-end interleaved channel bit rate:\s+(\d*.\d*)\s+/)
        {
	        $bitrate_down=$1;
        }
	if (@result_chan[0] =~ /far-end interleaved channel bit rate:\s+(\d*.\d*)\s+/)
        {
        	$bitrate_up=$1;
        }

shift(@result_chan);
}

# Debug options
#print " snrdown: $snr_down \n snrup: $snr_up \n attdown: $att_down \n attup: $att_up \n pwrdown: $pwr_down \n bitratedown: $bitrate_down \n bitrateup: $bitrate_up \n";

# if $RRD_FILE doesn't exist, create it
if (not -e $RRD_FILE) 
{
    RRDs::create("$RRD_FILE",
		"--start=N",
		"--step=$STEP",
                            "DS:snrd:GAUGE:$HEARTBEAT:0:U",
                            "DS:snru:GAUGE:$HEARTBEAT:0:U",
                            "DS:attd:GAUGE:$HEARTBEAT:0:U",
                            "DS:attu:GAUGE:$HEARTBEAT:0:U",
                            "DS:powd:GAUGE:$HEARTBEAT:0:U",
                            "DS:rated:GAUGE:$HEARTBEAT:0:U",
                            "DS:rateu:GAUGE:$HEARTBEAT:0:U",
                            "RRA:AVERAGE:0.5:1:1000",
                            "RRA:AVERAGE:0.5:6:1000",
                            "RRA:AVERAGE:0.5:24:1000",
                            "RRA:AVERAGE:0.5:288:1000");
}


# update the database
RRDs::update("$RRD_FILE","N:$snr_down:$snr_up:$att_down:$att_up:$pwr_down:$bitrate_down:$bitrate_up");

Guardar el código en algún directorio. El nombre no importa, en mi caso se llama 'linechan.pl'.

Algunas aclaraciones sobre el código:

$STEP está en segundos y por defecto toma el valor 300. Es importante que el cron corra el script cada $STEP para que las mediciones sean correctas, sobre todo si se usan contadores del tipo 'COUNTER' o 'DERIVE' que utilizan el tiempo de $STEP para calcular los valores que se van a representar después.

$HEARTBEAT está en segundos e indica que si no llega ningún dato válido cada $HEARTBEAT, entonces en la base de datos se inserta un unknown en vez de inventar un valor (e.g. cero).

Dump_log e Input_log son útiles para debug, muestran la última respuesta que envió el dispositivo interrogado. Se pueden omitir para reducir la escritura en disco.

shift() desplaza la lista. Aparentemente si la respuesta fuese una cadena $chain en vez de una lista @list, se podría omitir el while(), el if() y el shift(), ya que se puede crear la nueva variable asignando el valor que devuelve la búsqueda con regex.

Debug options se pueden habilitar para mostrar en la consola los valores de las variables.

La documentación de RRDtool está acá: http://oss.oetiker.ch/rrdtool/doc/index.en.html y un tutorial detallado explicando el uso: http://oss.oetiker.ch/rrdtool/tut/rrdtutorial.en.html

Ahora hay que crear la tarea para que la ejecute crontab cada $STEP segundos.

Cron

Simplemente hay que decirle que ejecute el script en el lapso de tiempo de $STEP. En mi caso son 300 segundos, lo que equivale a 5 minutos.

Editar el crontab…

 ~# crontab -e 

y anexar

 */5 * * * * perl /path/to/script/linechan.pl 

donde '*/5' indica que corre cada 5 minutos. Una vez guardado no es necesario reiniciar.

Ya debería estar guardando los valores en la base de datos cada $STEP. Falta poder visualizar los gráficos.

Configurando CGI

Common Gateway Interface (CGI) funciona en Lighttpd sin necesidad de instalar plugins, solamente hay que habilitar el módulo CGI, incluir el archivo de configuración y asegurarse que el intérprete del lenguaje esté instalado (i.e. para interpretar python debería estar instalado python).

Crear /etc/lighttpd/conf.d/cgi.conf y agregar:

server.modules += ( "mod_cgi" )

cgi.assign                 = ( ".pl"  => "/usr/bin/perl",
                               ".cgi" => "/usr/bin/perl",
                               ".rb"  => "/usr/bin/ruby",
                               ".erb" => "/usr/bin/eruby",
                               ".py"  => "/usr/bin/python",
                               ".php" => "/usr/bin/php-cgi" )

index-file.names           += ( "index.pl",   "default.pl",
                               "index.rb",   "default.rb",
                               "index.erb",  "default.erb",
                               "index.py",   "default.py",
                               "index.php",  "default.php" )

En el archivo de configuración de Lighttpd, /etc/lighttpd/lighttpd.conf agregar:

include "conf.d/cgi.conf"

de esta manera, lighttpd llama al intérprete cada vez que le piden abrir por ejemplo un archivo .cgi.

Script CGI

Es muy simple, solamente hay que leer la documentación de rrdcgi para configurar los gráficos.

En mi caso, quedó de la siguiente manera

#!/usr/bin/rrdcgi
 <HTML>
 <HEAD><TITLE>Line Stats for TD8816</TITLE></HEAD>
 <BODY>


 <H1>TD8816 ADSL modem line and channel stats</H1>
 <H2>Line Stats for SNR</H2>
 <P>
 <RRD::GOODFOR 300>
 <RRD::GRAPH TD8816SNR<RRD::CV span>.png
        --lazy
        
        --start -<RRD::CV span>
        --end now
          DEF:snrdown=/mnt/hdd/web/html/rrdtool/TL8816.rrd:snrd:AVERAGE
          DEF:snrup=/mnt/hdd/web/html/rrdtool/TL8816.rrd:snru:AVERAGE
          AREA:snrdown#2A3CFF22:
          AREA:snrup#00FF0022:
          LINE1:snrdown#0000BB:"Downstream [db]"
          LINE1:snrup#00BB00:"Upstream [db]"
 >
 </P>



 <H2>Line Stats for Attenuation</H2>
 <P>
 <RRD::GOODFOR 300>
 <RRD::GRAPH TD8816attenuation<RRD::CV span>.png
        --lazy
        
        --start -<RRD::CV span>
        --end now
          DEF:attdown=/mnt/hdd/web/html/rrdtool/TL8816.rrd:attd:AVERAGE
          DEF:attup=/mnt/hdd/web/html/rrdtool/TL8816.rrd:attu:AVERAGE
          AREA:attdown#8030F022:
          AREA:attup#0A551822:
          LINE1:attdown#8030F0:"Downstream [db]"
          LINE1:attup#0A5518:"Upstream [db]"
  >

 </P>



 <H2>Line Stats for Power</H2>
 <P>
 <RRD::GOODFOR 300>
 <RRD::GRAPH TD8816power<RRD::CV span>.png
        --lazy
        
        --start -<RRD::CV span>
        --end now
          DEF:powdn=/mnt/hdd/web/html/rrdtool/TL8816.rrd:powd:AVERAGE
          AREA:powdn#0011AA22:
          LINE1:powdn#0011AA:"Downstream Power [dBm]"
          
 >
 </P>



 <H2>Line Stats for Bitrate</H2>
 <P>
 <RRD::GOODFOR 300>
 <RRD::GRAPH TD8816brate<RRD::CV span>.png
        --lazy
        
        --start -<RRD::CV span>
        --end now
          DEF:ratedown=/mnt/hdd/web/html/rrdtool/TL8816.rrd:rated:AVERAGE
          DEF:rateup=/mnt/hdd/web/html/rrdtool/TL8816.rrd:rateu:AVERAGE
          AREA:ratedown#00A27722:
          AREA:rateup#A2770022:
          LINE1:ratedown#00A277:"Downstream [Kbps]"
          LINE1:rateup#A27700:"Upstream [Kbps]"
          
 >
 </P>

 </BODY>
 </HTML>

Vale aclarar que <RRD::CV span> sirve para llamar al script con los parámetros en la misma URL, por ejemplo si yo pido http://midominioodireccionIP/cgi-bin/stats.cgi?span=1d, donde el código tenga un marcador <RRD::CV span>, se va a interpretar como 1d. De esta forma no es necesario generar un archivo .cgi por cada período de tiempo que se desee graficar. Por ejemplo se puede referenciar de la siguiente forma en un html:

<ol>
             <li><a href="stats.cgi?span=1h">last hour</a></li>
             <li><a href="stats.cgi?span=1d">last day</a></li>
             <li><a href="stats.cgi?span=1w">last week</a></li>
             <li><a href="stats.cgi?span=1m">last month</a></li>
             
</ol>

En el tutorial de rrdtool también aparecen ejemplos de configuración de los gráficos.

obtener_parametros_por_telnet_y_graficarlos_con_rrdtool.txt · Last modified: 2024/10/17 21:42 by 127.0.0.1