Archivo de la etiqueta: eficiencia

El arte de depurar y perfilar con PHP

Depurar y perfilar funciones PHP gráficamente con XDebug, KCacheGrind y Webgrind en Ubuntu 11.10

El arte de depurar y perfilar con PHP
El arte de depurar y perfilar con PHP

En desarrollo web y por lo general, en cualquier aplicación que requiera tener una demanda escalable, son muy importantes los algoritmos empleados, las llamadas a funciones nativas y las estructuras de datos utilizadas.

En aplicaciones PHP con miles de líneas de código se necesita un diseño eficiente para poder manejar una buena ejecución del código PHP.

Principalmente se busca reducir el consumo de memoria y CPU al máximo posible, sin que la aplicación pierda funcionalidad, ni características. Para lograr este objetivo y que la aplicación sea escalable, se busca utilizar buenas herramientas de depuración y profiling (perfilado de rendimiento). Con ello se consigue que el programador o ingeniero disponga de una buena perspectiva de lo que esta haciendo a bajo nivel su aplicación y decidir en consecuencia que técnica aplicar.

El perfilado o medición del rendimiento (performance profiling) permite ejecutar código en un entorno controlado y devolver un listado con estadísticas de tiempo gastado en cada funcion, longitud de consultas a la base de datos o la cantidad de memoria que ha sido usada. De esta forma, se puede desechar codígo lento o superfluo de una manera muy rápida o incluso realizar pequeños trucos en partes críticas para ganar más rendimiento.

Herramientas

En PHP existen varias herramientas de depuración(debugging) y localización de cuellos de botella(bottleneck) para perfilado de rendimiento.

Las herramientas más utilizadas son:

  • BenchMark es un proyecto PEAR de PHP, pero actualmente no esta mantenido.
  • DBG PHP Debugger es otra herramienta de depuración que dispone de una versión comercial y otra gratuita. La versión gratuita sólo permite depurar la rama de versión 5.2 y la comercial la versión 5.3. La aplicación es bastante cara y parece que no tiene mucha actividad y estar sólo integrada con el IDE NuSphere PhpED.
  • El Depurador Avanzado de PHP (APD, del inglés “Advanced PHP Debugger“) es un proyecto PEAR de PHP escrito en C por George Schlossnagle and Daniel Cowgill. Es cargado como una extensión en el motor Zend y funciona enlazando e interceptando las llamadas a funciones en el núcleo de Zend. Esto permite la medición del tiempo de ejecución de una función, la vuelta atrás de pilas, etc. La documentación es mínima y los informes de depurado junto con funcionalidades son algo reducidas, por lo que en la práctica no es la opción más utilizada.
  • Xdebug es el depurador y perfilador por excelencia de PHP escrito por Derick Rethans. Tiene una gran variedad de parámetros de configuración, depuración remota, integración con muchos IDEs (Eclipse, Netbeans, etc), reportes coloreados, niveles máximos de anidación para el depurado, accionadores (triggers) para el perfilador, un conjunto de funciones (para puntos de ruptura, analisis de tiempos, etiquetado de código, etc), reemplazo de var_dump por funciones de visualización, trazas de la pila de ejecución,  trazas de funciones, análisis de cobertura de código,   etc.

Instalación y configuración de Xdebug

Puesto que Xdebug ofrece las mejores características y facilidades, es la opción preferida, ademas la instalación y configuración en Ubuntu 11.10 es bastante sencilla.

Se pueden bajar los f uentes y compilarlos, pero resulta más cómodo bajar del repositorio PEAR la aplicación e instalarla.

Para instalar el paquete PEAR, debemos tener previamente PEAR instalado:

$ sudo apt-get install php-pear php5-dev

Despues, instalamos Xdebug a través de PECL

$ sudo pecl install xdebug

Una vez finalizada la instalación, podemos encontrar la biblioteca compartida compilada en /usr/lib/php5/20090626+lfs/xdebug.so

Pasamos a configurar el php.ini para que cargue Xdebug. Por defecto Ubuntu 11.10 tiene varios php.ini, como son /etc/php5/cli/php.ini/etc/php5/apache2/php.ini. Este último es que se utiliza para la configuración global.

