RRDtool

Las estadísticas gráficas eran, y en cierto modo siguen siendo, una de mis tantas asignaturas pendientes. Estos últimos días he estado echando un vistazo a la potentísima herramienta RRDTOOL. Con esta utilidad, derivada de MRTG, podremos crear gráficos de todo lo imaginable, para ejemplo un botón:

Entre la ayuda de unos, otros y un poco de tiempo intentaré explicar una mínima parte de la potencia de esta herramienta creando un ejemplo para amenizar la lectura. Entremos en materia... Nuestro trabajo consistirá principalmente en 3 tareas. Debemos comenzar con la creación de la base de datos que hará de fuente de datos en nuestra generada. A continuación debemos proveer de datos a esa base, para ello desarrollaremos un script ejecutable (cualquier lenguaje de programación es válido, usaremos bash). Por último haremos otro script encargado de crear un gráfico similar al de la imagen anterior. En un pequeño inciso he de aclarar que, en mi caso, los pasos 2 y 3 los he fundido en un solo script por comodidad, quizás no sea óptimo del todo:

1.- Creación de la base de datos

RRDTOOL significa "herramienta de bases de datos en Round Robin", donde "Round Robin" es una técnica que implica un número de datos fijos y un puntero al dato más reciente. Imaginemos un círculo con puntos en su interior, cada punto es un dato y una flecha apunta desde el centro del círculo al dato más reciente; cuando en el círculo no se pueda dibujar ningún punto más, empezaremos a reutilizar los más antiguos, con lo que no hay crecimiento de tamaño en la base de datos ni requerirá de algún tipo de mantenimiento. Este rollo explica el funcionamiento del motor de bases de datos Round Robin del cual nos vamos a servir.

Una vez dicho esto vamos a ejecutar "rrdtool create" para crear nuestra base de datos, pongamos como ejemplo que queremos una gráfica que represente el porcentaje de memoria usada y libre para saber en todo momento el estado de la RAM de nuestro equipo:

#!/bin/sh

rrdtool create memoria.rrd --start `date +%s` --step 300
        DS:mem_total:GAUGE:600:0:U
        DS:mem_usada:GAUGE:600:0:U 
        RRA:AVERAGE:0.5:1:600 
        RRA:AVERAGE:0.5:6:700 
        RRA:AVERAGE:0.5:24:775
        RRA:AVERAGE:0.5:288:797 
        RRA:MAX:0.5:1:600 
        RRA:MAX:0.5:6:700 
        RRA:MAX:0.5:24:775
        RRA:MAX:0.5:288:797
        RRA:MIN:0.5:1:600
        RRA:MIN:0.5:6:700
        RRA:MIN:0.5:24:775
        RRA:MIN:0.5:288:797

- "rrdtool create memoria.rrd --start `date +%s` --step 300": Creamos una base de datos llamada memoria.rrd, indicamos que empezaremos a introducir datos a partir del momento de creación (--start `date +%s`) y por último anotamos que se alimentará de datos cada 5 minutos (--step 300), período que ha de coincidir con el script del paso 2 programado en cron que posteriormente veremos.

- "DS:mem_total:GAUGE:600:0:U": Añadimos una fuente de datos (Data Source) llamada "mem_total" de tipo "GAUGE". Hay varios tipos de datos GAUGE, COUNTER, DERIVE y ABSOLUTE, *mirar en man*. Posteriormente indicamos que si en 600 segundos no se recibe dato alguno se almacenará Unknown. Los últimos dos valores son el máximo y el mínimo que se puede guardar en este DS, refiriéndonos con U a Unlimited, (sin límite si es que no lo conocemos). Análogamente creamos el segundo origen de datos: "DS:mem_usada:GAUGE:600:0:U". Es sencillo una vez analizado :).

- "RRA:AVERAGE:0.5:1:600": Ahora añadimos los archivos Round Robin (RRA), cuya función en nuestro caso será organizar los datos de media, máximo y mínimo (AVERAGE, MAX y MIN) en función del tiempo para poder crear gráficas diarias, semanales, mensuales... El 0.5 indica lo que debe hacer cuando encuentre un valor desconocido (Unknown, no tengo más información de este valor), 1 indica de cuantos valores se ha de hacer la media (uno solo en este caso) y 600 indica cuantos valores calculados se guardarán en el RRA. Esto es un pelín complejo de entender, quizás con otro ejemplo quede suficientemente claro: "RRA:AVERAGE:0.5:24:775". En este RRA calcularemos la media de los 24 valores acumulados, si nos fijamos veremos que tenemos 24 valores, si tomamos 1 valor cada 300 segundos (5 minutos): 24 x 5 = 120 min. = 2 h., tendremos los datos para la gráfica de 2h. He ahí que los RRA usados sean practicamente standard. Con los valores 1, 6, 24 y 288 están cubiertas nuestras necesidades. Repetimos los RRA para MAX y MIN siempre y cuando nos interese quedarnos con los valores máximo y mínimo en ese intervalo, de no ser así podrían suprimirse dichas líneas.

2.- Creación del script que alimenta de datos la base Round Robin

Una vez creada la base de datos ahora toca llenarla con datos. Como hemos indicado más arriba, los datos serán guardados cada 300 segundos de intervalo (--step 300), por lo que desarrollaremos un script que irá al cron cumpliendo nuestras exigencias. El script será similar al siguiente:

#!/bin/sh

NOW=`date +%s`
CMD=`free | grep Mem: | awk '{ print $2":"$3 }'`

rrdtool update memoria.rrd $NOW:$CMD

Sencillo, ¿verdad?. Simplemente agregamos los datos de la forma TIMESTAMP:MEM_TOTAL:MEM_USADA, para ello usamos el comando "free | grep Mem: | awk '{ print $2":"$3" }'", que formatea los datos según lo requerido. El "rrdtool update" se encarga de introducir los datos en "memoria.rrd".

3.- La esperada gráfica

Ahora que tenemos la base con datos en su poder, podemos crear la gráfica para, poco a poco, ir viendo los resultados en pantalla :). Este script también se pondrá en el cron del sistema, puesto que cada vez que se añadan nuevos datos a la base, generaremos una gráfica nueva (de ahí que antes hubiera mencionado que, por comodidad, he juntado los pasos 2 y 3 en uno único, pero eso lo explicaré al final). El script que genera la gráfica es el siguiente:

#!/bin/sh

RRDB=memoria.rrd
HOSTNAME=`hostname -a`

# Variables de tiempo
NOW=`date +%s`
ONE_HOUR_AGO=$(($NOW-3600))
ONE_DAY_AGO=$(($NOW-86400))
ONE_WEEK_AGO=$(($NOW-604800))
ONE_MONTH_AGO=$(($NOW-2419200))
ONE_YEAR_AGO=$(($NOW-29030400))

# Funcion que hace graficas
function draw_graphic()
{
        rrdtool graph $1 -s $2 -e $3 -a PNG 
        --title "Uso de memoria para $4" 
        --base=1000 
        --width=600 
        --height=120 
        --alt-autoscale-max 
        --lower-limit=0 
        --vertical-label="Carga de memoria (%)"
        DEF:mem_total=$RRDB:mem_total:AVERAGE 
                CDEF:mem_total2=mem_total,1024,* AREA:mem_total2#3FE719:"Mem. Total " 
                LINE1:mem_total#31B314 
        DEF:mem_usada=$RRDB:mem_usada:AVERAGE 
                CDEF:mem_usada2=mem_usada,1024,* AREA:mem_usada2#FF7A7A:"Mem. Usada "
                LINE1:mem_usada#B35656 
}

draw_graphic '1memoria_last_hour.png' $ONE_HOUR_AGO $NOW "$HOSTNAME (ultima h.)"
draw_graphic '2memoria_last_day.png' $ONE_DAY_AGO $NOW "$HOSTNAME (ultimas 24h.)"
draw_graphic '3memoria_last_week.png' $ONE_WEEK_AGO $NOW "$HOSTNAME (semanal)"
draw_graphic '4memoria_last_month.png' $ONE_MONTH_AGO $NOW "$HOSTNAME (mensual)"
draw_graphic '5memoria_last_year.png' $ONE_YEAR_AGO $NOW "$HOSTNAME (anual)"

Parece más complejo de lo que realmente es. Empezamos definiendo algunas variables que harán más bonito el gráfico ($HOSTNAME) y más simple la función draw_graphic ($NOW, $ONE_HOUR_AGO...). A continuación, ya en la función que dibuja la gráfica, vemos lo siguiente:

- "rrdtool graph $1 -s $2 -e $3 -a PNG": rrdtool graph es el comando encargado de crear la gráfica, la guardará con el nombre que se pase como primer parámetro a la función ("1memoria_last_hour.png" en el primer caso), empezando el intervalo de tiempo en el segundo parámetro y acabando en el tercero ($ONE_HOUR_AGO y $NOW respectivamente, siempre hablando de la primera llamada a la función), el parámetro "-a PNG" indica el formato en el que será almacenado la gráfica en disco.

- "--title "Uso de memoria para $4"...": A continuación viene una serie de parámetros demasiado obvios como los títulos (vertical y horizontal), dimensiones, límites... en el manual está todo mucho mejor explicado ;).

- "DEF:mem_total=$RRDB:mem_total:AVERAGE": Empezamos con las definiciones, definimos el campo "mem_total" correspondiente a la media (AVERAGE) del campo "mem_total" de la "$RRDB" (memoria.rrd). Hacemos lo mismo para el segundo campo que queremos representar:"DEF:mem_usada=$RRDB:mem_usada:AVERAGE".

- "CDEF:mem_total2=mem_total,1024,*": Los campos CDEF se usan para hacer ciertas modificaciones sobre las escalas o los datos DEF, en este caso estamos multiplicando la escala por "1024" para mostrar Mb. en la gráfica en vez de Kb, se pueden realizar múltiples operaciones en los campos CDEF.

- "AREA:mem_total2#3FE719:"Mem. Total "": Ahora definimos que el campo "mem_total2" sobre el cual hemos aplicado las operaciones realizadas en CDEF se representará en forma de "AREA" del color "#3FE719" con el texto "Mem. Total" en la leyenda.

- "LINE1:mem_total2#31B314": Esta linea es de lucimiento personal, representamos de nuvo el campo pero esta vez de forma LINE1 y con un color un poco más oscuro que el anterior, sirve simplemente para poner un borde oscuro al área anteriormente pintada. Hay varios tipos de lineas según su grosor, LINE1, LINE2 y LINE3. Además de las líneas y AREA existe el STACK, que es similar al área pero no se sobrepone con otra área previamente representada.

4.- Fuuusión

Si habeis leido hasta este punto, habreis notado que en un par de ocasiones he mencionado algo así como "se pueden juntar los pasos 2 y 3", pues ha llegado el momento de explicarlo. Tanto la toma de datos como la generación de la gráfica son procesos que han de hacerse periodicamente, normalmente esto significa tener un par de scripts en el cron, primero ejecutar el de toma de datos (paso 2) y seguidamente regeneramos la gráfica (paso 3), ¿por qué no hacerlo todo en un solo paso y así limpiar nuestro cron dejándolo con un solo script?. Pues eso se pretende en el ejemplo:

#!/bin/sh

RRDB=memoria.rrd
HOSTNAME=`hostname -a`

# Variables de tiempo
NOW=`date +%s`
ONE_HOUR_AGO=$(($NOW-3600))
ONE_DAY_AGO=$(($NOW-86400))
ONE_WEEK_AGO=$(($NOW-604800))
ONE_MONTH_AGO=$(($NOW-2419200))
ONE_YEAR_AGO=$(($NOW-29030400))
CMD=`free | grep Mem: | awk '{ print $2":"$3 }'`

# Agregamos datos a la base
rrdtool update memoria.rrd $NOW:$CMD

# Funcion que hace graficas
function draw_graphic()
{
        rrdtool graph $1 -s $2 -e $3 -a PNG 
        --title "Uso de memoria para $4"
        --base=1000 
        --width=600 
        --height=120 
        --alt-autoscale-max 
        --lower-limit=0 
        --vertical-label="Carga de memoria (%)" 
        DEF:mem_total=$RRDB:mem_total:AVERAGE 
                CDEF:mem_total2=mem_total,1024,* AREA:mem_total2#3FE719:"Mem. Total " 
                LINE1:mem_total#31B314 
        DEF:mem_usada=$RRDB:mem_usada:AVERAGE 
                CDEF:mem_usada2=mem_usada,1024,* AREA:mem_usada2#FF7A7A:"Mem. Usada " 
                LINE1:mem_usada#B35656 
}
# Llamamiento a la funcion
draw_graphic '1memoria_last_hour.png' $ONE_HOUR_AGO $NOW "$HOSTNAME (ultima h.)"
draw_graphic '2memoria_last_day.png' $ONE_DAY_AGO $NOW "$HOSTNAME (ultimas 24h.)"
draw_graphic '3memoria_last_week.png' $ONE_WEEK_AGO $NOW "$HOSTNAME (semanal)"
draw_graphic '4memoria_last_month.png' $ONE_MONTH_AGO $NOW "$HOSTNAME (mensual)"
draw_graphic '5memoria_last_year.png' $ONE_YEAR_AGO $NOW "$HOSTNAME (anual)"

Ponemos el script, guardado como memoria.sh, en nuestro cron cada 5 minutos (300 segundos) de la siguiente forma:

# crontab -l
*/5 * * * * /usr/local/htdocs/rrdtool/memoria.sh > /dev/null

Para finalizar solo queda dejar pasar el tiempo y ver como nuestra gráfica se va completando poco a poco. El resultado será similar a la primera ilustración. Cabe recordar que esta explicación es sencilla y no refleja ni de lejos todas las posibilidades que ofrece RRDTOOL, recomiendo leer su manual en castellano, el post de pof y ver los ejemplos y scripts que desde su web nos ofrecen. Deja que tu imaginación haga el resto.

About the author

Óscar
has doubledaddy super powers, father of Hugo and Nico, husband of Marta, *nix user, Djangonaut and open source passionate.