miércoles, 27 de agosto de 2014

Crea tu propio benchmarking de páginas web

El siguiente código en Python te permitirá realizar tus propios benchmarkings de páginas Web, simulando un escenario concurrente de muchos usuarios al mismo tiempo accediendo al mismo sitio Web. El resultado se almacena en un archivo de log que después podrá llevarse a una hoja de cálculo para calcular estadísticas de tiempos, o para identificar errores.

import time
import datetime
import urllib2
import logging
import threading
 
# Coleccion de urls a solicitar
urls = [
   'http://192.168.0.3/oiddigity_actual/index.html',
   'http://192.168.0.3/oiddigity_actual/registrado.asp',
   'http://192.168.0.3/oiddigity_actual/inicio.html',
   'http://192.168.0.3/oiddigity_actual/botones2.html',
   'http://192.168.0.3/oiddigity_actual/horafecha.asp'

 
# Nombre del fichero de log para guardar resultados
fich_log = 'testeo.log'
 
# Numero de hilos
num_hilos = 200
 
# Numero de procesos por hilo
num_procesos = 100
 
# Conteo de errores
num_errores = 0
 
# Metodo: proceso_urls()
# Procesa las urls, retornando el
# tiempo que consume
def proceso_urls():
   inicio = datetime.datetime.now()
   resultado = ""
 
   # Solicita y lee todas las urls
   for i in urls:
      try:
         peticion = urllib2.Request(i)
         respuesta = urllib2.urlopen(peticion)
         contenido = respuesta.read()
         resultado = "|OK"
         break
      except Exception as e:
         resultado = "|NOK[" + i + "]" + format(str(e))
 
   fin = datetime.datetime.now()
   tiempo = inicio.strftime("%T.%f")+'|'+fin.strftime("%T.%f")+"|"
   tiempo += get_tiempo(fin-inicio)
   tiempo += resultado
 
   return tiempo
 
# Metodo: get_tiempo()
# Retorna, a partir de un time, el tiempo en formato %H|%M|%S|%f
def get_tiempo(tiempo):
   resultado = ""
   segundos = tiempo.seconds
 
   if (segundos>=3600):
      horas = int(segundos/3600)
      resultado += str(horas) + "|"
   else:
      resultado += "0|"
 
   if (segundos>=60):
      minutos = int(segundos/60)
      resultado += str(minutos) + "|"
   else:
      resultado += "0|"
 
   resultado += str(segundos) + "|" + str(tiempo.microseconds)
 
   return resultado
 
# Metodo: proceso_hilo()
# Metodo principal del hilo
def proceso_hilo(count):
   global num_errores # Referencia a variable global compartida
 
   print 'Iniciando hilo ', count
 
   for i in range(num_procesos):
      resultado = proceso_urls()
 
      if (resultado.find("NOK") > 0):
         num_errores += 1
         print "#", num_errores, " > ", str(count), "[", str(i), "] > ", resultado
 
      logging.info(''+str(count)+"|"+str(i)+"|"+resultado)
 
   print 'Hilo', count, 'finalizado'
 
 
# Configuracion del fichero de log
logging.basicConfig(filename=fich_log, filemode='w', level=logging.DEBUG)
# Cabecera del log
logging.info('Hilo|Proceso|Inicio|Fin|Horas|Minutos|Segundos|Milisegundos|Resultado')
 
print '----------------------------------'
print 'Testeo. 2014 by Rafael Hernamperez'
print '----------------------------------'
print 'URLs a testear:'
 
for i in urls:
   print ' - ', i
 
print '> Ejecutando ', num_hilos, ' hilos'
print '> Total procesos: ', num_hilos * num_procesos
 
# Genera los hilos y los ejecuta
threads = list()
 
for i in range(num_hilos):
   t = threading.Thread(target=proceso_hilo, args=(i,))
   threads.append(t)
   t.start()

La variable urls permite definir las urls que van a solicitarse. Una página web puede tener varios iframes incluyendo otras partes de código web (otras páginas o servicios), las cuales han de invocarse para tener tiempos reales.

La variable fich_log define el nombre del fichero de log, en el cual se volcarán todos los resultados.

La variable num_hilos define el número de conexiones concurrentes. Esto simulará el escenario de n usuarios accediendo al mismo tiempo a las urls definidas.

La variable num_procesos define el número de veces que cada hilo llamará a las urls definidas.

La variable num_errores recoge el número de errores que se pueda producir durante el testeo.

El método proceso_urls() solicita las urls definidas y leyendo las respuestas por parte del servidor web. Tras el proceso guarda en el log el resultado con los tiempos y los posibles errores.

El método get_tiempo() retorna un literal con la descomposición del tiempo que se pasa por parámetro. Este literal se utiliza para el log, y es invocado por proceso_urls() para obtener el desglose del tiempo que ha tardado el proceso en ejecutarse.

El método proceso_hilo() es utilizado por cada uno de los hilos para lanzar todos los procesos definidos en la variable num_procesos. Es decir, repite num_procesos veces el acceso a las urls definidas.

Por último, para hacer funcionar esta aplicación, crea un fichero llamado (por ejemplo) testeo.py, copia y pega el código, guárdalo y ejecútalo desde la consola o terminal mediante el comando:

$ python testeo.py

Configurar la concurrencia en Apache y MySQL

Llega un momento en el que una aplicación Web tiene tanto éxito que el número de conexiones concurrentes satura las capacidades del servidor. Empiezan a aparecer errores de timeout o errores 500 Internal Server Error.

Para evitar ésto, se puede configurar Apache y MySQL para que admitan más concurrencia, es decir, más conexiones funcionando al mismo tiempo.

Concurrencia en Apache

Para configurar la concurrencia en Apache, hemos de configurar el archivo httpd.conf (el archivo será apache2.conf si está instalado en un Linux basado en Debian e instalado mediante sudo apt-get install apache2) o el archivo 000-default.conf.

Lo primero que debemos hacer es extraer la configuración actual de Apache. Para ello ejecutaremos el siguiente comando desde la terminal:

$ apache2 -V

El resultado será similar al siguiente:
Server version: Apache/2.2.22 (Debian)
Server built: Jul 24 2014 15:34:00
Server's Module Magic Number: 20051115:30
Server loaded: APR 1.4.6, APR-Util 1.4.1
Compiled using: APR 1.4.6, APR-Util 1.4.1
Architecture: 64-bit
Server MPM: Prefork
threaded: no
forked: yes (variable process count)
Server compiled with....
-D APACHE_MPM_DIR="server/mpm/prefork"
-D APR_HAS_SENDFILE
-D APR_HAS_MMAP
-D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
-D APR_USE_SYSVSEM_SERIALIZE
-D APR_USE_PTHREAD_SERIALIZE
-D APR_HAS_OTHER_CHILD
-D AP_HAVE_RELIABLE_PIPED_LOGS
-D DYNAMIC_MODULE_LIMIT=128
-D HTTPD_ROOT="/etc/apache2"
-D SUEXEC_BIN="/usr/lib/apache2/suexec"
-D DEFAULT_PIDLOG="/var/run/apache2.pid"
-D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
-D DEFAULT_LOCKFILE="/var/run/apache2/accept.lock"
-D DEFAULT_ERRORLOG="logs/error_log"
-D AP_TYPES_CONFIG_FILE="mime.types"
-D SERVER_CONFIG_FILE="apache2.conf"

Los primeros ajustes a realizar son:

  • KeepAlive On: Permite conexiones persistentes (más de una petición por conexión)
  • MaxKeepAliveRequests 100: Número máximo de peticiones permitidas durante una conexión persistente
  • KeepAliveTimout 5: Número de segundos de espera para la próxima petición en la misma conexión

A continuación, se ha de configurar el módulo de multiprocesamiento (MPM). En el ejemplo anterior lo conoceremos por la primera línea de la sección "Server compiled with...", en el parámetro APACHE_MPM_DIR. Como se puede observar, el módulo de multiprocesamiento es prefork. Por tanto, hemos de buscar en el archivo de configuración de Apache la sección correspondiente a este módulo, el cual contendrá los siguientes parámetros:

<IfModule mpm_prefork_module>
   StartServers 50
   MinSpareServers 20
   MaxSpareServers 70
   ThreadLimit 64
   ThreadsPerChild 25
   ServerLimit 500
   MaxClients 500
   MaxRequestsPerChild 0
</IfModule>

Los significado de estos parámetros es el siguiente:

  • StartServers: Número de servidores con los que arranca
  • MinSpareServers: Número mínimo de procesos de servidor que se mantienen de repuesto
  • MaxSpareServers: Número máximo de procesos de servidor que se mantienen de respuesto
  • ThreadLimit: Número máximo de hilos concurrentes
  • ThreadsPerChild: Número de hilos en funcionamiento en cada proceso del servidor
  • ServerLimit: Núḿeros máximo de servidores
  • MaxClients: Número máximo de procesos de servidor con los que arranca
  • MaxRequestsPerChild: Número máximo de peticiones que un proceso puede servir

NOTA: En el caso de nuestro Apache no tenga instalado un módulo de multiprocesamiento, podemos instalar uno mediante los siguientes comandos:

sudo apt-get update
sudo apt-get install apache2-mpm-prefork

Concurrencia en MySQL

Aunque configuremos Apache para que abastezca una gran cantidad de concurrencia, si nuestras webs acceden a bases de datos MySQL, encontrarán otro cuello de botella, pues MySQL también tiene una configuración predeterminada de concurrencia a la base de datos (unas 100 conexiones).

Para ajustar la concurrencia en MySQL, hemos de editar en el archivo my.cnf los siguientes parámetros:

  • max_connections: Número máximo de conexiones
  • thread_concurrency: Número de hilos concurrentes

A continuación, hay que rearrancar la base de datos para que MySQL asuma los cambios:

/etc/init.d/mysql restart

NOTA: Este comando es para Linux. En Windows, es similar, salvo la ruta.

Por último, verificamos que MySQL está funcionando con los valores actualizados. Para ello, arrancamos el cliente MySQL:

mysql -u root -p

Una vez nos logamos como administrador (hay que introducir la contraseña), en la línea de comandos del cliente MySQL introducimos la siguiente sentencia:

mysql> SHOW VARIABLES;

Buscamos las variables max_connections y thread_concurrency para verificar sus valores.

lunes, 25 de agosto de 2014

Conceptos básicos en CakePHP

Instalar Adobe Reader en Linux Debian, Ubuntu, Mint...

En primer lugar, descargar el paquete de instalación para Linux de Adobe Reader:

$ wget http://ardownload.adobe.com/pub/adobe/reader/unix/9.x/9.5.5/enu/AdbeRdr9.5.5-1_i386linux_enu.deb

A continuación instalar Adobe Reader:

$ sudo dpkg -i --force-architecture AdbeRdr9.5.5-1_i386linux_enu.deb

Configurar mod_rewrite para Kumbia y CakePHP

Kumbia y CakePHP son dos frameworks MVC (Modelo Vista Controlador) para PHP muy populares y bastante similares. Para empezar a utilizar ambos, es requisito fundamental instalar Apache y realizar algunos ajustes en la configuración. El ajuste más crítico es el de mod_rewrite, el cual permite a Apache gestionar los direccionamientos (routings) generados por estos frameworks.

Para realizar esta configuración, es necesario configurar el archivo httpd.conf. Nota: en el caso de una instalación en distribuciones basadas en Ubuntu, es posible que no exista este archivo. Ello se debe a que está configurado para que utilice el archivo apache2.conf en su lugar).