Pasamos a modo root:

$ sudo su -

Añadimos Xdebug al php.ini cargándolo como extensión zend y con el path completo, en nuestro caso el path es /usr/lib/php5/20090626+lfs/xdebug.so

# echo 'zend_extension = "/usr/lib/php5/20090626+lfs/xdebug.so"' >> /etc/php5/apache2/php.ini

Después lo cargamos como extensión PHP:

# echo "extension=xdebug.so" >> /etc/php5/apache2/php.ini

Para comprobar que ha sido cargado, podemos mirar los módulos cargados de PHP:

# php -m | grep Xdebug
Xdebug

O bien en la información general:

# php -i | grep Xdebug
with Xdebug v2.1.2, Copyright (c) 2002-2011, by Derick Rethans

Si filtramos en minúsculas para la información general, podemos ver todas las opción de configuración:

# php -i | grep xdebug
xdebug
xdebug support => enabled
xdebug.auto_trace => Off => Off
xdebug.collect_assignments => Off => Off
xdebug.collect_includes => On => On
xdebug.collect_params => 0 => 0
xdebug.collect_return => Off => Off
xdebug.collect_vars => Off => Off
xdebug.default_enable => On => On
xdebug.dump.COOKIE => no value => no value
xdebug.dump.ENV => no value => no value
xdebug.dump.FILES => no value => no value
xdebug.dump.GET => no value => no value
xdebug.dump.POST => no value => no value
xdebug.dump.REQUEST => no value => no value
xdebug.dump.SERVER => no value => no value
xdebug.dump.SESSION => no value => no value
xdebug.dump_globals => On => On
xdebug.dump_once => On => On
xdebug.dump_undefined => Off => Off
xdebug.extended_info => On => On
xdebug.file_link_format => no value => no value
xdebug.idekey => root => no value
xdebug.manual_url => http://www.php.net => http://www.php.net
xdebug.max_nesting_level => 100 => 100
xdebug.overload_var_dump => On => On
xdebug.profiler_aggregate => Off => Off
xdebug.profiler_append => Off => Off
xdebug.profiler_enable => On => On
xdebug.profiler_enable_trigger => On => On
xdebug.profiler_output_dir => /var/www/webgrind/tmp => /var/www/webgrind/tmp
xdebug.profiler_output_name => cachegrind.out.%t.%p => cachegrind.out.%t.%p
xdebug.remote_autostart => Off => Off
xdebug.remote_connect_back => Off => Off
xdebug.remote_cookie_expire_time => 3600 => 3600
xdebug.remote_enable => On => On
xdebug.remote_handler => dbgp => dbgp
xdebug.remote_host => 127.0.0.1 => 127.0.0.1
xdebug.remote_log => no value => no value
xdebug.remote_mode => req => req
xdebug.remote_port => 9000 => 9000
xdebug.scream => Off => Off
xdebug.show_exception_trace => Off => Off
xdebug.show_local_vars => Off => Off
xdebug.show_mem_delta => Off => Off
xdebug.trace_format => 0 => 0
xdebug.trace_options => 0 => 0
xdebug.trace_output_dir => /tmp => /tmp
xdebug.trace_output_name => trace.%c => trace.%c
xdebug.var_display_max_children => 128 => 128
xdebug.var_display_max_data => 512 => 512
xdebug.var_display_max_depth => 3 => 3

Activando configuraciones en Xdebug

Para generar información de depuración, debemos activar como mínimo las siguientes configuraciones de la familia xdebug.profiler_*:

  • Primero escribimos la cabecera de configuraciónpara xdebug:
    # echo "[xdebug]" >> /etc/php5/cli/php.ini
  • xdebug.profiler_enable activa el perfilador de xdebug para que genere archivos de perfilado en un directorio de salida. Estos archivos puedes leerse por programas visualizadores gráficos de llamadas como KCacheGrind o Webgrindque será explicado más adelante. Esta configuración también puede habilitarse mediante la función ini_set() por lo que resulta interesante para activar selectivamente el perfilador en nuestras aplicaciones. Se activa con:
    # echo "xdebug.profiler_enable = 1" >> /etc/php5/cli/php.ini
  • xdebug.profiler_enable_triggerpermite generar archivos de perfilador usando el parámetro XDEBUG_PROFILE por peticiones GET/POST o establecer una cookie. Lo activamos con:
    # echo "xdebug.profiler_enable_trigger = 1" >> /etc/php5/cli/php.ini
  • xdebug.profiler_output_dir el valor por defecto donde el perfilador escribira su salida. Debe tener permisos de escritura para PHP y no puede ser establecida con ini_set(). Para propósitos posteriores, la estableceremos como:
    # echo 'xdebug.profiler_output_dir = "/var/www/webgrind/tmp"' >> /etc/php5/cli/php.ini
  • xdebug.profiler_output_name determina el nombre del archivo usado para las trazas de depuración. La configuración especifica el formato del nombre con los especificares de formato que son muy similares a sprintf() y strtime(). Por defecto es cachegrind.out.%p. Existe un un listado completo de los especificadoresdocumentados. Lo configuramos con:
    # echo "xdebug.profiler_output_name = cachegrind.out.%t.%p" >> /etc/php5/cli/php.ini

La configuración mínima recomendada que resultaría al final del php.ini quedaría como:

zend_extension = "/usr/lib/php5/20090626+lfs/xdebug.so
extension=xdebug.so

[xdebug]
xdebug.profiler_enable = 1
xdebug.profiler_enable_trigger = 1
xdebug.profiler_output_dir = "/var/www/webgrind/tmp"
xdebug.profiler_output_name = cachegrind.out.%t.%p

Si queremos activar ademas la depuración remota en un puerto y máquina (o bien en la misma), usaremos la familia de configuraciones xdebug.remote_*. Las mínimas recomendadas son:

  • xdebug.remote_enable si esta activado, Xdebug tratará de contactar con el cliente que este escuchando en el puerto y host configurad. Si la conexión no puede ser establecidad, el script continuará ejecutado como si estuviera desactivado. Lo activamos con:
    # echo "xdebug.remote_enable = 1" >> /etc/php5/cli/php.ini
  • xdebug.remote_host selecciona el host donde el cliente de depuración se ejecutará. Puede utilizar un nombre de host o una dirección IP. Lo configuramos con:
    # echo "xdebug.remote_host=127.0.0.1" >> /etc/php5/cli/php.ini
  • xdebug.remote_port el puerto al que Xdebug tratara de conectar en el host remoto. Por defecto es el puerto 9000, para el cliente y cliente de debug integrado. Lo configuramos con:
    # echo "xdebug.remote_port=9000" >> /etc/php5/cli/php.ini
  • xdebug.remote_handler puede ser ‘php3′ que selecciona el depurador antiguo para PHP3, tambiíen puede ser ‘gdb’ que habilita el depurador GDB o bien el protocolo de depuración con ‘dbgp’. El protocolo de depuraciónesta por lo general más soportado por los clientes. Lo configuramos con:
    # echo "xdebug.remote_handler=dbgp" >> /etc/php5/cli/php.ini

Puedes ver más información en la introducción al depurado remoto de Xdebug.

Por lo que el resultado final sería:

zend_extension = "/usr/lib/php5/20090626+lfs/xdebug.so
extension=xdebug.so

[xdebug]
xdebug.profiler_enable = 1
xdebug.profiler_enable_trigger = 1
xdebug.profiler_output_dir = "/var/www/webgrind/tmp"
xdebug.profiler_output_name = cachegrind.out.%t.%p
xdebug.remote_enable=true
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9000
xdebug.remote_handler=dbgp

Instalando KCacheGrind

KCacheGrind es un programa de escritorio (en especial para diseñado para KDE, pero compatible para GNOME) escrito para visualizar los archivos de depuración generados por xdebug. Es un frontend de Callgrind, que a su vez usa en tiempo de ejecución al framework de Valgrind para simulación de caches y generación de las llamadas gráficas. Un aspecto a recalcar es que incluso las bibliotecas compartidas que son abiertas dinamicamente son perfiladas. Podemos abrir con el archivos de depuración con formato Callgrind generados con xdebug de forma bastante rápida.

Para instalarlo simplemente:

$ sudo apt-get install kcachegrind

