====== 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 [[http://search.cpan.org/~jrogers/Net-Telnet-3.04/lib/Net/Telnet.pm|Net::Telnet]] que hace de interfaz telnet). ~# apt-get install perl ~# apt-get install rrdtool ===== Script en Perl ===== El script realiza los siguientes pasos: - abre una conexión telnet al dispositivo que interesa monitorear - inicia una sesión con la contraseña del dispositivo - envía los comandos necesarios - guarda el resultado en una variable por cada resultado obtenido - utiliza expresiones regulares para capturar y guardar los valores que después se van a graficar - 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 [[http://oss.oetiker.ch/rrdtool/doc/rrdcgi.en.html|rrdcgi]] para configurar los gráficos. En mi caso, quedó de la siguiente manera #!/usr/bin/rrdcgi Line Stats for TD8816

TD8816 ADSL modem line and channel stats

Line Stats for SNR

.png --lazy --start - --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]" >

Line Stats for Attenuation

.png --lazy --start - --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]" >

Line Stats for Power

.png --lazy --start - --end now DEF:powdn=/mnt/hdd/web/html/rrdtool/TL8816.rrd:powd:AVERAGE AREA:powdn#0011AA22: LINE1:powdn#0011AA:"Downstream Power [dBm]" >

Line Stats for Bitrate

.png --lazy --start - --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]" >

Vale aclarar que '''' 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 '''', 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:
  1. last hour
  2. last day
  3. last week
  4. last month
En el [[http://oss.oetiker.ch/rrdtool/tut/rrdtutorial.en.html|tutorial de rrdtool]] también aparecen ejemplos de configuración de los gráficos.