LoadModule rewrite_module modules/mod_rewrite.so

Más adelante, en el mismo archivo, encontraremos una sección como ésta:

<Directory />
   ...
</Directory>

Esta sección configura los permisos o roles que tendrá, para apache, el acceso al directorio especificado y a sus subcarpetas. En este caso comenzaría en el directorio raíz del equipo, lo cual es muy crítico y peligroso. Por ello, debemos definir (si no lo tenemos ya), una sección que apunte al directorio de nuestra aplicación Kumbia o CakePHP, el cual suele ubicarse en el directorio principal de páginas Web de Apache. En su interior deberíamos tener la siguiente configuración:

<Directory "/var/www/miaplicacionweb">
   Options FollowSymLinks
   AllowOverride All
   Require all granted
</Directory>

Una vez hechos los cambios, se guarda el archivo y se reinicia el servidor Apache para que reconozca la nueva configuración:

sudo /etc/init.d/apache2 start

Con esto debería funcionar el routing de nuestras aplicaciones.

Posibles problemas

Si al lanzar el contenido de la aplicación da problemas con mod_rewrite, puede ser que la ruta del archivo mod_rewrite.so no sea la correcta. En ese caso hay buscar la ruta del mismo con:

$ locate mod_rewrite.so

ó con

$ sudo find / find -name mod_rewrite.so

