Monthly Archives: March 2010

Python: Ver dominios de Apache que más transferencia consumen

Hace un rato publiqué un post con un script para ver qué dominios de Apache consumen mayor transferencia. El script en BASH toma unos 7 segundos en procesar 750 archivos de uno de mis servidores. Ahora hice una versión en Python, para la que me dieron algunos consejos los chicos de PyAr, y toma aproximadamente 4 segundos en procesar los mismos archivos. Aquí les dejo el código:

#!/usr/bin/env python
""" Access Log Parser

Parses all the files in a directory
treating them as access_log files
and outputs the list of files sorted
by transfered megabytes. Useful for
identifying heavy users.

Usage:
./access_log_parser.py <base_dir>
base_dir = directory where the access_log files are
"""
import sys
import os
import time
from operator import itemgetter

def main (args):
    """Main

    Main function of the script.
    This is where the magic happens.
    It takes the script arguments
    and returns an exit code.
    """

    # Parameter checking
    if len(args) < 2:
        print "Usage: %s <base_dir>" % args[0]
        return 1

    if os.path.isdir(args[1]):
        base_dir = args[1]
    else:
         print "%s is not a directory" % args[1]
         return 2

    # Init vars
    t1 = time.time()
    mbyte = 1048576.0
    domains = []

    # Start processing files
    for item in os.listdir(args[1]):
        path = os.path.join(base_dir, item)
        if os.path.isfile(path) and \
           os.path.getsize(path) > 0:

            bytes = 0
            init_date = None
            data = None

            # Process file lines
            for line in open(path, ‘r’):
                data = line.split(" ")
                if init_date is None:
                    init_date = data[3][1:]
                try:
                    bytes += long(data[9])
                except ValueError:
                    pass
            domains.append({‘domain’: item, \
                            ‘mbytes’: bytes / mbyte, \
                            ‘init_date’: init_date, \
                            ‘end_date’: data[3][1:] })

    # Print out sorted information
    for domain in sorted(domains, key=itemgetter(‘mbytes’)):
        print "%.2f MB | From: %s | To: %s | %s" % \
              (domain[‘mbytes’], \
               domain[‘init_date’], \
               domain[‘end_date’], \
               domain[‘domain’])

    print "Generated in %d seconds" % (time.time() – t1)
    return 0

if __name__ == "__main__":
    sys.exit(main(sys.argv))

Para ejecutarlo:

./access_log_parser.py /tmp/domain_logs
...
544.30 MB | From: 10/Mar/2010:02:48:33 | To: 10/Mar/2010:18:13:25 | dominio1.com.ar.log
602.34 MB | From: 10/Mar/2010:00:23:09 | To: 10/Mar/2010:23:39:45 | dominio2.com.ar.log
944.03 MB | From: 10/Mar/2010:00:49:35 | To: 10/Mar/2010:23:39:57 | dominio3.com.ar.log
Generated in 3 seconds

Ver dominios de Apache que más transferencia consumen

Frecuentemente me encuentro ante la necesidad de saber qué dominios de los que tengo alojados en mis servidores consumen más tráfico HTTP. Como éste es el tipo de tráfico más importante y que más incide en el desempeño general de mis servidores, saber qué dominios transfieren más datos por HTTP me orienta en la búsqueda de heavy users. Para ello hice un script hace algún tiempo, y ahora lo estuve revisando, retocando y simplificando, y decidí compartirlo.

En el entorno de Directadmin, los logs de Apache se encuentran en /var/log/httpd. Allí hay una carpeta “domains” que guarda los logs de cada dominio. Por cada dominio hay tres logs: dominio.com.log, dominio.com.error.log y dominio.com.bytes. El primero es el access_log, el segundo el error_log y el tercero solamente guarda la cantidad de bytes de cada request. La opción más sencilla es trabajar con este último tipo de logs, pero el problema que tienen es que no indican la fecha. Si bien se supone que los logs de Directadmin rotan una vez por día, alguna vez me ha pasado que por razones que nunca pude descubrir, algunos logs no rotaban y generaban ciertas confusiones. Por lo tanto, el script que dejo usa los access_logs. Además esta opción es reutilizable en otras implementaciones de Apache que no tengan archivos de bytes.

El código:

# Access Log Parser
#
# Parses all the files in a directory
# treating them as access_log files
# and outputs the list of files sorted
# by transfered megabytes. Useful for
# identifying heavy users.
#
# Usage:
# ./access_log_parser.sh <base_dir>
# base_dir = directory where the access_log files are