Por ejemplo podemos crear un archivo con la información de PHP para ver un ejemplo sencillo de depuración y ejecutarlo para que se genere un archivo cachegrind.out, que podamos abrir con KCacheGrind:

# echo "<?php phpinfo(); ?>" >> /var/www/phpinfo.php

Como resultado, obtendríamos un grafo y los tiempos de ejecución globales y relativos implicados:

KCacheGrind - Ejemplo sencillo con phpinfo()Ejemplo de depuración de phpinfo() con KCacheGrind

Es una aplicación bastante notable, pero en su lugar es más recomendable usar Webgrind como frontend web, que veremos a continuación.

Instalar webgrind

Webgrind 1.0 logo

Webgrind es un perfilador gráfico de PHP via web escrito en PHP por Joakim Nygård y Jacob Oettinger. Tiene como dependencia Xdebug ya que es en realidad un wrapper web no completo de las funciones de KCacheGrind como reemplazo de frontend web de xdebug (existe incluso una proposición de Google Summer of Code aceptada por Chung-Yang Lee).

Su apareciencia es simple y agradable para depurar aplicaciones PHP. Es multiplataforma (al ser vía web) y permite seguimiento de los tiempos empleados por cada llamada de función, por coste de ellas misma o coste inclusivo (el coste inclusivo es el tiempo dentro de la función mas llamadas internas de esa función a otras).

También permite ver tiempo de llamadas internas y llamadas de usuario diciendo la llamada por la que fue invocada previamente.

Descargamos la aplicación de github, ya que en el momento de escribir este artículo están haciendo una migración de google code a github (también puedes hacer un checkout del repo, aunque puede ser inestable):

$ wget https://github.com/downloads/jokkedk/webgrind/webgrind-release-1.0.zip

Descomprimos:

$ unzip webgrind-release-1.0.zip

En ubuntu el DocumentRoot de Apache esta establecido por defecto en /var/www según /etc/apache2/sites-available/default

Luego, copiaremos los fuentes de webgrind y crearemos los archivos temporales que configuramos como directorio de salida en el perfilador de Xdebug:

$ sudo mv webgrind /var/www
$ sudo mkdir /var/www/webgrind/tmp
$ sudo chmod 777 /var/www/webgrind/tmp

Editamos el archivo de configuración e introducimos la ruta de directorio de salida de xdebug:

$ sudo vi /var/www/webgrind/config.php

Busca las variables $storageDir y $profilerDir y escribe:

static $storageDir = '/var/www/webgrind/tmp';
static $profilerDir = '/var/www/webgrind/tmp';

Nos aseguramos de todos los archivos tienen los permisos de grupo correctos de Apache:

$ sudo chown -R www-data.www-data /var/www/webgrind/

Si además queremos que no se creen archivos de depuración para el propio webgrind, creamos un archivo .htaccess en el directorio de webgrind desactivandolo:

php_flag xdebug.profiler_enable 0

Para entrar a la interfaz web de Webgrind, simplemente escribe en tu navegador http://localhost/webgrind/ y aparecerá disponible un listado de archivos cachegrind* donde podemos ver la traza y tiempos de depuración del perfilador:

Traza de depuración de phpinfo() con WebgrindTraza de depuración de phpinfo() con Webgrind

Como usar bibliotecas de C en Python

Para determinadas aplicaciones, la eficiencia y el rendimiento es clave. Python es un buen lenguaje para programar rápido y mucho en pocas líneas, pero a veces queremos bajar más de nivel para ofrecer mejores prestaciones.

Pero esta decisión no debe implicar, perder nuestra comodidad que tenemos con Python. Existe una biblioteca incluida nativamente desde Python 2.5 llamada ctypes que nos permite utilizar funciones y bibliotecas compartidas de C (en cualquier sistema operativo) obteniendo la eficiencia del lenguaje C pero sin renunciar a las bondades de sintaxis de Python.

Ejemplo práctico y sencillo

Veamos un ejemplo sencillo para ilustrar esta gran funcionalidad. Para empezar podemos crear un simple archivo de código fuente en C y por ejemplo llamarlo ‘libtest.c’.

Por ejemplo, en esta biblioteca C, haremos una simple función que multiplique enteros:

int multiply(int num1, int num2)
{
    return num1 * num2;
}

Ahora necesitaremos compilar nuestra biblioteca ‘libtest.c’ en una biblioteca válida, para ello utilizaremos los siguientes comandos de compilación:

gcc -c -fPIC libtest.c
gcc -shared libtest.o -o libtest.so

Ello creara una versión final de la biblioteca para GNU/Linux llamada libtest.so que podamos usar en Python.

Ahora probemos nuestra biblioteca (debes poner el path o camino de libtest.so en el código para que funcione o incluirlo en las rutas donde GNU/Linux buscara las bibliotecas comunes):

# A partir de Python >= 2.5 se incluye la libreria *ctypes* en el core, comprobar si esta disponible
import sys
try:
    from ctypes import *
except ImportError:
    print 'ERROR! La biblioteca *ctypes* para Python no esta disponible.'
    sys.exit(-1)

libtest = cdll.LoadLibrary('ruta_a_la_biblioteca/libtest.so')
print 'La multiplicación de 2 * 2 es:', libtest.multiply(2, 2)

Este ejemplo imprimirá 4 si todo ha ido correctamente. Como se aprecia, no es nada complicado usar código C nativo en Python.

Ejemplo con parámetros

Podemos crear ejemplos más complejos, incluso con funciones que tengan argumentos como punteros o pasados por referencia. Por ejemplo una lista donde se sumen todos los valores, su media:

/* suma de una lista de valores */
double sum(double *data, int n)
{
    int i=0;
    double sum=0.0;

    for (i=0; i < n; i++)
        sum += data[i];
    return sum;
}

/* media de una lista de valores */
double mean(double *data, int n)
{
    double s = sum(data, n);
    return s/((double)n);
}

Lo compilamos de nuevo con:t:

$ gcc -shared -fPIC liblist.c -o liblist.so

Y en python de nuevo usamos ctypes:

from ctypes import *
so = CDLL("liblist.so")

# Establecemos las interfaces con los argumentos

# y los valores que devuelven
so.mean.argtypes= [POINTER(c_double), c_int]
so.mean.restype = c_double
so.sum.argtypes = [POINTER(c_double), c_int]
so.sum.restype = c_double

# Llamamos a las funciones
def cmean(self, dlist):
    doubleArray = c_double*len(dlist) # Tipo de dato (double[n])
    cx = doubleArray(*dlist) # Array actual
    n = len(cx) # Longitud del dato

    result = so.mean(cx, n)
    return float(result)

def csum(self, dlist):
    doubleArray = c_double*len(dlist)
    cx = doubleArray(*dlist)
    n = len(cx)

    result = so.sum(cx, n)
    return float(result)

# Ahora podemos usar estas funciones como si fuera python puro!
data = [1,2,3,4,5,6]
print cmean(data)
print csum(data)

Con ctypes podemos crear un wrapper (envoltura o adaptador) para una biblioteca matemática u otras funciones que nos interesen.

Ejemplo con MPI

Otro ejemplo práctico sería poder escribir MPI en python desde C, como el ejemplo que escribí hace unos días. Notad que ya existen implementaciones python de MPI, pero nunca esta demás saber como funcionan y que principios siguen, por ejemplo para iniciarse en openMPI desde python, podríamos hacer:

#!/usr/bin/python

import sys

try:
from ctypes import CDLL, pythonapi, c_int, POINTER, c_char_p, byref, RTLD_GLOBAL
from ctypes.util import find_library
except ImportError:
print 'ERROR! La biblioteca *ctypes* para Python no esta disponible!'
sys.exit(-1)

libc = CDLL('libc.so.6')

print libc._handle, libc._name

f = pythonapi.Py_GetArgcArgv
argc = c_int()
argv = POINTER(c_char_p)()
f(byref(argc), byref(argv))

mpi = CDLL(find_library('mpi'), RTLD_GLOBAL)
print mpi._handle, mpi._name

libc.printf("Hola mundo");

mpi.MPI_Finalize()

En este ejemplo, utilizo el método find_library() de ctypes que me permite olvidarme de localizar la ruta de una biblioteca y así buscar por si mismo el programa su ruta. Se importa la biblioteca de C y la de MPI y se hace un simple hola mundo mostrando algunos datos sobre las bibliotecas también. Para ctypes únicamente he importando las funciones necesarias, evitando que se cargue toda la biblioteca.

En definitiva ctypes es una potente herramienta que todo desarrollador Python debería tener en cuenta cuando quiera realizar desarrollos más eficientes y aprovechar implementaciones de otras bibliotecas compartidas.

Apache checker: un script comprobador de Apache en bash

apache-checker-logo

La gestión de servidores es un tema apasionante, pero exige mucho control sobre los sistemas a administrar. Personalmente en la empresa de hospedaje que administro llamada Quijost necesitamos un riguroso control de los servicios para detectar cualquier mal funcionamiento o sobrecarga y obtener una solución de forma casi inmediata.

Uno de los principales problemas es la gestión de recursos de memoria en servidores con Apache y que por lo general suelen usar Cpanel.

Cpanel es un buen sistema de panel de administración, pero es muy exigente en recursos y a veces consume demasiada memoria llegando a colapsar sus propios procesos e invocando a daemons encargados de reiniciarlo.

El problema viene cuando Cpanel además provoca un mal funcionamiento de Apache o bien tenemos un exceso de consumo en servidor por algún efecto Barrapunto, Menéame, Digg, etc.

En esos casos Apache atenderá todas las peticiones posibles dada la memoria de la que dispongamos. Normalmente y como referencia unas 200 peticiones por segundo con 1 GB de RAM (aunque tened presente que esta cifra puede variar bastante según configuraciones y hardware).

Cuando el servidor se quede sin memoria, las peticiones no se atenderán incluso otros servicios como emails (exim) pueden colapsar. Para evitar estas situaciones, he desarrollado un script que se encarga de comprobar periodicamente mediante una tarea cron, los recursos del sistema, la disponibilidad de Apache y la memoria disponible en el servidor, para actuar en consecuencia y reiniciar si es necesario, además de notificar a los administradores y mantener un log.

El script llamado Apache Checker está escrito en bash y tiene el siguiente aspecto:

# Apache Checker: a script for check resources on apache servers
# Author: Shakaran (http://www.shakaran.net)
# License: GPLv3

# For CentOs servers require bc and mutt:
# yum install bc
# yum install mutt

# Uses:
# Add this script to a cron's task with crontab -e
# For example: For run the checking every minute
# */1 * * * * /apache_check.sh &> /dev/null 

# Exit immediately if a simple command exits with a non-zero status
set -e

# Number of current apache2 processes.
N_CURRENT="$(ps aux | grep apache2 | wc -l)"
N_MIN="1"
DESTINY_EMAIL="your-server-admin-address@domain.com"
USER=`id -un` # For example: root
HOST=`hostname`
USERHOST=$USER@$HOST

THRESHOLD=90 # Max threshold for restart apache
TOTAL_MEMORY=$(free | grep "Mem:" | awk '{print $2}')
REMAINING_MEMORY=$(free | grep "Mem:" | awk '{print $4}')
CURRENT_MEMORY=$(echo "($REMAINING_MEMORY/$TOTAL_MEMORY)*100.0" | bc -l)

MAX_NPROCESS_APACHE=5
NPROCESS_APACHE=`ps fu $USERNAME  | awk '/processname/ { x++ } END{print x}'`

if [ "$N_CURRENT" -lt "$N_MIN" ]; then
    apachectl restart
    echo "$HOST: The Apache process is not working and it has been restarted."
    echo "$HOST: The Apache process is not working and it has been restarted." \
    >> /var/log/apache_restarter.log
    SUBJECT="Script Apache checker: start"
    echo "$HOST: The Apache process is not working and it has been restarted." | mutt -s "$SUBJECT" $DESTINY_EMAIL
fi 

ps -fea | grep "/usr/sbin/apache2"
if test ! $? -eq 0 then
    apachectl start
    echo "$HOST: Apache has stopped and it has been reactivated."
    echo "$HOST: Apache has stopped and it has been reactivated." \
    >> /var/log/apache_restarter.log
    SUBJECT="Script Apache checker: restart"
    echo "$HOST: Apache has stopped and it has been reactivated." | mutt $DESTINY_EMAIL
-s "$SUBJECT"
fi

if [ $CURRENT_MEMORY -gt $THRESHOLD ]; then
    apachectl restart
    echo "$HOST: Restarted apache on `date +'%Y-%m-%d %H:%M:%S'`. RAM utilization at
${CURRENT_MEMORY}%"
    echo "$HOST: Restarted apache on `date +'%Y-%m-%d %H:%M:%S'`. RAM utilization at
${CURRENT_MEMORY}%" \
    >> /var/log/apache_restarter.log
    echo "Restarted apache on `date +'%Y-%m-%d %H:%M:%S'`. RAM utilization at
${CURRENT_MEMORY}%" \
    >> /var/log/apache_restarter.log
    SUBJECT="Script Apache checker: restart"
    echo "$HOST: Restarted apache on `date +'%Y-%m-%d %H:%M:%S'`. RAM utilization at
${CURRENT_MEMORY}%" | mutt $DESTINY_EMAIL -s "$SUBJECT"
fi

if [ `ps fu $USERNAME  | awk '/processname/ { x++ } END{print x}'`>
$MAX_NPROCESS_APACHE] then
    echo "$HOST: max number of apache process = ${MAX_NPROCESS_APACHE} `date
+'%Y-%m-%d %H:%M:%S'`. RAM utilization at ${CURRENT_MEMORY}% "
    echo "$HOST: max number of apache process = ${MAX_NPROCESS_APACHE} `date
+'%Y-%m-%d %H:%M:%S'`. RAM utilization at ${CURRENT_MEMORY}% " \
    >> /var/log/apache_restarter.log
    SUBJECT="Script Apache checker: max number of apache process"
    echo "$HOST: max number of apache process = ${MAX_NPROCESS_APACHE} `date
+'%Y-%m-%d %H:%M:%S'`. RAM utilization at ${CURRENT_MEMORY}% " | mutt $DESTINY_EMAIL
-s "$SUBJECT"
fi

El script esta basado en bash y ha sido probado en servidores GNU/Linux CentOs 5.4, pero debería funcionar en cualquier distribución que soporte bash. Como únicos requisitos necesita tener instalados los programas mutt (para enviar correo) y bc (para calcular datos). En CentOs puedes instalarlos con:

# yum install bc mutt

Además para su instalación necesitas añadir una tarea cron que ejecute el script periodicamente, por ejemplo para cada minuto, abre tu editor de cron con:

# crontab -e

Y suponiendo que pones el script en / escribe:

*/1 * * * * /apache_check.sh &> /dev/null

Nota: se asume que el usuario que ejecuta el script tiene permisos de ejecución para Apache y programas bc y mutt que se utilizan (normalmente root), de lo contrario no funcionará correctamente.

¿como funciona?

El script necesita que configures una dirección de envío para los mails de notificación, que puedes cambiar en el valor de la variable DESTINY_EMAIL.

La primera comprobación que hace el script es para evitar ataques DDOS en los que se intentan que Apache haga muchos procesos hijos y sature el servidor (esto puede ser limitado en Apache) pero por si hubiese alguna manera de que el atacante lo incrementara o superase, el script reiniciará apache en caso de que haya muchos procesos y de esta manera se pueda liberar memoria. Para establecer el numero minimo y máximo, se pueden configurar las variables N_MIN y MAX_NPROCESS_APACHE respectivamente.

La segunda comprobación consiste en comprobar si apache esta funcionando, por si hubiese colapsado podamos volverlo a su ejecución normal.

La tercera comprobación establece un límite de consumo de memoria en el servidor, para que en tal caso (suponiendo que es Apache en que la consume) se reinicie apache y se liberen recursos. Por defecto este limite es el 90% de memoria del servidor y puede ser cambiado con la variable THRESHOLD.

De esta manera se puede conseguir tener un servidor un poco más optimizado al uso de memoria y tener constancia de cuando se producen picos debidos a Apache.

El script lo libero con licencia GPLv3 para todos aquellos que lo necesiten y quieran hacer uso de él.

Puedes descargarlo comprimido aquí: [download id="27"]

Todas la mejoras, sugerencias, fallos y críticas son bien recibidas.