Integración continua con Drupal 7 (I/V)

01/05/2013

Este post es el primero de una serie de cinco (que espero poder escribir) en los que trataremos la manera de pasar un proyecto desarrollado con Drupal 7 a integración continua.

¿Cuál es el objetivo final de todo esto?, ¿qué es lo que perseguimos en todo este proceso?

Lo que se pretendo conseguir con este proceso de integración continua no es ni más ni menos que mantener a nuestros clientes totalmente informados, es más sobreinformados, de los avances en el proyecto de la misma manera que cuando compramos cualquier producto en cualquier tienda en Internet (de mediana calidad).

Todo esto se puede realizar regularmente de manera manual, remitiéndole a nuestros clientes un informe de evolución del proyecto en el que se detalle el grado de evolución de cada una de las tareas que se han completado y sobre todo cuál es el aspecto que va tomando su proyecto desplegando en nuestro entorno de preproducción.

Evidentemente realizar este proceso de una manera manual es algo que consumiría mucho tiempo, y que por tanto es susceptible de ser automatizado, en mayor o menor medida, con el fin de conseguir una mayor productividad.

Para conseguir este objetivo necesitamos, en primer lugar, definir la infraestructura de desarrollo que vamos a utilizar. En el siguiente esquema se muestra una posible definición de la misma.

schemas_01

Como se puede observar tenemos varios programadores cada uno de los cuales cuenta con un entorno de desarrollo propio, conectado con una base de datos común, estos programadores sincronizan su código entre ellos a través de SVN.

Uno de los elementos que necesitaremos será un sistema de integración continua que se encargue de ejecutar tareas de manera automática, en este caso he optado por Jenkins por su rapidez de instalación (aunque no pierdo de vista CruiseControl). Las tareas a ejecutar serían:

  1. Ejecución de pruebas
  2. Despliegue automático del proyecto
  3. Notificación automática del resultado
  4. Envío de informe de evolución

Los puntos 1, 3 y 4 son los que aún están pendientes, pero el punto 2 sí lo tengo mucho más avanzado y es el que vamos a ver ahora … manos a la obra.

Lo primero un esquema de vamos a montar.

ci

Para llevar a cabo el despliegue automático vamos a desarrollar un script SH que haciendo uso de drush, se encargue de:

  1. Generar el fichero de despliegue
  2. Realizar el backup de la copia anterior en el entorno de preproducción
  3. Desplegar la nueva versión en el entorno de preproducción
#!/bin/bash

# VARIABLES
fecha=$(date +"%Y%m%d%H%M%S")

inicio=$(date +"%s")

project=P0001_proyecto_de_prueba
project_name=drupalci
project_repo=http://192.168.1.126/repo/$project
project_repo_trunk=/trunk

deploy_env_host=192.168.1.125
deploy_env_user=deploy
deploy_env_path=/var/www/$project

# Comienza la ejecución
echo '['$(date +"%d/%m/%Y-%H:%M:%S:%S")'] Comienza el proceso de despliegue del proyecto '$project

# Se hace un tag del trunk svn
echo '['$(date +"%d/%m/%Y-%H:%M:%S")'] Se genera la etiqueta '$fecha
svn copy $project_repo$project_repo_trunk $project_repo/tags/$fecha -m 'Etiqueta correspondiente con el despliegue '$fecha

# Se hace un export del tag
svn export $project_repo/tags/$fecha /tmp/$project/$fecha/
echo '['$(date +"%d/%m/%Y-%H:%M:%S")'] Se genera un export de la etiqueta '$fecha

# Se genera el fichero de release
cd /tmp/$project/$fecha/
drush archive-dump default --destination=/tmp/$project/$fecha/$fecha.tar
echo '['$(date +"%d/%m/%Y-%H:%M:%S")'] Se genera el fichero de release en '/tmp/$project/$fecha/$fecha.tar

# Se sube al entorno de despliegue
scp /tmp/$project/$fecha/$fecha.tar $deploy_env_user@$deploy_env_host:$deploy_env_path
echo '['$(date +"%d/%m/%Y-%H:%M:%S")'] Se sube al entorno de despliegue el fichero generado'

# Se hace un backup de la copia anterior y se elimina
ssh $deploy_env_user@$deploy_env_host 'rm -rf '$deploy_env_path'/old.tar'
echo '['$(date +"%d/%m/%Y-%H:%M:%S")'] Se elimina la copia del día anterior'
ssh $deploy_env_user@$deploy_env_host 'cd '$deploy_env_path'/'$project_name'; drush archive-dump --destination='$deploy_env_path'/old.tar'
echo '['$(date +"%d/%m/%Y-%H:%M:%S")'] Se genera una nueva copia del despliegue del día anterior'
ssh $deploy_env_user@$deploy_env_host 'rm -rf '$deploy_env_path'/'$project_name
echo '['$(date +"%d/%m/%Y-%H:%M:%S")'] Se elimina el despliegue anterior'

# Se despliega y se elimina el fichero de release
ssh $deploy_env_user@$deploy_env_host 'drush archive-restore '$deploy_env_path'/'$fecha'.tar --db-url=mysql://root:root@localhost/drupalci --destination='$deploy_env_path'/'$project_name
echo '['$(date +"%d/%m/%Y-%H:%M:%S")'] Se despliega la plataforma'
ssh $deploy_env_user@$deploy_env_host 'rm -rf '$deploy_env_path'/'$fecha'.tar'
echo '['$(date +"%d/%m/%Y-%H:%M:%S")'] Se elimina la copia de despliegue'

# Se modifica el fichero de settings por el de producción
ssh $deploy_env_user@$deploy_env_host 'rm -rf  '$deploy_env_path'/'$project_name'/sites/default/settings.php'
echo '['$(date +"%d/%m/%Y-%H:%M:%S")'] Se elimina el fichero de settings'
ssh $deploy_env_user@$deploy_env_host 'mv '$deploy_env_path'/'$project_name'/sites/default/settings_pro.php '$deploy_env_path'/'$project_name'/sites/default/settings.php'
echo '['$(date +"%d/%m/%Y-%H:%M:%S")'] Se activa el fichero de settings de producción'

# Se elimina el fichero generado en local
rm -rf /tmp/$project
echo '['$(date +"%d/%m/%Y-%H:%M:%S")'] Se elimina el fichero local generado'

# Finaliza el proceso
fin=$(date +"%s")
tiempo=$((fin - inicio))
echo '['$(date +"%d/%m/%Y-%H:%M:%S")'] El proceso ha concluido con éxito en '$tiempo' segundos'

Como podemos ver el script no es totalmente funcional, ya que no cuenta con gestión de errores, que en definitiva serán los que indicarán si el proceso ha terminado correctamente.

Como pre-requisitos para este script debemos contar con:

  • Cliente SVN por consola 
  • Instalación de DRUSH
  • Acceso por SSH por certificado

¿Qué os parece?, ¿alguna aportación?


Integrando Android y Drupal

18/06/2011

Desde los inicios de S·dos los mercados de movilidad y gestión de contenidos ha sido dos de los tres grandes ejes del desarrollo del negocio de S·dos, prueba de esta apuesta tecnológica y económica son el desarrollo tanto de numerosas apliacaciones web, como portales de gestión de contenidos, basados principalmente en Drupal, que se han desarrollado en estos tres años de vida de S·dos.

En esta continua experiencia de mejora, trabajo e innovación que nos caracteriza, hemos llevado a cabo un amplio trabajo de integración entre aplicaciones web y aplicaciones móviles bajo los paradigmas de desarrollo actuales principalmente basados en la integración de aplicaciones móviles con sistemas externos ad-hoc con el que se comunican para diversas tareas.

Cuando nos encontramos ante la situación de llevar a cabo la implementación de esta integración contra un sistema basado en un CMS la forma de llevar a cabo la integración cambia, ya que se ha de ser capaz de ofrecer a la aplicación móvil las funcionalidades que nos demande, pero bajo el contexto y las reglas que nos marca el CMS.

Cuando hablamos de Drupal, la integración es sencilla (por supuesto si conocemos y comprendemos su arquitectura funcional) gracias a la arquitectura lógica del sistema que basa su escalabilidad funcional en la implementación de métodos gancho y su utilización en módulos de terceras partes.

Para llevar a cabo este tipo de integraciones en S·dos hemos apostado por utilizar una arquitectura basada en REST a través de peticiones HTTP/GET desde la aplicación Android al portal web Drupal, respondiendo éste en formato JSON.

Un posible ejemplo de esta arquitectura podrían ser los siguientes fragmentos de código:

Ejemplo de petición Android:

public JSONArray getResultados() {
    JSONArray jsonArray = new JSONArray();
    // Se configura la petición
    HttpClient httpClient = new DefaultHttpClient();
    HttpConnectionParams.setConnectionTimeout(httpClient.getParams(), 10000);
    // Se realiza la petición a la url necesaria
    HttpPost r = new HttpPost(Config.getUrlBase()+url_menu+idMenu);
    HttpParams params = new BasicHttpParams();
    List nameValuePairs = new ArrayList(2);
    try {
        r.setEntity(new UrlEncodedFormEntity(nameValuePairs));
    } catch (UnsupportedEncodingException e1) {
        // TODO: Tratamiento de la excepción
    }
    // Se configura la cabecera de la petición
    r.setHeader("Content-Type", "application/x-www-form-urlencoded");
    try {
        // Se realiza la petición
        HttpResponse response;
        response = httpClient.execute(r);
        HttpEntity resEntityGet = response.getEntity();
        String json = new String();
        if (resEntityGet != null) {
            // Se obtiene el JSON de respuesta
            json = EntityUtils.toString(resEntityGet);
        }
        try {
            JSONObject jsonResult = new JSONObject(json);
            jsonArray = jsonResult.getJSONArray(jsonCampo);
        } catch (JSONException e) {
            // TODO: Tratamiento de la excepción
        }
    } catch (ClientProtocolException e2) {
        // TODO: Tratamiento de la excepción
    } catch (IOException e) {
        // TODO: Tratamiento de la excepción
    }
    return jsonArray;
}

Para dar respuesta a esta petición simplemente tendremos que crear una entrada de menú en un módulo Drupal a través del hook_menu:

$items['mobile/ejemplo/%'] = array(
'title' => 'Ejemplo',
'page callback' => 'ver_ejemplo',
'page arguments' => array(2),
'access arguments' => array('access content'),
'type' => MENU_NORMAL_ITEM,
);

y la correspondiente función de que atiende la entrada de menú:

function ver_ejemplo($nid){
return drupal_json(array('ejemplo' => node_load($nid)));
exit();
}

Como podemos imaginar una vez sentados estos conceptos la arquitectura se puede extender de la forma que deseemos para dar respuesta a las necesidades de nuestros clientes, permitiendo autenticación, integración con vistas, etc.

“Dime y lo olvido, enséñame y lo recuerdo, involúcrame y lo aprendo”
— Benjamín Franklin


Hacia portales OpenCms en integración continua

20/07/2010

Hace ya muchísimo tiempo que no escribo nada en el blog. La verdad que no ha sido por falta de ganas, si no como suele ocurrir por falta de tiempo. En todo este tiempo he seguido trabajando en el módulo OpenCms Free Balance pero, desde que los amigos de OpenCms Hispano me invitaran como ponente a la 2ª tarde tecnológica de OpenCms, he estado trabajando en algo que por aquellos días solo conseguí esbozar y que la verdad sea dicha me ha costado mucho más de lo que en un principio pensaba, pero creo que los resultado han merecido la pena: un plugin de maven que permite a los desarrolladores, tras compilar el módulo, desplegarlo en OpenCms automáticamente

En un principio esa fue realmente la idea original, para la versión beta, ya que después, dándole una vuelta de turca más en base a mi experiencia en el desarrollo de portales haciendo uso de tecnología OpenCms, se me ocurrió que sería mucho más interesante que el plugin, además permitira el despliegue del módulo en un servidor remoto de forma que hiciera factible la integración continua.

Aunque crear un nuevo plugin en Maven es realmente fácil, los mayores problemas han sido:

  1. Gestionar todas las dependencias necesarias para la obtención de un objeto CmsObject fuera del contexto de OpenCms.
  2. Obtención del propio objeto CmsObject, en función de la versión de OpenCms que se use el modo de obtención de dicho CmsObject será distinto.
  3. Importación y publicación del módulo dentro del contexto de OpenCms.

Solucionar el problema fue fácil aunque muy tedioso ya que el proceso consistió en compilar el módulo ejecutar el plugin y cuando fallaba se busca y declara la dependencia, el resultado final ha sido:

<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>servlet-api</artifactId>
<version>6.0.26</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>oro</groupId>
<artifactId>oro</artifactId>
<version>2.0.8</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>20040616</version>
</dependency>
<dependency>
<groupId>org.safehaus.uuid</groupId>
<artifactId>uuid</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons.digester</groupId>
<artifactId>digester</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>20030211.134440</version>
</dependency>
<dependency>
<groupId>quartz</groupId>
<artifactId>quartz</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>org.htmlparser</groupId>
<artifactId>htmlparser</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>jsp-api</artifactId>
<version>6.0.26</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queries</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-snowball</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>commons-email</groupId>
<artifactId>commons-email</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.7</version>
</dependency>
<dependency>
<groupId>com.alkacon</groupId>
<artifactId>simapi</artifactId>
<version>0.9.8</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.1</version>
</dependency>

El siguiente problema a que tuve que solucionar fue, como he comentado antes, la obtención de un objeto CmsObject, para ello tuve que apoyarme en el código fuente de varios plugins de eclipse que permiten sincronizar los ficheros físicos con los ficheros de OpenCms, a continuación os dejo algunas de las referencias que me han sido de utilidad:

  1. OpenCms Mod Dev
  2. OpenCms VFS

Y el último problema que tuve que solucionar fue la importación del módulo, importación que para nada es trivial y para solucionarlo una vez más tuve que adentrarme en el código fuente de OpenCms para finalmente encontrar la solución:

String importpath = OpenCms.getSystemInfo().getPackagesRfsPath();

importpath = OpenCms.getSystemInfo().getAbsoluteRfsPathRelativeToWebInf(
importpath + “modules/” + config.getModuleName());

// Se obtiene el módulo
module = CmsModuleImportExportHandler.readModuleFromImport(importpath);

// check the module dependencies
List dependencies = OpenCms.getModuleManager().checkDependencies(module,
CmsModuleManager.DEPENDENCY_MODE_IMPORT);
if (dependencies.size() > 0) {
// some dependencies not fulfilled
StringBuffer missingModules = new StringBuffer();
Iterator it = dependencies.iterator();
while (it.hasNext()) {
CmsModuleDependency dependency = (CmsModuleDependency) it.next();
missingModules.append(”  “).append(dependency.getName()).append(“, Version “).append(
dependency.getVersion()).append(“\r\n”);
}
System.out.println(“[ERROR] ” + module.getName() + “, Version ” + module.getVersion() + missingModules);
}

// check the imported resource types for name / id conflicts
List checkedTypes = new ArrayList();
Iterator i = module.getResourceTypes().iterator();
while (i.hasNext()) {
I_CmsResourceType type = (I_CmsResourceType) i.next();
// first check against the already configured resource types
int externalConflictIndex = OpenCms.getResourceManager().getResourceTypes().indexOf(type);
if (externalConflictIndex >= 0) {
I_CmsResourceType conflictingType = (I_CmsResourceType) OpenCms.getResourceManager()
.getResourceTypes().get(externalConflictIndex);
if (!type.isIdentical(conflictingType)) {
// if name and id are identical, we assume this is a module replace operation
throw new CmsConfigurationException(org.opencms.loader.Messages.get().container(
org.opencms.loader.Messages.ERR_CONFLICTING_MODULE_RESOURCE_TYPES_5,
new Object[] {type.getTypeName(), new Integer(type.getTypeId()), module.getName(),
conflictingType.getTypeName(), new Integer(conflictingType.getTypeId())}));
}
}
// now check against the other resource types of the imported module
int internalConflictIndex = checkedTypes.indexOf(type);
if (internalConflictIndex >= 0) {
I_CmsResourceType conflictingType = (I_CmsResourceType) checkedTypes.get(internalConflictIndex);
throw new CmsConfigurationException(org.opencms.loader.Messages.get().container(
org.opencms.loader.Messages.ERR_CONFLICTING_RESTYPES_IN_MODULE_5,
new Object[] {module.getName(), type.getTypeName(), new Integer(type.getTypeId()),
conflictingType.getTypeName(), new Integer(conflictingType.getTypeId())}));
}
// add the resource type for the next check
checkedTypes.add(type);
}

// Se comprueba si el módulo esta instalado
if(!OpenCms.getModuleManager().hasModule(module.getName())){
OpenCms.getModuleManager().addModule(cmsObject, module);
} else {
OpenCms.getModuleManager().updateModule(cmsObject, module);
}

// reinitialize the resource manager with additional module resource types if necessary
if (module.getResourceTypes() != Collections.EMPTY_LIST) {
OpenCms.getResourceManager().initialize(cmsObject);
}
// reinitialize the workplace manager with additional module explorer types if necessary
if (module.getExplorerTypes() != Collections.EMPTY_LIST) {
OpenCms.getWorkplaceManager().addExplorerTypeSettings(module);
}

// import the module resources
CmsImport cmsImport = new CmsImport(cmsObject,
new CmsShellReport(cmsObject.getRequestContext().getLocale()));
CmsImportParameters parameters = new CmsImportParameters(importpath, “/”, true);
cmsImport.importData(parameters);

cmsObject.publishResource(“/”);

Para la primera versión del módulo, una vez que compruebe el correcto funcionamiento en distintas plataformas el objetivo de esta primera versión estará cumplido, el siguiente paso será que el plugin permita el despliegue remoto de módulos de forma que se permita la integración continua.


Integrando Hibernate y Maven en un módulo de OpenCms

28/04/2010

Una de las tareas que forman parte de la release-beta del módulo opencms-free-balance es llevar a cabo la integración de este módulo con Hibernate para facilitar su interacción con la base de datos, para ejecutar esta tarea he seguido con el enfoque que en GMV seguimos (y que recomiendo encarecidamente) a la hora de trabajar con este gestor de contenidos y sus módulos, utilizar Maven como herramienta de construcción de dichos módulos.

La utilización de Maven como herramienta de construcción en el proceso de desarrollo de los módulos facilita enormemente tareas habituales como: la gestión de  dependecias, generación del desplegable en OpenCms, generación de reportes, ejecución de tests, etc.

Bueno, pues manos a la obra. El primer paso para integrar Hibernate en un módulo de OpenCms es declarar las dependecias necesarias en el pom.xml del módulo, a continuación se deberán declarar en el fichero manifest.xml para que sean incluidas en el directorios WEB-INF/lib de OpenCms. También se tendrá que configurar el fichero hibernate.cfg.xml con los datos de conexión con la base de datos, anotar convenientemente las entidades que interacutarán con la base de datos y por último se tendrá que implementar la interfaz DAO que se haya definido (si es que se ha definido). Adjunto el módulo para que os sirva como ejemplo/punto de partida (es el proyecto eclipse completo, para generar el instalable, como siempre, ejecutar “mvn clean install”).

Esta posible solución es válida en aquellos módulos, como es el caso del módulo opencms-free-balance, que deben ser lo más autocontenidos, fáciles de instalar e independientes. En otros escenarios, en los que se está llevando a cabo un desarrollo a medida, con varios módulos accediendo a la base de datos, es mejor otro tipo de aproximaciones como la propuesta por Saga con su módulo de integración (de propósito general) entre OpenCms e Hibernate.

Lo maravilloso de aprender es que nadie puede arrebatárnoslo.


Sincronización de instancias de OpenCms

08/02/2010

Uno de los temas sobre los que más se habló en la tarde tecnológica de OpenCms fue sobre los problemas que existían a la hora de poner OpenCms en clúster sin contar con el módulo de pago OCEE, pues bien, desde hace algún tiempo vengo trabajando, en mis ratos libres, en un módulo para este CMS que permita disponer de varias instancias del gestor de contenidos, consumiendo los datos de una base de datos común, de forma que los cambios que realice en una instancia se propaguen a las demás. Por defecto, OpenCms dispone de una serie de funcionalidades de caché que no permiten el comportamiento anterior, dificultando de esta forma la posibilidad de disponer de un clúster de instancias de OpenCms.

Para paliar este problema he desarrollado el módulo “org.opencms.free.balance” cuya base de funcionamiento es sencilla, cada vez que se realice una modificación de los contenidos, desde cualquiera de las instancias de OpenCms, todas las demás se actualizan, a través de un servicios web, de forma que el contenido está disponible y listo para ser servido por cualquiera de los nodos.

La anatomía del módulo es la siguiente:

  1. admin: Componente encargado de llevar a cabo la administración de los nodos, en esta primera versión alpha sólo CRUD de los nodos del cluster.
  2. action: Componente encargado de interceptar las acciones que se realizan sobre OpenCms y en función de dicha acción creación, modificiación, etc le pedirá a los demás nodos del clúster que se actualicen para que puedan disponer de los cambios realizados.
  3. client: Componente que se encarga de realizar la llamada de sincronización del servicio web.
  4. webservice: Componente que se encarga de atender las peticiones de los clientes y de sincronizar, borrando la caché de OpenCms del servidor que se encuentra alojado.

Para llevar a cabo su instalación es necesario, además de desplegar el módulo, instalar un servicio web en el contexto de OpenCms.

He creado un proyecto en Google Code donde os podréis descargar el módulo, por supuesto, cualquiera que se anime a colaborar será bienvenido😉


Tarde Tecnológica OpenCms

14/01/2010

Esta tarde he tenido la oportunidad de poder asistir a un evento organizado por OpenCms Hispano e Ingenia titulado “Tarde Tecnológica OpenCms“.

Respecto al evento y a todo lo que él se ha hablado y tratado sólo puedo tener palabras de alabanza. Ha sido una reunión en la que muchos profesionales del sector que nos dedicamos al desarrollo web basado en OpenCms hemos expuesto nuestras experiencias e impresiones y en la que se han puesto de manifiesto una serie de ideas que pueden dar como resultado, con el esfuerzo de todos, un cambio en la dirección en la que se mueve actualmente la comunidad de OpenCms.

A todos los asistentes muchas gracias y en especial a los organizadores del evento, Sergio Raposo y Alejandro Alves, sólo espero que pronto podamos volver a vernos todos y continuar tratando temas tan interesantes y útiles como los que hemos tratado hoy.

Personalmente me ha encantado la idea que Sergio a propuesto de reunirnos para hacer un sprint.

A todos muchas gracias.


Rendimiento en aplicaciones PHP

22/10/2009

En esta última semana he comenzado a participar en un nuevo proyecto. Consiste en hacer una auditoría del rendimiento y la calidad del software de una aplicación implementada con tecnología PHP. El problema está en que la aplicación va degradando el servidor de aplicaciones web Apache hasta que este provoca una denegación de servicio y es preciso reiniciarlo.

Para esta auditoría se van a utilizar diversas herramientas:

1. Análisis del rendimiento de la aplicación web. En esta parte se localizarán los cuellos de botella en tiempo de ejecución y en definitiva los motivos por los cuales la aplicación consigue degradar el servidor hasta el punto de hacer que provoque la denegación de servicio antes comentada. Para ello se van a utilizar dos herramientas, The Grinder y XCacheGrind.

  • The Grinder. Me la recomendó mi compañero Antonio, y es simplemente espectacular. Es una herramienta basada software libre y su funcionamiento e instalación es muy simple, está basado en una consola central y en uno o varios agentes, cada uno de estos agentes (que son distribuidos entre distintas máquinas físicas) despliega una serie de workers y cada uno de estos workers despliega los hilos que los agentes tengan configurados. Además proporciona un proxy que nos permite realizar las pruebas UI fácilmente con cualquier navegador.
Esquema de funcionamiento de The Grinder

Esquema de funcionamiento de The Grinder

  • XCacheGrind. Esta herramienta tiene dos variantes WinCacheGrind y KCacheGrind y permiten visualizar gráficamente el consumo de tiempo que cada una de las funciones o scripts PHP ha tardado en ejecutarse y otra información que puede ser de interés para realizar las tareas de profile. Para ello hace uso de los logs que proporciona XDebug, que se tendrán que configurar con los siguientes parámetros:

xdebug.remote_autostart = On
xdebug.remote_enable=On
xdebug.profiler_output_dir = “/home/aclr/kcachegrind/”
xdebug.trace_output_dir = “/home/aclr/kcachegrind/”
xdebug.profiler_append = On
xdebug.profiler_enable = On
xdebug.auto_trace = On

Veamos un ejemplo de la información que obtenemos haciendo uso de estas herramientas:

<?php
function escribe($cad){
echo $cad.'<br/>’;
}

for($i = 0 ; $i < 100 ; $i++){
for($j = 0; $j < 100 ; $j++){
escribe(“hola”);
}
}
?>

Distribución de la ejecución

Como se puede apreciar en esta imagen solo el 9.6% del tiempo consumido en la ejecución es utilizado por la función escribe, el resto, el 99.4% del tiempo de ejecución se ejecuta en el main, es decir, en el bucle, en un caso real una situación análoga a esta nos diría que tenemos que tratar de centrar nuestros esfuerzos de optimización en el main, no en la función escribe.

En próximas entradas intentaré hablar un poco más en profundidad de The Grinder y sobre las herramientas que se utilizarán para el análisis de la calidad del código.

Aunque me gusta terminar mis posts con la cita de alguna frase célebre que trate o evoque sobre el tema del que versa el post, esta vez voy no podrá ser, son las 2:04 y me voy a la cama😉