if [ -z $1 ]; then
    echo "Usage: $0 "
    exit 1
fi

BASE_DIR=$1
T1=`date +%s`
for file in $BASE_DIR/*;
do
    size=`stat -c %s $file`
    if [ $size -gt 0 ]; then
        from=`head -1 $file | awk ‘{print($4)}’ |sed ‘s/^\[//’`
        to=`tail -1 $file | awk ‘{print($4)}’ |sed ‘s/^\[//’`
        bytes=`cat $file | awk ‘{a+=$10}END{print a}’`
        mbytes=`echo $bytes | awk ‘{print $1 / 1048576}’`
        echo  "$mbytes MB ($bytes bytes) | From: $from | To: $to | ${file:${#BASE_DIR}+1}"
    fi
done \
| sort -nb
T=`date +%s`

Un detalle importante: el script levanta todos los archivos de un directorio que se especifica por parámetro. Esto es así porque yo lo invoco desde otro script:

#!/bin/bash
LOG_DIR=/var/log/httpd/domains/
TMP=/tmp/domain_logs
if [ ! -d $TMP ]; then
    mkdir -p $TMP
fi

rm -f $TMP/*

cd $LOG_DIR
for i in `ls *.log |grep -v error`; do
    ln $i $TMP/$i
done
/root/access_log_parser.sh $TMP

Este último script lo que hace es crear un directorio temporal (si no existe). Vaciar su contenido. Y luego generar hardlinks a todos los access_logs de /var/log/httpd/domains. De esta forma, no me tengo que preocupar por si los logs son modificados mientras yo estoy ejecutando el script. Por último, ejecuta mi primer script (access_log_parser.sh) y le pasa como parámetro el directorio temporal donde están los access_logs.

Por último, la salida del script sería algo así:

...
186.995 MB (196078184 bytes) | From: 10/Mar/2010:00:27:47 | To: 10/Mar/2010:16:51:09 | dominio1.com.ar.log
187.096 MB (196184596 bytes) | From: 10/Mar/2010:03:24:55 | To: 10/Mar/2010:16:51:24 | dominio2.com.ar.log
245.692 MB (257626221 bytes) | From: 10/Mar/2010:02:53:50 | To: 10/Mar/2010:16:39:54 | dominio3.com.log
273.46 MB (286743390 bytes) | From: 10/Mar/2010:02:48:33 | To: 10/Mar/2010:14:59:29 | dominio4.com.ar.log
306.344 MB (321224473 bytes) | From: 10/Mar/2010:00:23:09 | To: 10/Mar/2010:16:51:20 | dominio5.com.ar.log
444.097 MB (465669066 bytes) | From: 10/Mar/2010:00:49:35 | To: 10/Mar/2010:16:51:20 | dominio6.com.ar.log
Generated in 6 seconds

Les dejo, de yapa, la opción del script utilizando los logs de bytes. Como dije, esta opción es un poco más sencilla y veloz, pero no nos dice las fechas de los logs.

#!/bin/bash

if [ -z $1 ]; then
    echo "Usage: $0 "
    exit 1
fi

BASE_DIR=$1
T1=`date +%s`
for file in $BASE_DIR/*.bytes;
do
    size=`stat -c %s $file`
    if [ $size -gt 0 ]; then
        bytes=`cat $file | awk ‘{a+=$1}END{print a}’`
        mbytes=`echo $bytes | awk ‘{print $1 / 1048576}’`
        echo  "$mbytes MB ($bytes bytes) ${file:${#BASE_DIR}+1}"
    fi
done \
| sort -nb
T=`date +%s`
echo "Generated in $(($T-$T1)) seconds"

Y la salida sería así:

# ./parser.sh /var/log/httpd/domains
...
181.716 MB (190543470 bytes) dominio1.com.bytes
187.115 MB (196203973 bytes) dominio2.com.ar.bytes
189.093 MB (198278283 bytes) dominio3.com.ar.bytes
245.692 MB (257626221 bytes) dominio4.com.bytes
273.472 MB (286756234 bytes) dominio5.com.ar.bytes
314.32 MB (329588717 bytes) dominio6.com.ar.bytes
444.516 MB (466108759 bytes) dominio7.com.ar.bytes
Generated in 2 seconds

Grave exploit para Apache descubierto

Leo en Slashdot que se descubrió un grave exploit para Apache. De acuerdo con la nota de ZDNet, la compañía de seguridad “Sense of Security” descubrió un serio bug en el servidor web Apache que podría permitir a un atacante remoto obtener control sobre una base de datos. Una vulnerabilidad existente en el módulo “mod_isapi” del core de Apache podría permitirle a un atacante obtener privilegios de administrador, comprometiendo seriamente la seguridad de la información.

La nota de ZDNet no da muchos detalles del bug, pero indica que comprometería especialmente a los servidores Windows. Por otra parte, el reporte de Sense of Security es más detallado e incluye una “prueba de concepto” acerca de cómo explotar el bug.

Se recomienda actualizar a la versión 2.2.15 de Apache que corrige el bug.

Directadmin ya ha hecho los cambios correspondientes, así que con Custombuild podemos actualizar rápida y fácilmente a la nueva versión siguiendo el procedimiento que explique recientemente.

httping: herramienta para medir la latencia de un servidor web

El otro día, Pablo Morales me pasó el dato de httping. Se trata de una herramienta similar al ping, pero que en vez de enviar paquetes por ICMP a una IP, envía peticiones HTTP a un servidor web. De esta forma podemos medir la latencia del servicio.

Instalación

En Debian/Ubuntu podemos instalar el paquete httping:

# sudo apt-get install httping

Para otras distribuciones, accediendo a la página de httping encontrarán RPMs y el tar.gz con los fuentes. Descargando los fuentes solamente hace falta descomprimirlo y darle “make && make install”.

Ejemplo de uso

Usarlo es muy sencillo. Por ejemplo, para pingear un servidor local:

elbarto@tarantino:~$ httping -c 5 -g http://localhost
PING localhost:80 (http://localhost):
connected to localhost:80, seq=0 time=1.56 ms
connected to localhost:80, seq=1 time=1.30 ms
connected to localhost:80, seq=2 time=1.30 ms
connected to localhost:80, seq=3 time=1.27 ms
connected to localhost:80, seq=4 time=1.19 ms
--- http://localhost ping statistics ---
5 connects, 5 ok, 0.00% failed
round-trip min/avg/max = 1.2/1.3/1.6 ms

El parámetro “-c 5″ define la cantidad de pings a realizar. Esto es igual que el ping de Linux.

También puedo pingear esta web.

httping -c 5 -g http://www.tail-f.com.ar
PING www.tail-f.com.ar:80 (http://www.tail-f.com.ar):
connected to www.tail-f.com.ar:80, seq=0 time=55.48 ms
connected to www.tail-f.com.ar:80, seq=1 time=45.74 ms
connected to www.tail-f.com.ar:80, seq=2 time=34.24 ms
connected to www.tail-f.com.ar:80, seq=3 time=41.79 ms
connected to www.tail-f.com.ar:80, seq=4 time=40.31 ms
--- http://www.tail-f.com.ar ping statistics ---
5 connects, 5 ok, 0.00% failed
round-trip min/avg/max = 34.2/43.5/55.5 ms

En este caso los tiempos son mayores, y tenemos que tener en cuenta dos cosas. En primer lugar, hay una diferencia por la conexión (no es lo mismo conectarme a localhost que a un servidor externo). En segundo lugar, este blog es un sitio dinámico hecho en PHP que realiza una serie de operaciones antes de mostrar el resultado. Pero, ¿cómo sabemos qué toma más tiempo, si la conexión o el procesamiento del sitio?. Para ello podemos separar ambos tiempos con el parámetro -S.

httping -c 5 -Sg http://www.tail-f.com.ar
PING www.tail-f.com.ar:80 (http://www.tail-f.com.ar):
connected to www.tail-f.com.ar:80, seq=0 time=25.96+22.93=48.89 ms
connected to www.tail-f.com.ar:80, seq=1 time=22.74+17.37=40.11 ms
connected to www.tail-f.com.ar:80, seq=2 time=21.90+20.39=42.29 ms
connected to www.tail-f.com.ar:80, seq=3 time=25.36+18.01=43.37 ms
connected to www.tail-f.com.ar:80, seq=4 time=27.81+15.85=43.66 ms
--- http://www.tail-f.com.ar ping statistics ---
5 connects, 5 ok, 0.00% failed
round-trip min/avg/max = 40.1/43.7/48.9 ms

Aquí vemos primero el tiempo de conexión, luego el de procesamiento y por último el total. En este caso, por lo general toma un poco más la conexión que el procesamiento, aunque es bastante parejo.

Otra cosa a tener en cuenta es que en este tipo de pruebas que venimos haciendo, el httping envía una petición HEAD al servidor. Para enviar un GET (que obtenga todo el contenido de la página), debemos agregar el parámetro “-G”. Por ejemplo:

elbarto@tarantino:~$ httping -c 5 -Gg http://www.tail-f.com.ar
PING www.tail-f.com.ar:80 (http://www.tail-f.com.ar):
connected to www.tail-f.com.ar:80, seq=0 time=210.41 ms
connected to www.tail-f.com.ar:80, seq=1 time=211.78 ms
connected to www.tail-f.com.ar:80, seq=2 time=194.49 ms
connected to www.tail-f.com.ar:80, seq=3 time=184.41 ms
connected to www.tail-f.com.ar:80, seq=4 time=210.62 ms
--- http://www.tail-f.com.ar ping statistics ---
5 connects, 5 ok, 0.00% failed
round-trip min/avg/max = 184.4/202.3/211.8 ms

Aquí los tiempos se incrementan, lógicamente, porque el tamaño de los paquetes traficados es mucho mayor.

Por último, podemos usar httping para hacer pruebas de stressing, con la opción “flood connect” (-f) que envía peticiones sin esperar respuesta.

elbarto@tarantino:~$ httping -fg http://www.tail-f.com.ar
PING www.tail-f.com.ar:80 (http://www.tail-f.com.ar):
connected to www.tail-f.com.ar:80, seq=0 time=51.54 ms
connected to www.tail-f.com.ar:80, seq=1 time=48.47 ms
connected to www.tail-f.com.ar:80, seq=2 time=56.07 ms
connected to www.tail-f.com.ar:80, seq=3 time=55.58 ms
connected to www.tail-f.com.ar:80, seq=4 time=55.99 ms
connected to www.tail-f.com.ar:80, seq=5 time=52.46 ms
connected to www.tail-f.com.ar:80, seq=6 time=56.06 ms
connected to www.tail-f.com.ar:80, seq=7 time=52.99 ms
connected to www.tail-f.com.ar:80, seq=8 time=52.54 ms
connected to www.tail-f.com.ar:80, seq=9 time=76.56 ms
connected to www.tail-f.com.ar:80, seq=10 time=63.04 ms
connected to www.tail-f.com.ar:80, seq=11 time=53.01 ms
connected to www.tail-f.com.ar:80, seq=12 time=36.00 ms
connected to www.tail-f.com.ar:80, seq=13 time=48.93 ms
connected to www.tail-f.com.ar:80, seq=14 time=42.78 ms
connected to www.tail-f.com.ar:80, seq=15 time=44.82 ms
connected to www.tail-f.com.ar:80, seq=16 time=52.41 ms
connected to www.tail-f.com.ar:80, seq=17 time=56.62 ms
connected to www.tail-f.com.ar:80, seq=18 time=48.44 ms
connected to www.tail-f.com.ar:80, seq=19 time=53.05 ms
connected to www.tail-f.com.ar:80, seq=20 time=54.13 ms
^Cconnected to www.tail-f.com.ar:80, seq=21 time=60.53 ms
--- http://www.tail-f.com.ar ping statistics ---
22 connects, 22 ok, 0.00% failed
round-trip min/avg/max = 36.0/53.3/76.6 ms

Luego tiene muchas otras opciones interesantes, como configuración de la salida para integrarse con Nagios y la posibilidad de definir un User-Agent y un Referer específicos en los requests. Les recomiendo instalarlo y hacer “man httping” para ver todas las opciones.

Enrutamiento Avanzado y Control de Tráfico en Linux

Leyendo El CóDiGo K me entero sobre la existencia de este manual de Enrutamiento Avanzado y Control de Tráfico en Linux. Anoche lo bajé y lo estuve hojeando un poco. La verdad que es muy interesante y vale realmente la pena. Dejo la explicación de Daniel que me pareció muy clara.

Uno de los documentos más completos y más leídos por los Administradores de Redes cuando se habla de Calidad de Servicio (QoS) en los sistemas conectados.

Este interesante documento pretende descubrir a fondo herramientas que perteneces a la poderosa infraestructura iproute2, sustituyendo antiguos comandos tales como routeifconfig.

Actualmente el documento “Enrutamiento Avanzado y Control de Tráfico en Linux” se encuentra en 8 idiomas totalmente traducidos, con diversos ejemplos para dominar por completo la administración de recursos de los sistemas.

Enlaces