y modificar el archivo httpd.conf (o apache2.conf, si es el caso), con la ruta correcta.

Si aún así sigue teniendo problemas, es posible que PHP no esté utilizando el módulo mod_rewrite. Para saberlo, lanzar una página phpinfo() y buscar el literal "mod_rewrite". Debería aparecer en una sección llamada "Configuration apache2handler", en la fila "Loaded Modules". Si no aparece, podemos instalar el módulo mediante el siguiente comando:

$ sudo a2enmod rewrite

Si a pesar de todo lo anterior, sigue sin funcionar, puede ser que Apache esté configurado para utilizarse como servidor virtual. En ese caso, la configuración de Apache sale del fichero de configuración alojado en /etc/apache2/sites-available. En dicho directorio habrá dos ficheros: 000-default.conf y default-ssl.conf. Si no se ha instalado o configurado Apache para utilizar protocolo SSL, editaremos el primer archivo y lo configuraremos como el httpd.conf. En el caso de utilizar el protocolo SSL, configuraremos el segundo archivo.

Tras realizar los cambios, habrá que reiniciar el servidor Apache y debería funcionar todo.

jueves, 21 de agosto de 2014

Guía de Instalación de Apache y PHP en Ubuntu y Linux Mint

Instalación de Apache y PHP5:

$ sudo apt-get install apache2
$ sudo apt-get install php5
$ sudo apt-get install libapache2-mod-php5
$ sudo /etc/init.d/apache2 restart

Para arrancar, parar y rearrancar Apache:

$ sudo /etc/init.d/apache2 start
$ sudo /etc/init.d/apache2 stop
$ sudo /etc/init.d/apache2 restart

Adicionalmente, se pueden instalar extensiones o módulos pdo, con lo que podremos añadir funcionalidades a php, tales como acceder a bases de datos, internacionalización, encriptación, json, etc.

$ sudo apt-get install php5-dev
$ sudo apt-get install php5-mysql
$ sudo apt-get install php5-pgsql
$ sudo apt-get install php5-sqlite
$ sudo apt-get install php5-intl
$ sudo apt-get install php5-mcrypt
$ sudo apt-get install php5-gd
$ sudo apt-get install php5-curl
$ sudo apt-get install php5-json

Para conocer los módulos PHP instalados con apt-get:

$ dpkg --list | grep php

Para ver la versión de PHP:

$ php -v

Para ver la información completa de la configuración de PHP:

$ php -i

Para ver los módulos instalados con PHP:

$ php -m

Para verificar que Apache y PHP están configurados bien, crear un archivo info.php en /var/www (o en el directorio html por defecto de Apache):

<?php
phpinfo();
?>
Abrir un navegador y escribir la url: http://localhost/info.php. Debería aparecer la siguiente pantalla con toda la información de nuestra instalación de PHP: