Escribir lapso temporal en java

28 07 2010

A veces necesario poder calcular el tiempo de ejecución de una parte del código. Ello puede ser porque estemos perfilando una aplicación o porque simplemente queramos dejar constancia de ello en algún fichero de log o en alguna consola de debugging.

En particular es algo que necesito muy a menudo así que lo tengo encapsulado en el siguiente método estática en una clase de utilidades:

public class Time {
	/**
	 * @param initial Initial time in milliseconds.
	 * @return a message with the shape: "xxx seconds (YYY millis)"
	 */
	public static String getLapseTimeMessage(long initial) {
		TimeUnit secondsUnit = TimeUnit.SECONDS;
		TimeUnit millisUnit = TimeUnit.MILLISECONDS;

		long timeLapse = System.currentTimeMillis() - initial;
		long seconds = secondsUnit.convert(timeLapse, millisUnit);
		long millis = millisUnit.convert(timeLapse, millisUnit);

		return seconds + " seconds (" + millis + " millis)";
	}
	../..
}

Una clase cliente la utilizaría como sigue:

long initial = 0;
if (logger.isDebugEnabled()) {
	initial = System.currentTimeMillis();
}

Parser parser=new ParserFactory().makeParser();
String text=this.programText;
this.program=parser.parse(text);//ParseException, IOException

logger.debug("Javascript parser needed " + Time.getLapseTimeMessage(initial));

Que dejaría una traza como la siguiente:

Javascript parser needed 1 seconds (1542 millis)

Obviamente esta es una aproximación muy básica que puede ser mejorada y más parametrizada, pero para casos sencillos cumple su cometido.

Anuncios




JLabel editable

24 02 2010

Los JLabel son elementos no editables. Lo cual tiene sentido. Están pensadas para mostrar información, posiblemente, etiquetando otro elemento.

En la aplicación que estoy construyendo queremos que unos componentes personalizados sirvan para mostrar información pero a su vez permitan editarla y queremos diferenciar claramente si el componente está en modo edición o no. En definitiva queremos tener etiquetas que bajo según qué circunstancias se puedan editar. Puesto que el JLabel no nos brinda esta opción, hemos tenido que trabajar un poco más y crear un componente personalizado.

El truco consistente en tener un componente, por ejemplo un JPanel, con un layout del tipo CardLayout con dos ‘cartas’ una de las cuales muestra una JLabel de toda la vida y otra un JTextField. El componente aporta la lógica para cambiar la carta en respuesta a ciertas interacciones del usuario. Si mantenemos el contenido de los dos componentes de forma coherente, tenemos la ilusión que la etiqueta se convierte en un campo de texto cuando entramos en modo edición y al revés al salir de este modo.

El siguiente código muestra una versión incipiente (pero funcional) de este ‘etiqueta editable’. Para entrar en modo edición hacemos doble click en la etiqueta y para salir del estado de edición pulsamos RETURN para confirmar los cambios o ESCAPE para cancelarlos.

Tomadlo como una prueba de concepto (que es lo que es) y no como una clase terminada con calidad industrial.

public class EditableLabel extends JPanel {
	/** The user is editing the component (JTextField is shown at the moment) */
	private boolean editing;

	/** The confirm edition key is Return */
	private static final int confirmKeyCode = KeyEvent.VK_ENTER;

	/** The cancel edition key is Return */
	private static final int cancelKeyCode = KeyEvent.VK_ESCAPE;

	/** The value which holds this component */
	private String value;

	// graphical components

	private CardLayout cl;
	private JPanel pnlCards;
	private static final String TEXT_FIELD = "text field";
	private JTextField textField;
	private static final String LABEL = "label";
	private JLabel label;

	public EditableLabel(String initialText) {
		this.value = initialText;

		cl = new CardLayout();
		pnlCards = new JPanel(cl);
		textField = new JTextField();
		label = new JLabel(initialText);
		pnlCards.add(textField, TEXT_FIELD);
		pnlCards.add(label, LABEL);
		cl.show(pnlCards, LABEL);
		add(pnlCards);

		// register the listeners
		label.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				// if double click, set edition mode
				if (e.getClickCount() == 2) {
					startEdition();
				}
			}
		});

		textField.addKeyListener(new KeyAdapter() {
			@Override
			public void keyReleased(KeyEvent e) {
				if (e.getKeyCode() == confirmKeyCode) {
					/* confirmation key pressed, so changing to non-edition
					 * and confirm changes */
					confirmEdition();
				} else if (e.getKeyCode() == cancelKeyCode) {
					/* cancel key pressed, so changing to non edition and
					 * cancel the changes */
					cancelEdition();
				}
			}
		});
	}

	private void startEdition() {
		cl.show(pnlCards, TEXT_FIELD);
		textField.setText(value);
		textField.requestFocus();
		textField.selectAll();
	}

	private void cancelEdition() {
		textField.setText(value);
		cl.show(pnlCards, LABEL);
	}

	private void confirmEdition() {
		value = textField.getText();
		label.setText(value);
		cl.show(pnlCards, LABEL);
	}

	/**
	 * @return the value
	 */
	public String getValue() {
		return value;
	}

	/**
	 * @param value the value to set
	 */
	public void setValue(String value) {
		this.value = value;
	}

	/**
	 * @return returns true if the component is currently showing the text
	 *  field or false otherwise.
	 */
	public boolean isEditing() {
		return editing;
	}
}




Trasteando con las JToolTip en Swing

23 02 2010

En esta entrada no voy a descubrir nada nuevo porque no soy un experto en Swing, pero son un par de detallitos que me han hecho perder algunos minutos.

La aplicación en la que estoy trabajando es un cliente Swing con componentes personalizados para construir una vista similar a diagramas de flujo. Estos componentes heredan de JPanel son más o menos complejos y sobrescriben los métodos de pintado como paintComponent. A su vez tienen componentes anidados y áreas ‘blancas’ que forman parte del componente Swing pero que en realidad para el usuario no forman parte del mismo. El siguiente diagrama lo ilustra un poco:

Mostrar el tooltip únicamente cuando el ratón está en determinadas partes del componente

Como sabemos la clase JComponent define el método setToolTipText(String tooltip) que permite establecer la tooltip, el ‘problema’ es que esta se establece para todo el componente. En mi ejemplo, yo sólo quiero que se muestre si el usuario posa el ratón en el diamante, ya que para él el resto del componente forma parte del fondo. Si no hacemos nada más, al poner el ratón en cualquier parte del componente se mostrará el tooltip.

La solución consiste en sobrescribir el método getToolTipText(MouseEvent event) para que devuelva null cuando las coordenadas del ratón (en el espacio de coordenadas del componente) están fuera del área para la que queremos mostrar el tooltip. El siguiendo snipet de código muestra la sobrescritura del método en mi componente personalizado: si estamos dentro del área definida por el rombo (calculado en un método auxiliar) entonces se muestra el tooltip establecido previamente con setToolTipText sino, devolvemos un null y por tanto no se renderizará el tooltip.

/**
* If the mouse is outside the diamond, don't show the tooltip.
*/
@Override
public String getToolTipText(MouseEvent event) {
    if (isInDiamond(event.getX(), event.getY())) {
        return getToolTipText();
    } else {
        return null;
    }
}

Tooltip multilínea

Aunque parezca sorprendente, la implementación por defecto del tooltip no soporta directamente multilíneas, así pues, si le pasamos un String que contenga retornos de carro en el método setToolTipText, éstos serán ignorados vilmente y se pintarán todas las líneas seguidas. Para solucionar ésto hay dos soluciones: una pesada y elegante y otra inmediata pero algo fea.

Empecemos por la segunda. Muchos componentes Swing tienen soporte para HTML. El tooltip también. Si asignamos un String que empiece por <html> el componente asumirá que le estamos pasando código HTML y lo renderizará como tal. El truco consiste pues, en sustituir todos los caracteres ‘\n’ por el String <br/>. Por ejemplo:

String strMultiLinea = ...
setToolTipText("<html>" + strMultiLinea.replaceAll("\n", "<br/>") + "</html>");

Rápido y funciona, pero es feo. La solución elegante consiste en implementar nuestro propio tooltip que soporte esta característica. Esto debemos hacerlo en dos pasos:

  1. Implementar nuestra clase tooltip personalizada heredando de javax.swing.JToolTip,
  2. Diciéndole al componente que debe usar nuestra propia implementación sobrescribiendo el método createToolTip para que devuelva una instancia de nuestra clase.




Capturar excepciones en el AWT Thread de una aplicación Swing

20 02 2010

En java podemos clasificar las excepciones en dos tipos, las “checked” y las que no son “not-checked” (perdonad, pero no sé cómo traducirlo satisfactoriamente).

Los métodos que lanzan excepciones del primer tipo ( cheched ) fuerzan al cliente de dicho métodos a tratarlas con un bloque try/catch o a lanzarlas para que sea un antecesor en la pilla de llamadas quien las trate. En última instancia la excepción alcanza el método principal main, que si no la gestiona la ‘lanza’ al sistema operativo y la aplicación muere con el volcado de pila del lugar donde se produjo.

Las excepciones not-checked no tienen esta restricción: ni es obligatorio capturarlas, ni el método cliente tiene que declarar explícitamente su lanzamiento mediante una cláusula throws en su signatura.

Las excepciones not-checked son todas aquellas que heredan de la clase RuntimeException . Así pues, si un método puede lanzar NullPointerException o IllegalArgumentException (entre otras), no está obligado a declararlo en su signatura ni el cliente a gestionar la excepción. De todos modos, si se produce, su propagación sigue la misma política: van escalando la pila de llamadas hasta que alguien las trate.

A la hora de programar una aplicación somos muy conscientes de las excepciones checked porque su no gestión es un error en tiempo de compilación, sin embargo para las excepciones not checked no tenemos más remedio que prever su posibilidad y hacer la gestión correspondiente donde corresponda. No obstante es posible que mientras estamos desarrollando o depurando no hayamos previsto una situación que desencadene el lanzamiento de una excepción de este tipo (típicamente se nos puede escapar alguna NullPointerException ). En estos casos (y siempre en general) es útil tener un bloque catch que capture el ‘resto’ de excepciones que el programador no previó e intentar hacer algo útil y más elegante que acabar la aplicación abruptamente (como por ejemplo guardar información en los logs que nos permita depurar o hacer un diagnóstico del problema).

¿Cómo y dónde ponemos este bloque “ captura el resto de excepciones”? En aplicaciones con un solo hilo de ejecución es muy fácil: en el main como ilustra el siguiente ejemplo.

public static void main(String[] args) {
try {
	// mi super-programa
} catch (Throwable e) {
	logger.debug("Unexpected exception!", e);
}

¿Pero qué pasa en una aplicación multi-hilo? En este caso el main creará varios hilos que tendrán su propia pila de llamadas por lo que cualquier excepción producida y no capturada en cualquiera de esos hilos no podrá ser capturada por el catch del ejemplo anterior.

En particular, las aplicaciones Swing son siempre multi-hilo. El código de programación de todos los listeners de gestión de eventos producidos por los diferentes elementos de la GUI, se ejecuta dentro del event dispatch thread (el hilo de gestión de eventos) también conocido como “AWT” Thread y que no es el mismo en el que se ejecuta el main del programa.

¿Qué pasa si se produce una excepción not-checked en este hilo? Pues que si no lo hemos previsto en el código y no hay ningún catch que la capture, se pierde irremediablemente. Afortunadamente existe una solución que expondré a continuación y que también está explicada en este post.

La API estándar de Java ofrece la clase ThreadGroup . Cuando creamos un thread podemos hacer que pertenezca a una instancia de esta clase. Dicha clase tiene un método con la siguiente signatura:

void uncaughtException(Thread t, Throwable e)

que es llamado por la máquina virtual de forma automática cuando cualquiera de los threads que forman parte de ese grupo lanza una excepción que nadie captura. Cuando un hilo se crea dentro de un grupo, todos los hilos que pueda crear ese hilo también pertenecen al mismo grupo.

Ahora ya tenemos todos los elementos que solucionan nuestro problema:

  1. Creamos una subclase de ThreadGroup que sobrescriba el método uncaughtException para que haga algo útil en nuestro programa.
  2. En el main construimos una instancia de la clase que acabamos de construir.
  3. Creamos un thread que se encarga de montar toda la GUI que forma parte del grupo recién instanciado.

Con esto conseguimos que todos los threads interesantes de nuestro programa pertenezcan al grupo que es capaz de gestionar las excepciones no capturadas.

Nuestro ThreadGroup podría tener el siguiente aspecto:

class HandleExceptionGroup extends ThreadGroup {
	private static Logger logger = Logger.getLogger(ExceptionGroup.class);

	public HandleExceptionGroup() {
		super("ExceptionGroup");
	}

	@Override
	public void uncaughtException(Thread t, Throwable e) {
		String msg =
			"GUI produced an unexpected exception (thread: " + t.getName() + ") ";

		logger.warn(msg, e);
	}
}

y el main de la clase principal:

public static void main(String[] args) {
        HandleExceptionGroup threadGroup = new HandleExceptionGroup();
	new Thread(threadGroup, "Main Thread"){
		@Override
		public void run() {
			// aquí construyo la GUI
		}
	}.start();
}




Monitorizar JBoss con Cacti (de JMX a SNMP)

30 01 2010

Descárgate este artículo como PDF aquí.

Introducción

Cuando tenemos una aplicación desplegada en producción o cuando estamos realizando pruebas de carga o de estrés, una de las cosas que necesitamos hacer es monitorizar diferentes parámetros de la misma y del servidor de aplicaciones que la contiene. Estos parámetros pueden incluir, por ejemplo, la evolución de la memoria, el número de sesiones, etcétera.

En este artículo explicaré cómo configurar JBoss para que comunique diferentes métricas que sean leídas y analizadas por la herramienta Cacti.

SNMP y JMX

SNMP y JMX son las siglas de dos protocolos de monitorización de recursos. El primero es un protocolo genérico, y bien conocido por nuestros queridos compañeros de sistemas, y el segundo es el protocolo propio de la tecnología Java.

Puesto que SNMP es un protocolo más antiguo, que aplica a todo tipo de componentes hardware (como un router) o servicios software (por ejemplo, un servidor web) y que es bien conocido por los profesionales que tienen que administrar estas infraestructuras, tiene una gran cantidad de herramientas y gestores que permiten trabajar con él. Estas herramientas en general sólo capturan información para ser analizada, pero el protocolo también permite la modificación de ciertos parámetros. JMX es algo parecido pero aplicado a la máquina virtual y a los servicios Java.

Puesto que tanto las herramientas como los profesionales que tienen que administrar recursos están más acostumbrados a trabajar con SNMP que con JMX (que pueden llegar a ignorarlo por completo), una posible solución pasa por poder exportar la información proporcionada mediante JMX al protocolo SNMP. El presente artículo intenta explicar básicamente esto: cómo una fuente JMX (nuestro querido JBoss) puede integrarse con una herramienta de gestión SNMP (Cacti).

No entraré a explicar los detalles de los dos protocolos porque, entro otras cosas, ni soy un experto en los mismos ni es relevante para el objetivo de este tutorial. Si queréis más información sólo hay que seguir los enlaces que voy proporcionando. Aquello que vayamos necesitando lo iré explicando a lo largo del tutorial.

A modo de resumen ( y extraído de la wikipedia):

“Una red administrada a través de SNMP consiste de tres componentes claves:

  • dispositivos administrados;

  • agentes;

  • sistemas administradores de red (NMS’s).

Un dispositivo administrado es un nodo de red que contiene un agente SNMP y reside en una red administrada. Estos recogen y almacenan información de administración, la cual es puesta a disposición de los NMS’s usando SNMP. Los dispositivos administrados, a veces llamados elementos de red, pueden ser routers, servidores de acceso, switches, bridges, hubs, computadores o impresoras.

Un agente es un módulo de software de administración de red que reside en un dispositivo administrado. Un agente posee un conocimiento local de información de administración (memoria libre, número de paquetes IP recibidos, rutas, etcétera), la cual es traducida a un formato compatible con SNMP y organizada en jerarquías.

Un NMS ejecuta aplicaciones que supervisan y controlan a los dispositivos administrados. Los NMS’s proporcionan el volumen de recursos de procesamiento y memoria requeridos para la administración de la red. Uno o más NMS’s deben existir en cualquier red administrada.”

Entorno y herramientas

Partiendo de la descripción anterior de infraestructura SNMP, nuestro dispositivo administrado será JBoss, nuestro agente una aplicación desplegada en JBoss que se llama “SNMP adaptor” y nuestro NMS será Cacti.

Los lectores de este blog sin duda conocen qué es JBoss, uno de los servidores de aplicaciones JEE open source más conocidos, sin embargo, es más posible que no estén tan familiarizados con el resto de herramientas ni en tareas de monitorización. Esto último suele pasar porque es muy habitual que este tipo de tareas las realicen perfiles técnicos más orientados a sistemas (lo cual me parece correcto); un desarrollador no debería preocuparse de mirar si los logs del Apache tienen cosas raras o de si una máquina debería ampliar su sistema de ficheros porque en los últimos meses se ha aumentado mucho su necesidad de espacio.

El problema suele ser que los perfiles de sistemas dan un trato ‘especial’ a los servidores Java. Esto se debe a que la administración de estos servicios es un poco diferente al resto de cosas a las que están acostumbrados (archivos de configuración XML vs. texto plano, máquina virtual vs. ejecución nativa, JMX vs. SNMP, etc.). Consecuencia de esta realidad, que yo me he encontrado en más de un sitio, los desarrolladores hemos tenido que aprender algunas cosas de sistemas.

En este artículo trabajaré con una versión virgen de Ubuntu 9.10 y explicaré paso a paso lo siguiente:

  • cómo instalar JBoss,
  • cómo instalar Cacti,
  • configurar JBoss para exportar métricas mediante SNMP y
  • configurar Cacti para monitorizar estas métricas.

Instalar JBoss

Aquí no dedicaremos mucho tiempo (entiendo que todo el mundo tiene esta fase superada). Primero instalamos el JDK de Sun con el gestor de paquetes:

apt-get install sun-java6-jdk

A continuación descargamos el JBoss AS de http://www.jboss.org/jbossas/downloads/ (en el momento de escribir este tutorial la última versión estable, y que yo voy a usar, es la 5.1.0-GA).

La instalación es sencilla y básicamente consiste en descomprimir el bundle, establecer los permisos adecuados y, opcionalmente, configurar el script de arranque en init.d . En mi caso instalo el servidor en /opt/jboss pero no es demasiado relevante. Me referiré al directorio raíz de JBoss como JBOSS_HOME.

Recordemos que para poner en marcha el servidor se hace mediante el script JBOSS_HOME/bin/run.sh y que si no le pasamos ningún parámetro por defecto arranca la configuración default .

Pongamoslo en marcha para ver si todo funciona correctamente. En el log debería aparecer una línea del tipo

15:50:56,747 INFO [ServerImpl] JBoss (Microcontainer) [5.1.0.GA (build: SVNTag=JBoss_5_1_0_GA date=200905221053)] Started in 1m:41s:932ms

y deberíamos poder acceder a la consola de administración del mismo en el puerto 8080 de la máquina:

Instalar Cacti

En un entorno productivo, si se está usando Cacti para monitorizar los sistemas (como es el caso en mi empresa), obviamente serán los chicos de sistemas los que lo tendrán montado y configurado. En nuestra prueba de concepto lo instalaremos en la misma máquina desde cero y con la inestimable ayuda de nuestro querido gestor de paquetes de Ubuntu.

apt-get install cacti

Cacti es una aplicación PHP que almacena la información que necesita en una base de datos MySQL por lo que tiene como dependencias importantes: el servidor MySQL, el Apache y, obviamente, el PHP. Si tenemos una instalación limpia, el gestor de paquetes nos pedirá datos de configuración de estos componentes (como por ejemplo el passwor de root del MySQL).

A continuación el script de instalación nos irá haciendo una serie de preguntas y como no queremos pelearnos con la configuración avanzada de Cacti, cogeremos el camino fácil:

  • ¿Qué servidor web quieres?

    • Apache 2 .

  • Configure database for cacti with dbconfig-common? (que traducido sería, ¿quieres que te configure yo automáticamente la base de datos o prefieres hacerlo tú a mano)

    • .

  • Dame la contraseña de root de MySQL parara que te pueda crear la base de datos.

  • Dame la contraseña que quieres que utilice para el usuario de Cacti en la base de datos.

    • DATABASE_CACTI_PASSWORD (obviamente, aquí ponemos el que estimemos oportuno).

Aún no ha acabado la instalación pero el resto lo haremos directamente desde la aplicación web. Si todo ha ido bien, tendremos un Apache 2 corriendo con un flamante Cacti instalado en la ruta http://localhost/cacti que nos llevará al asistente de configuración:


El asistente es muy sencillo y básicamente no requiere explicación: la siguiente pantalla nos pregunta el tipo de instalación (nueva) y la última nos muestra las rutas a la diferentes herramientas que necesita preguntándonos confirmación (configuración que nos parecerá estupenda, por supuesto).


Si todo ha ido bien, al finalizar el asistente, seremos redirigidos a la pantalla de login de Cacti, sin embargo, en ningún punto anterior hemos sido preguntados por usuario o contraseña administrativa (las contraseñas que hemos introducido anteriormente se referían al usuario en la base de datos pero no a la aplicación propiamente dicha). Para entrar utilizaremos los valores por defecto que tanto para el usuario como para la contraseña son “ admin” y seremos redirigidos a otra pantalla que nos obliga a decidir una contraseña algo más segura. Nos referiremos a esta contraseña como ADMIN_CACTI_PASSWORD.


En este punto ya hemos acabado de configurar Cacti y por fin entramos en la página principal de la aplicación.

Cacti es una herramienta muy poderosa, pero no es el propósito de este artículo explicar todo lo que puede hacer, así que sólo explicaré unas nociones. La interfaz se divide en dos secciones principales representadas por las dos pestañas del margen superior izquierdo: la consola y los gráficos. La consola permite lleva a cabo todas las tareas de configuración (añadir dispositivos SNMP, crear nuevos informes, etc.). La sección gráficos muestra los informes. Efectivamente, Cacti presenta la información como gráficos a lo largo del tiempo (por ejemplo, uso de la memoria a lo largo de la última media hora).

Una única instancia de Cacti puede monitorizar toda la infraestructura de una red. Sólo hay que añadir los agentes en cada uno de los dispositivos y luego configurar Cacti para que los escuche. Los diferentes dispositivos se ven en el árbol de la izquierda. En la configuración básica que tenemos montada sólo estamos monitorizando algunos parámetros básicos de la máquina local.

El marco superior nos permite seleccionar el marco temporal de los gráficos que estamos visualizando. Por defecto Cacti nos muestra algunos parámetros locales interesantes, el uso de memoria, de CPU, etc. Nuestro objetivo es añadir nuevos gráficos como: tamaño del heap del JBoss o número de sesiones de la aplicación X.

Los gráficos, además, son elementos interactivos que permiten redefinir el periodo temporal que están mostrando a golpe de ratón (zoom-in y zoom-out) o exportar los datos en formato CSV para trabajar con ellos en cualquier herramienta que nos interese (como por ejemplo OpenOffice Calc o Microsoft Excel).

Configurar JBoss para exportar datos por SMMP

En este punto ya tenemos una configuración básica de JBoss y de Cacti. El siguiente paso es hacer que JBoss sea capaz de producir métricas en el protocolo SNMP. Por suerte, va a ser bastante sencillo.

La manera de hacerlo será convertir los datos que de forma nativa ya está generando en JMX a SNMP. Pero, ¿qué información está exportando por JMX? Muy sencillo, lo podemos ver directamente en la consola JMX de JBoss que está accesible en http://localhost:8080/jmx-console/

Para más información sobre la API JMX de JBoss, podéis ir al propio manual o echarle un ojo a este interesante tutorial que propone otras alternativas de monitorización con herramientas Java.

Ahora que sabemos qué podemos monitorizar, ¿cómo lo exportamos a SNMP? Fácil, desplegaremos una aplicación que viene con el propio JBoss: el SNMP Adaptor.

Esta aplicación viene desplegada únicamente en la configuración de servidor all , así que si estamos utilizando otra (como la default o una personalizada), tendremos que desplegarla donde corresponda. En mi caso, que estoy usando la configuración default, haría lo siguiente:

cp -fR JBOSS_HOME/server/all/deploy/snmp-adaptor.sar JBOSS_HOME/server/default/deploy

Desde este mismo momento ya estamos produciendo datos SNMP para algunos parámetros que vienen configurados por defecto, como por ejemplo, la cantidad de memoria libre. Para comprobar que está funcionando usaremos un cliente SNMP de línea de comandos:

snmpwalk -v 1 -c public localhost:1161 .1.2.3.4.1

a lo que JBoss debería responder con algo parecido a:

iso.2.3.4.1.1 = INTEGER: 60
iso.2.3.4.1.2 = Gauge32: 97540696
iso.2.3.4.1.3 = Gauge32: 530907136
iso.2.3.4.1.4 = INTEGER: 0
End of MIB

El snmpwalk es un cliente simple, que recibe los parámetros de dónde buscar el agente (en localhost en el puerto 1161) y qué información buscar, la identificada por .1.2.3.4.1. Los identificadores SNMP ( oid ‘s) son un acuerdo entre el agente y los clientes SNMP y tienen estructurar jerárquica. La respuesta que nos ha dado el servidor es el conjunto de todos los parámetros que está produciendo que ‘cuelgan’ del identificador.1.2.3.4.1.

Cada uno de estos parámetros y su mapeo con el identificador SNMP lo podemos encontrar en archivo de configuración del SNMP Adaptor que se encuentra en el fichero XML siguiente: JBOSS_HOME/server/all/deploy/snmp-adaptor.sar/attributes.xml. Básicamente sirve para configurar un parámetro JMX con un identificador SNMP. Por ejemplo, los cuatro parámetros que hemos visto en el ejemplo se corresponden con esta entrada:

<attribute-mappings>
  <!-- basic system information -->
  <mbean name="jboss.system:type=ServerInfo" oid-prefix=".1.2.3.4.1">
    <attribute name="ActiveThreadCount" oid=".1"/>
    <attribute name="FreeMemory" oid=".2"/>
    <attribute name="MaxMemory" oid=".3"/>
  </mbean>
../..
</attribute-mappings>

Como vemos por un lado estamos identificando el bean JMX y los atributos del mismo que producen la información y por otro el identificador SNMP que queremos vincularle. En la siguiente captura de pantalla del cliente, podemos ver parte de este bean:


Obviamente podemos modificar libremente el fichero para quitar o añadir los bean y atributos que queramos.

Exportar información de sesión para una aplicación web por SNMP

Un ejemplo de información interesante a monitorizar es la asociada a las sesiones de las aplicaciones desplegadas en el servidor. Estos parámetros no están configurados por defecto porque el bean JMX que los exporta depende del nombre del contexto de la aplicación desplegada.

Con la configuración por defecto de JBoss tenemos desplegadas una serie de aplicaciones web, una de ellas es la propia consola JMX. La usaremos como ejemplo. Así pues, para monitorizar una selección de parámetros relacionados con la sesión para la aplicación jmx-console añadiríamos el siguiente fragmento XML al archivo de configuración del SNMP Adaptor. En negrita he marcado el nombre del bean, que, como vemos, incluye el nombre del la aplicación (del contexto) cuyos parámetros queremos exportar. Los oid elegidos son arbitrarios.

<mbean name=" jboss.web:host=localhost,path=/jmx-console,type=Manager" oid-prefix=".1.2.3.4.5">
  <!-- Number of active sessions at this moment -->
  <attribute name="activeSessions" oid=".1"/>

  <!-- Average time an expired session had been alive -->
  <attribute name="sessionAverageAliveTime" oid=".2"/>

  <!-- longest time an expired session had been alive -->
  <attribute name="sessionMaxAliveTime" oid=".3"/>

  <!-- Total number of sessions created by this manager -->
  <attribute name="sessionCounter" oid=".4"/>

  <!-- Number of sessions that expired ( doesn't include explicit invalidations ) -->
  <attribute name="expiredSessions" oid=".5"/>

  <!-- Number of sessions we rejected due to maxActive beeing reached -->
  <attribute name="rejectedSessions" oid=".6"/>

  <!-- Maximum number of active sessions so far -->
  <attribute name="maxActive" oid=".7"/>

  <!-- Number of duplicated session ids generated -->
  <attribute name="duplicates" oid=".8"/>

  <!-- Time spent doing housekeeping and expiration -->
  <attribute name="processingTime" oid=".9"/>
</mbean>

Al igual que antes, hacemos una petición desde la línea de comandos para ver si todo ha ido bien:

snmpwalk -v 1 -c public localhost:1161 .1.2.3.4.5

para lo que deberíamos obtener una respuesta similar a lo siguiente:

iso.2.3.4.5.1 = INTEGER: 1
iso.2.3.4.5.2 = INTEGER: 92
iso.2.3.4.5.3 = INTEGER: 92
iso.2.3.4.5.4 = INTEGER: 2
iso.2.3.4.5.5 = INTEGER: 1
iso.2.3.4.5.6 = INTEGER: 0
iso.2.3.4.5.7 = INTEGER: 1
iso.2.3.4.5.8 = INTEGER: 0
iso.2.3.4.5.9 = Gauge32: 0
End of MIB

Configurar el Cacti para monitorizar el JBoss

Cacti es capaz de monitorizar cualquier fuente SNMP. Básicamente hay que añadir los diferentes hosts y dispositivos con la consola administrativa de la interfaz web y luego definir las las gráficas que se quieren graficar sobre los diferentes servicios SNMP que tengan cada uno de esos dispositivos.

La tarea anterior puede ser más o menos tediosa (que no complicada), afortunadamente el propio Cacti viene con una serie de gráficas que permiten configurar a golpe de ratón servicios típicos. Por ejemplo, la siguiente captura de pantalla muestra alguna de las posibilidades: Console → New Graphs → Create.

Además de los informes que vienen incluidos con esta distribución en Internet pueden encontrarse para cualquier tipo de dispositivo que se nos pueda ocurrir. La distribución de estos informes se hace mediante lo que Cacti denomina templates (plantillas). Estas plantillas son unos ficheros XML que llevan la configuración SNMP de los diferentes dispositivos y además la información para que Cacti pueda crear los gráficos prediseñados. Así pues, si tenemos un router de la marca Acme modelo Roadrunner, sólo tenemos que buscar un poco en la página de Cacti o googlear un poco porque seguro que alguien ha creado la plantilla.

Como podéis ver en la captura de pantalla anterior, la propia interfaz administrativa tiene dos enlaces que nos permiten importar y exportar plantillas respectivamente.

JBoss no iba a ser menos, así que existen al menos un par de plantillas proporcionadas por la comunidad. Una de ellas puede descargarse desde directamente desde desde este hilo en los foros de Cacti. El autor ha proporcionado una plantilla que grafica directamente los parámetros que vienen en la configuración por defecto del “SNMP Adaptor” de JBoss.

Para instalarla sólo tenemos que descargarla e importarla mediante la interfaz gráfica. Si todo va bien, Cacti nos muestra un informe con todas las entidades procesadas que ha podido importar de la plantilla tal y como se muestra en la siguiente captura.


No hace falta entrar en los detalles, pero básicamente se ha configurado un nuevo agente SNMP, diferentes parámetros producidos por el mismo y tres definiciones de gráficas construidas a partir de estos parámetros. Ahora sólo faltaría construir nuevas gráficas usando estas definiciones para poder monitorizar el JBoss. Para ello deberemos:

  1. Construir un nuevo dispositivo administrado que se corresponda con el JBoss.
  2. Configurar las gráficas asociadas.

Para construir un nuevo dispositivo administrado, hacemos click en “Devices” y en el frame superior del área central (que actúa como filtro de los dispositivos listados) hacemos click en “Add” que nos lleva una pantalla de configuración de dispositivo.

En la pantalla de configuración del dispositivo seleccionamos los siguientes parámetros:

  • Configuración general

    • Description: un nombre significativo para identificar el servidor JBoss.
    • Hostname: el nombre de la máquina o su IP.
    • Host template : aquí es donde tenemos que seleccionar el “JBoss SNMP Adaptor” que acabamos de importar.
  • La siguiente sección “ Availability / Reachibility options” se refiere al mecanismo que debe usar Cacti para determinar que el host sigue vivo. Cuando el mecanismo seleccionado le indica que el host no está ahí, el poller de Cacti (el subsistema que interroga al agente SNMP en el dispositivo) deja de recoger datos para ese dispositivo. Los parámetros por defecto basados en ping ya me parecen correctos para esta prueba de concepto, pero sentíos libres de adaptarlos a vuestro entorno.
  • Configuración SNMP que debe coincidir con la configurada en el SNMP Adaptor
    • SNMP version : 1
    • SNMP community : public
    • SNMP Port : 1161 (¡ojo el puerto por defecto es 161!)
    • Y el resto de parámetros tal y como estén o prefiráis.


Si no ha habido ningún problema y Cacti ha podido comprobar que el servidor está vivo, éste nos redirige a la misma página de edición del servidor pero con un mensaje de feedback informando de ello y con nuevas opciones entre las que se nos propone la creación de gráficos asociados al dispositivo.



Hacemos caso de la sugerencia y seguimos el link “Create Graphs for this Host”, que nos lleva a una pantalla que nos permite seleccionar los gráficos a seleccionar de entre los configurados en la plantilla para este tipo de dispositivo. Los seleccionamos todos y pulsamos “create”.



En este momento los gráficos han empezado a procesarse, sin embargo, si vamos a la sección “graphs” no están visibles. Esto se debe a que para verlos debemos incluirlo en un árbol de gráficos (que es lo que muestra la pantalla “Graphs”) . Para ello hacemos click en “Graph Trees” en la pantalla “Console”, sección “Management” y usamos el asistente para incluir los diferentes gráficos.

No nos vamos a complicar, así que aprovechamos el “default tree” para añadir los nuevos gráficos. Paso a paso:

  1. Click en “Default Tree”

  2. Añadir un nodo que contendrá todos los gráficos de JBoss

    1. Click en “Add” y configurar los siguientes parámetros:

      1. Parent Item: [root]
      2. Tree Item Type: Header
      3. Title: JBoss in localhost
  3. Añadir un nodo de tipo gráfico para cada uno de los gráficos:
    1. Click en “Add” al lado del nombre del nodo y configurar los siguientes parámetros:
      1. Parent Item: JBoss in localhost
      2. Graph: cada uno de los diferentes gráficos de JBoss


Al terminar de añadir los gráficos, por fin podemos ir a la sección “Graphs” y empezar a disfrutar de nuestra nueva monitorización. Ahora sólo habrá que tener un poco de paciencia para que el poller de Cacti recoja suficientes datos para que los gráficos aporten información útil.

Monitorizar información de sesión para una aplicación web

La configuración de los gráficos precedentes ha sido más o menos sencilla porque la plantilla que hemos descargado nos lo ha dado todo hecho, ¿pero qué pasa si queremos graficar otros parámetros SNMP exportados por JBoss (o por cualquier otro dispositivo)? Pues que tenemos que usar la interfaz para crear bastante fácilmente esos nuevos gráficos.

Como ejemplo vamos a construir un gráfico que se genere a partir del número de sesiones activas en cada momento. Si recordáis, en una sección precedente explicamos cómo hacer que JBoss exportara estos datos por SNMP para la aplicación jmx-console. Utilizaremos el mismo ejemplo, así que el oid SNMP del número de sesiones activas se corresponde con .1.2.3.4.5.1

El proceso es sencillo:

  1. Crear la definición del nuevo gráfico.
  2. Crear un gráfico del nuevo tipo en el dispositivo JBoss.
  3. Añadir el gráfico al árbol de gráficos.

Para crear el gráfico en la sección “Console”, clickaremos en el enlace “New graphs”. Nos aseguramos de tener seleccionado el dispositivo correspondiente al JBoss en el desplegable “Host” y la opción “SNMP – Generic OID Template” en el desplegable “Create”. A continuación pulsamos el botón “Create”.

A continuación introducimos los parámetros de configuración del gráfico basado en SNMP tal y como muestra la siguiente captura. Todo son parámetros descriptivos del gráfico generado excepto el oid del parámetro que queremos graficar (en nuestro caso el que se corresponde con el número de sesiones exportado por el JBoss).



Cuando le damos al botón “create” han pasado tres cosas:

  1. Se ha creado la definición del gráfico.
  2. Se ha vinculado un gráfico de este tipo en el dispositivo JBoss.
  3. Se ha creado un datasource de tipo “SNMP – Generic OID” que utiliza el poller para obtener los datos que generan el gráfico.

Ahora ya sólo falta añadir el gráfico a nuestro árbol de gráficos por defecto tal y como hicimos con el resto de gráficos que venían con la plantilla de JBoss.

Una vez lo hemos hecho, vemos nuestro gráfico justo al resto en la vista “graphs” tal y como muestra la siguiente captura.



¿Qué hemos visto?

A modo de resumen:

  1. JMX es el protocolo de monitorización nativo de Java.
  2. SNMP es un protocolo de monitorización de recursos en red.
  3. Hay más herramientas SNMP que JMX y los profesionales de sistemas se sienten más cómodos con SNMP.
  4. JBoss exporta de manera nativa su estado mediante JMX.
  5. JBoss proporciona una aplicación que hace la traducción de JMX a SNMP (un agente SNMP) que se denomina “SNMP Adaptor”
  6. Cacti es una poderosa herramienta de monitorización SNMP.
  7. La comunidad ha creado plantillas para monitorizar en Cacti los aspectos básicos de JBoss (la configuración por defecto de la aplicación “SNMP Adaptor”).
  8. Podemos configurar tanto JBoss como Cacti para monitorizar otros aspectos aspectos que nos interesen de nuestro servidor de aplicaciones o de nuestras aplicaciones.




Métricas de calidad con NetBeans y Hudson

5 03 2009

Este artículo como PDF.

Introducción

En un post anterior expliqué cómo montar un entorno de integración continua básico, que luego completamos para que también se convirtiera en un verdadero entorno TDD con tests y métricas de cobertura. El siguiente paso para tener un entorno totalmente ágil consiste en añadir métricas de calidad de código para favorecer la mejora continua del trabajo hecho por todo el equipo y para que los gestores puedan tener una perspectiva algo más “objetiva” del nivel de calidad. Continuaré en este tutorial sobre el mismo ejemplo, así que en numerosos puntos haré referencias a los artículos previos para no tener que repetirme más de lo necesario.

Nótese que he puesto la palabra objetiva entre comillas. En este artículo vamos a ver una serie de herramientas que detectarán malas prácticas, bugs potenciales o puntos mejorables y generarán informes al respecto. El desarrollo de software es algo muy complejo y la medida de su calidad real no es automatizable (si así fuera, también lo sería su desarrollo y aún no hemos llegado a ese punto), ni hay un convenio universal de en qué consiste, con lo cual hay que saber leer e interpretar el resultado que producen estas herramientas. Si están avisando de que nuestro código tiene problemas, muy probablemente tengan razón y deberemos poner medidas correctoras. Lo contrario, sin embargo, no implica que nuestro código sea de alta calidad, sino que no se han encontrado aquellas cosas que se sabe que producen problemas. No se podrá automatizar, por ejemplo, cosas como la detección de uso inadecuado de patrones de diseño o la escalabilidad de la plataforma, que podríamos estar de acuerdo que son medidas de calidad. Simplificando mucho: se puede detectar que el código de un proyecto tiene poca calidad de manera automática, pero no se puede decir que para aquellos para los que no se ha detectado, el código sea de alta calidad.

Hechas estas matizaciones, en este artículo vamos a explicar cómo integrar las siguientes herramientas en NetBeans y Hudson usando Ant:

  • Checkstyle : genera informes sobre el grado de seguimiento del código a los estándares de codificación establecidos.

  • Simian : detector de copia de bloques de código.

  • PMD : analizador de código estático en busca de posibles bugs y malas prácticas.

  • FindBugs : similar al anterior.

El procedimiento de instalación de las herramientas va a ser análogo para cada una de ellas:

  1. Instalación del plugin correspondiente en el NetBeans (si lo hubiere) y configuración del mismo.

  2. Instalación de las bibliotecas de la herramienta en la máquina de desarrollo.

  3. Modificación del script del Ant para poder lanzar el análisis sin la ayuda del IDE.

  4. Instalación del plugin correspondiente en el Hudson.

  5. Modificación del job para incluir la generación y publicación de informes correspondientes.

Checkstyle

Cuando hablo de estilo de codificación (o convenciones de código) me refiero a cosas como el nivel de indentación, utilizar espacios o tabuladores, añadir comentarios, cómo organizar las llaves de los bloques de código, el tamaño máximo de la línea, etcétera.

Parece un hecho ampliamente aceptado que la uniformidad de estilo a la hora de codificar facilita el grado de cohesión del equipo de desarrollo, la mantenibilidad de la base de código y en definitiva la productividad. Si todo el equipo trabaja con un mismo estilo, puede entender la estructura del programa más fácilmente de un vistazo.

En el mundo Java, Sun proporcionó unas guías de estilo que han sido ampliamente adoptadas por la comunidad. En este documento las podéis encontrar junto con los motivos que hay detrás de cada convención.

Checkstyle es una herramienta que genera informes del nivel de seguimiento de estas convenciones. Seguir el estilo al pie de la letra puede resultar algo duro en ocasiones, así que también es posible rebajar el nivel de exigencia decidiendo para qué convenciones se quiere generar una alarma y cuáles se puede ignorar o incluso para definir nuestras propias convenciones.

Checkstyle lo tenemos disponible en las tres vertientes: plugin para el NetBeans, como task del Ant y como plugin del Hudson.

Plugin para NetBeans del Checkstyle

El plugin no forma parte de los repositorios estándar, así que el primer paso para instalarlo será añadir el repositorio correspondiente. Para ello iremos a: Tools -> Plugins -> Settings -> Add y añadiremos la siguiente url : http://www.sickboy.cz/checkstyle/autoupdate/autoupdate.xml tal y como se muestra en las siguientes imágenes.

A continuación hacemos click en la pestaña de Available Plugins y seleccionamos Checkstyle Beans Library y Checkstyle Beans Plugin y los instalamos (seguimos los pasos indicados por el asistente y cuando nos pregunte si queremos instalar plugins no firmados le decimos que sí) . Ahora sólo resta reiniciar el NetBeans para que cargue el plugin.

Ahora cada vez que alguna línea de código no conforme con respecto a las convenciones de Sun, aparecerá una etiqueta a modo de aviso. Poniendo el ratón encima de la etiqueta, mostrará cuál es la convención que no se está siguiendo.

Como decía, se pueden utilizar plantillas con convenciones personalizadas. Para ello deberemos editar la configuración por defecto que se encuentra en: Tools -> Options -> Miscellaneous -> Checkstyle.

Incluir Checkstyle en el script de Ant del NetBeans

Como ya he explicado en otros artículos, NetBeans realiza todas sus tareas mediante scripts Ant que autogenera y el build.xml que hay en la raíz del proyecto es extensible para que los desarrolladores podamos introducir nuestros propios targets .

Lo primero que tenemos que hacer es descargarnos la distribución del Checkstyle (en el momento de escribir este artículo la versión 5 sigue siendo beta , así que yo me decanto por la versión 4.4) que, entre otras cosas, aporta los tasks necesarios para invocarlo desde Ant. A continuación habremos de descomprimir el paquete en algún lugar de la máquina de desarrollo. Yo, siguiendo mi propio estándar, lo situaré en el directorio /srv y luego lo enlazaré con un link simbólico para no tener que hardcodear la ruta hacia una versión concreta en ningún fichero de configuración o script que haga uso del mismo. También crearé un enlace simbólico al jar que contiene la implementación del task por el mismo motivo.

root@hargon:/srv# tar -zxf checkstyle-4.4.tar.gz
root@hargon:/srv# ln -s checkstyle-4.4 checkstyle
root@hargon:/srv# ls -alh checkstyle
lrwxrwxrwx 1 root root 14 2009-02-25 16:24 checkstyle -> checkstyle-4.4
root@hargon:/srv# rm checkstyle-4.4.tar.gz
root@hargon:/srv# cd checkstyle
root@hargon:/srv/checkstyle# ln -s checkstyle-all-4.4.jar checkstyle-all .jar

El siguiente paso consiste en modificar el build.xml . Lo primero que vamos a necesitar es importar el task tal y como sigue:

<taskdef
    resource="checkstyletask.properties"
    classpath="${checkstyle.dir}/checkstyle-all.jar"/>

Puesto que el valor de checktyle.dir puede ser diferente en la máquina de diferentes desarrolladores o en la máquina que contiene en el servidor de integración, esta propiedad la definimos en el fichero de propiedades privadas del proyecto (que recordemos no forma parte del repositorio de código): nbproject/private/private.properties.

checkstyle.dir=/srv/checkstyle

Como yo estoy partiendo del mismo ejemplo del artículo previo donde explicaba cómo integrar Cobertura, yo ya tengo definido un import en el build.xml del fichero de propiedades privadas, si no lo tuvierais acordaros de añadirlo.

<property file="nbproject/private/private.properties"/>

A continuación definimos el target que construirá los informes en el build.xml :

<target name="checkstyle-report">

    <property file="nbproject/project.properties"/>

    <mkdir dir="${checkstyle.report.dir}"/>

    <checkstyle failOnViolation="false"
        config="${checkstyle.dir}/sun_checks.xml">
        <fileset dir="${src.dir}" includes="**/*.java"/>
        <formatter type="xml"
            toFile="${checkstyle.report.dir}/checks.xml"/>
        <formatter type="plain"
            toFile="${checkstyle.report.dir}/checks.txt"/>
    </checkstyle>
</target>

El target es bastante fácil de entender. En primer lugar construimos el directorio donde se generarán los diferentes informes. A continuación lanzamos el task del Checkstyle indicándole que siga procesando el build.xml aunque se encuentren fallos de formato ( failOnViolation=”false” ) y que utilice las convenciones definidas en el fichero sun_checks.xml que forma parte de la distribución estándar de Checkstyle. Este fichero es donde se describen, en un formato que escapa al ámbito de este artículo, cuáles son las convenciones que caso de no seguirse generan un aviso. Obviamente podemos editar o sustituir este fichero por otro adaptado a nuestras necesidades, pero si dejamos éste, forzamos las convenciones de Sun que, como ya había indicado previamente, se describen en este documento .

Finalmente generamos los informes en dos formatos: en texto plano y en XML. El primero es más sencillo de leer por humanos pero el segundo es el adecuado para integrarlo con Hudson.

En este target presuponemos inicializadas las propiedades checkstyle.dir , src.dir y checstyle.report.dir . La primera ya la hemos configurado en el punto anterior, la segunda forma parte de las propiedades creadas por NetBeans y la última la tenemos que definir. Las añadiremos como propiedades del proyecto en el nbproject/project.properties :

reports.dir=${build.dir}/reports
checkstyle.report.dir=${reports.dir}/checkstyle-report

Integrando Checkstyle con Hudson

Para generar los informes en Hudson nos aprovecharemos del target que acabamos de definir en el build.xml del NetBeans, por tanto el proceso va a ser sencillo:

  1. añadir el plugin del Checkstyle al Hudson,

  2. instalar las bibliotecas del Checkstyle en la máquina donde reside el Hudson,

  3. añadir las propiedades privadas a mano (recordad que este fichero no forma parte del Subversion),

  4. añadir el nuevo target en la construcción del proyecto,

  5. configurar el job para que utilice el target .

El proceso para instalar el plugin, ya lo hemos visto en el post donde explicaba cómo añadir el plugin de Cobertura . Básicamente, seleccionarlo de la lista de plugins disponibles, instalarlo con el asistente y reiniciar el Hudson.

Utilizaré el mismo procedimiento y ruta para instalar la distribución del Checkstyle en el servidor donde se ejecuta el Hudson, así que repetiré exactamente los mismos pasos que hice en la máquina de desarrollo.

A continuación tenemos que modificar las propiedades privadas para añadir la ruta al Checkstyle. Nos situaremos en el directorio del job y editaremos (o crearemos) el fichero nbproject/private/private.properties para añadir la línea:

checkstyle.dir=/srv/checkstyle

Finalmente añadimos checkstyle-report a la lista de tagets a construir y configuramos el job para que lea los informes generados en xml y los publique. Adicionalmente podemos configurar opciones avanzadas como los límites a partir de los cuales el build debe considerarse inestable o roto.

Ahora ya sólo resta ver los informes que genera el Hudson:

Simian

Alguna vez oí, aunque no recuerdo la fuente, que en informática la duplicación es el diablo y yo no puedo estar más de acuerdo. En ciertas ocasiones requerimientos no funcionales, como por ejemplo eficiencia, puede requerir duplicar información (denormalización de tablas en bases de datos, materialización de cálculos de agregación de información, etc), pero ello debe ser consecuencia de una decisión de diseño meditada y no de la dejadez. La replicación de código, en general, es menos justificable e introduce un gran coste en mantenibilidad y calidad: modificaciones en cualquiera de las copias debería llevar aparejadas actualizaciones en el resto, lo cual se hace muy tedioso y complicado de implementar.

Por todo lo anterior, si tenemos repetido un bloque de código de un tamaño razonable, probablemente ello está descubriendo un mal diseño y ese código debería encapsularse de alguna manera para que exista en un único punto.

Simian es la herramienta que nos va a permitir encontrar replicación de bloques de código (o de texto en general) en una gran cantidad de lenguajes de programación y formatos de texto. Hasta donde yo sé, no existe como plugin para NetBeans (aunque sí para Eclipse e IntelliJ). Sí que existe el task para Ant y como plugin para el Hudson y por tanto veremos estas configuraciones.

Como el procedimiento para incluir nuevas herramientas de análisis y métricas va a ser siempre muy similar a lo que he explicado en el caso del Checkstyle, no explicaré el proceso de una manera tan detallada (entre otras cosas porque no aporta nada y sólo hace crecer el documento).

Incluir Simian en el script de Ant del NetBeans

Lo primero es obtener y configurar la herramienta en el entorno de desarrollo, para ello lo descargamos , lo descomprimimos en /srv / (id con cuidado porque el paquete no contiene un directorio raíz) y creamos el enlace simbólico correspondiente.

root@hargon:/srv# mkdir simian-2.2.24
root@hargon:/srv# cd simian-2.2.24
root@hargon:/srv/simian-2.2.24# mv ../simian-2.2.24.tar.gz .
root@hargon:/srv/simian-2.2.24# tar -zxf simian-2.2.24.tar.gz
root@hargon:/srv/simian-2.2.24# rm simian-2.2.24.tar.gz
root@hargon:/srv/simian# cd bin/
root@hargon:/srv/simian/bin# ln -s simian-2.2.24.jar simian.jar
root@hargon:/srv/simian-2.2.24# cd ../..
root@hargon:/srv# ln -s simian-2.2.24 simian

A continuación configuramos el build.xml de manera análoga al Checkstyle:

<taskdef
    resource="simiantask.properties"
    classpath="${simian.dir}/bin/simian.jar"/>

<target name="simian-report">
    <property file="nbproject/project.properties"/>

    <mkdir dir="${simian.report.dir}" />

    <simian>
        <fileset dir="${src.dir}" includes="**/*.java"/>

        <formatter type="xml"
            toFile="${simian.report.dir}/simian.xml"/>

        <formatter type="plain"
            toFile="${simian.report.dir}/simian.txt"/>
    </simian>
</target>

Las propiedades las definiremos allí donde toca: el simian.dir (con valor /srv/simian ) en el nbproject/private/private.properties y el simian.report.dir (con valor ${reports.dir}/simian-report ) en el nbproject/project.properties .

El task de Simian es muy sencillo. En su configuración simple tal y como tenemos, tan sólo hace falta especificar el directorio que contiene el código a analizar y el formato de los informes (en XML para integrarse con el Hudson y en texto plano para que sea más legible por humanos).

Integrando Simian con Hudson

Como en el caso del Checkstyle, replicamos la instalación de Simian en la máquina donde se encuentra el Hudson y definimos la propiedad simian.dir en el nbproject/private/private.properties .

El siguiente paso debería consistir en instalar el plugin del Simian para el Hudson, pero cuando lo buscamos en la lista de plugins disponibles nos damos cuenta que no aparece. Afortunadamente existe un plugin llamado Violations que integra diferentes herramientas de análisis estático de código (de hecho podríamos utilizar este mismo plugin para otras de las tratadas en este artículo) y que tiene soporte para Simian. Lo instalamos de la manera habitual, reiniciamos el Hudson para que lo cargue y el siguiente paso consiste en configurar el job . Como siempre, añadiremos el simian-report a la lista de targets a construir por el Ant y a continuación configuramos el plugin tal y como se muestra en la ilustración (también podemos definir los límites a partir de los cuales se rompe el build ).

Tras construir el job podremos ver los informes de replicación de código de manera integrada en el Hudson.

PMD

Tanto PMD como FindBugs, son herramientas de análisis estático de código (analizan el código y no el programa en ejecución) en busca de estructuras potencialmente peligrosas tales como:

  • posibles bugs,

  • código “muerto” (variables no accedidas, bloques de ejecución inalcanzables, etc.),

  • código subóptimo,

  • bloques con una estructura poco legible o más complicada de lo necesario.

Estas herramientas son especialmente útiles integradas en el IDE porque de esta manera el programador puede ir viendo mientras escribe el código las posibles alertas. Obviamente también tienen tasks de Ant lo que permite integrarlas fácilmente en Hudson para obtener los informes correspondientes.

Plugin para NetBeans del PMD

En este caso el plugin del PMD no está accesible mediante un repositorio, por lo que para instalarlo procederemos a la descarga del mismo y a su instalación local.

Una vez tenemos el fichero, deberemos descomprimirlo y nos quedamos con el plugin empaquetado en el fichero pmd.nbm que instalaremos a través la pestaña Downloaded del gestor de plugins: primero lo añadimos a la lista mediante el botón Add Plugins y después le damos a Install .

Aceptamos la licencia, asumimos el riesgo de instalar un plugin “no firmado” y rebotamos el IDE para que se reflejen los cambios.

En este momento el plugin debería estar listo para ser utilizado. Mientras trabajamos PMD monitorizará el código que escribimos y nos pondrá una marca en el editor cuando algo no le guste. Si ponemos el puntero encima de la marca, se nos mostrará un mensaje informativo del problema en forma de tooltip .

Si queremos obtener un informe de todos los posibles problemas en una carpeta de código, paquete o clase, sobre la ventana Projects hacemos click derecho en el elemento correspondiente -> Tools -> Run PMD.

Ello abrirá una nueva pestaña de informe con todas los avisos disponibles en forma de tabla (permitiendo ordenación por las diferentes columnas). Haciendo doble click sobre cualquiera de las advertencias, el editor se desplazará a la línea de código correspondiente.

PMD puede ser muy estricto y es posible que nos interese personalizar el tipo de alertas generadas y, quizá, desactivar algunas. Hay que encontrar un compromiso entre lo razonable y la calidad. Por ejemplo, algunas de las estructuras autogeneradas por el IDE (como el equals mostrado en el ejemplo unas líneas más arriba) producen advertencias que podríamos ignorar con tranquilidad ya que NetBeans sabe lo que hace . Para modificar la configuración del plugin y el tipo de reglas que aplicar, nos vamos al menú de opciones: Tools -> Options -> Miscellaneous.

Las reglas se gestionan mediante rulesets temáticos. Es decir un ruleset puede contener todas aquellas reglas ( rules) que tengan que ver, por ejemplo, con código muerto. Mediante el menú de rulesets podemos incluir o no las incluidas con PMD y añadir otras propias generadas por nosotros o por terceros. En el menú Manage rules se nos permite la activación o desactivación de reglas individuales.

Incluir PMD en el script del Ant de NetBeans

De manera análoga a las herramientas anteriores, descargamos e instalamos la herramienta en /srv .

root@hargon:/srv# unzip -q pmd-bin-4.2.5.zip
root@hargon:/srv# ln -s pmd-4.2.5/ pmd
root@hargon:/srv# rm pmd-bin-4.2.5.zip
root@hargon:/srv# cd pmd/lib/
root@hargon:/srv/pmd/lib# ln -s pmd-4.2.5.jar pmd.jar

A continuación modificaros el build.xml de manera adecuada.

<taskdef name="pmd"
    classname="net.sourceforge.pmd.ant.PMDTask"
    classpath="${pmd.dir}/lib/pmd.jar"/>

<target name="pmd-report">
    <property
        file="nbproject/project.properties"/>

    <mkdir dir="${pmd.report.dir}" />

    <pmd shortFilenames="true">
        <ruleset>unusedcode,basic,design,controversial</ruleset>
        <formatter type="xml"
            toFile="${pmd.report.dir}/pmd.xml"
            linkPrefix="http://pmd.sourceforge.net/xref/" />

        <formatter type="html"
            toFile="${pmd.report.dir}/pmd.html"
            linkPrefix="http://pmd.sourceforge.net/xref/" />

        <fileset dir="${src.dir}">
            <include name="**/*.java"/>
        </fileset>
    </pmd>
</target>

Las propiedades las añadimos al fichero correspondiente: el pmd.dir (con valor /srv/pmd ) en el nbproject/private/private.properties y el pmd.report.dir (con valor ${reports.dir}/pmd-report ) en el nbproject/project.properties .

El task del PMD es sencillo de utilizar. Primeramente indicamos qué rulesets de los estándar queremos analizar (podríamos también especificar rulesets personalizados) y a continuación indicamos en qué formatos queremos generar los informes. Como es habitual, tendremos los informes en XML que se integran bien con Hudson y en formato HTML para ser leídos por humanos.

Integrando PMD con Hudson

Al igual que las dos herramientas anteriores, instalamos PMD en el servidor de integración y definimos la propiedad pmd.dir en el nbproject/private/private.properties .

A continuación instalamos el plugin del PMD del Hudson de la manera habitual (no olvidéis rebotar lo) y ya podemos proceder a la configuración del job tal y como se muestra en la siguiente captura. Obviamente podemos establecer los límites que por defecto, como casi siempre, están desactivados.

Y estos serían los informes generados.

FindBugs

Sin entrar en demasiados detalles, FindBugs es una herramienta similar a la anterior. En el momento de escribir este tutorial todavía no existe una versión compatible del plugin para NetBeans 6.5, pero los ingenieros de FindBugs están trabajando en ello y me imagino que estará disponible en breve (sí que existen versiones funcionales para otros IDEs).

Incluir FindBugs en el plugin de Ant de NetBeans

Procedemos a descargar el paquete para instalarlo de la manera habitual.

root@hargon:/srv# tar -zxf findbugs-1.3.7.tar.gz
root@hargon:/srv# ln -s findbugs-1.3.7 findbugs
root@hargon:/srv# ls -alh findbugs
lrwxrwxrwx 1 root root 14 2009-03-05 15:55 findbugs -> findbugs-1.3.7

A continuación modificamos el build.xml para añadir un target que use el task del NetBeans.

<taskdef name="findbugs"
    classname="edu.umd.cs.findbugs.anttask.FindBugsTask"
    classpath="${findbugs.dir}/lib/findbugs.jar" />

<target name="findbugs-report" depends="compile">

    <property file="nbproject/project.properties"/>

    <mkdir dir="${findbugs.report.dir}"/>

    <findbugs home="${findbugs.dir}"
        output="xml"
        outputFile="${findbugs.report.dir}/findbugs.xml">
    
        <sourcePath path="${src.dir}" />
        <class location="${build.classes.dir}" />

    </findbugs>
</target>

Como en los casos anteriores, añadimos las propiedades a los ficheros correspondientes: el findbugs.dir (con valor /srv/findbugs ) en el nbproject/private/private.properties y el findbugs.report.dir (con valor ${reports.dir}/findbugs-report ) en el nbproject/project.properties .

El task del FindBugs requiere no sólo el código fuente sino también las clases compiladas y por ello el target tiene una dependencia del target estándar de NetBeans compile . Las clases compiladas se referencian mediante una propiedad también estándar de NetBeans: build.classes.dir .

El resto de propiedades son las típicas que hemos ido viendo, el directorio de instalación de la herramienta y el directorio de salida de los informes.

En este caso estamos generando los informes en XML que son los que entiende Hudson, pero también pueden generarse en otros formatos, como en texto plano o en HTML.

Integrando FindBugs con Hudson

Como siempre, replicamos la instalación del FindBugs en la máquina donde está instalado el Hudson y definimos la propiedad findbugs.dir apuntando al directorio correspondiente en el nbproject/private/private.properties .

FindBugs también tiene un plugin nativo para Hudson, así que lo instalamos y reiniciamos. A continuación, de manera totalmente análoga al PMD, configuramos el nuevo target y las opciones de FindBugs.

Finalmente ya podemos ver los informes generados.

Conclusión

En este artículo hemos aprendido que existen herramientas que nos pueden ayudar a generar métricas sobre la calidad de nuestro código. También hemos enfatizado el hecho que obtener un 100% de puntuación no garantiza un software de calidad.

Hemos visto que las herramientas suelen venir en tres “sabores”: como plugin para el IDE que dan un feedback inmediato al programador, como task del Ant que permiten ejecutarlas en modo consola e integrarlas con otros sistemas y como plugin para Hudson que permite publicar los informes correspondientes de una manera agradable para los humanos.

Existen muchas más herramientas de calidad. En próximos artículos tocaré algunas mas.





Problemas desplegando unidades de persistencia en JBoss 5

24 02 2009

En los últimos cursos que he dado sobre tecnologías JEE he utilizado los productos open source de Sun porque se integran bastante bien y además los considero más adecuados para formación que otras posibles alternativas. En particular, he estado utilizando NetBeans y Glassfish para explicar EJB 3.0 y las apis de persistencia y transaccionalidad (JPA y JTA).

Hace unos días me bajé JBoss AS 5 para ver las novedades que presentaba (ya que en el pasado trabajé bastante con él) y al ir a desplegar algún jar que había funcionado perfectamente en Glassfish me ha dado problemas. Como sospechaba, el problema estaba en el context.xml y en que JBoss AS se ha vuelto un poco más pijo.

Veámoslo con un ejemplo. Este es el persistence.xml que funcionaba sin problemas en Glassfish y que reventaba en el JBoss:

<?xml version="1.0" encoding="UTF-8"?>
<persistence>
  <persistence-unit name="Banco" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>banco</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="hibernate.hbm2ddl.auto" value="update"/>
    </properties>
  </persistence-unit>
</persistence>

Esta es la salida que producía en el log del JBoss:

2009-02-24 19:28:42,832 ERROR [org.jboss.kernel.plugins.dependency.AbstractKernelController] (HDScanner) Error installing to Parse: name=vfszip:/home/ivan/jboss-5.0.0.GA/server/all/deploy/titan.jar state=Not Installed mode=Manual requiredState=Parse
org.jboss.deployers.spi.DeploymentException: Error creating managed object for vfszip:/home/ivan/jboss-5.0.0.GA/server/all/deploy/titan.jar
	at org.jboss.deployers.spi.DeploymentException.rethrowAsDeploymentException(DeploymentException.java:49)
	at org.jboss.deployers.spi.deployer.helpers.AbstractParsingDeployerWithOutput.createMetaData(AbstractParsingDeployerWithOutput.java:337)
	at org.jboss.deployers.spi.deployer.helpers.AbstractParsingDeployerWithOutput.createMetaData(AbstractParsingDeployerWithOutput.java:297)
	at org.jboss.deployers.spi.deployer.helpers.AbstractParsingDeployerWithOutput.createMetaData(AbstractParsingDeployerWithOutput.java:269)
	at org.jboss.deployers.spi.deployer.helpers.AbstractParsingDeployerWithOutput.deploy(AbstractParsingDeployerWithOutput.java:230)
	at org.jboss.deployers.plugins.deployers.DeployerWrapper.deploy(DeployerWrapper.java:171)
	at org.jboss.deployers.plugins.deployers.DeployersImpl.doDeploy(DeployersImpl.java:1439)
	at org.jboss.deployers.plugins.deployers.DeployersImpl.doInstallParentFirst(DeployersImpl.java:1157)
	at org.jboss.deployers.plugins.deployers.DeployersImpl.install(DeployersImpl.java:1098)
	at org.jboss.dependency.plugins.AbstractControllerContext.install(AbstractControllerContext.java:348)
	at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:1598)
	at org.jboss.dependency.plugins.AbstractController.incrementState(AbstractController.java:934)
	at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:1062)
	at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:984)
	at org.jboss.dependency.plugins.AbstractController.change(AbstractController.java:822)
	at org.jboss.dependency.plugins.AbstractController.change(AbstractController.java:553)
	at org.jboss.deployers.plugins.deployers.DeployersImpl.process(DeployersImpl.java:781)
	at org.jboss.deployers.plugins.main.MainDeployerImpl.process(MainDeployerImpl.java:545)
	at org.jboss.system.server.profileservice.hotdeploy.HDScanner.scan(HDScanner.java:290)
	at org.jboss.system.server.profileservice.hotdeploy.HDScanner.run(HDScanner.java:221)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441)
	at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:317)
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:150)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$101(ScheduledThreadPoolExecutor.java:98)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.runPeriodic(ScheduledThreadPoolExecutor.java:181)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:205)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
	at java.lang.Thread.run(Thread.java:619)
Caused by: org.jboss.xb.binding.JBossXBException: Failed to parse source: Failed to resolve schema nsURI= location=persistence
	at org.jboss.xb.binding.parser.sax.SaxJBossXBParser.parse(SaxJBossXBParser.java:203)
	at org.jboss.xb.binding.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:168)
	at org.jboss.deployers.vfs.spi.deployer.JBossXBDeployerHelper.parse(JBossXBDeployerHelper.java:199)
	at org.jboss.deployers.vfs.spi.deployer.JBossXBDeployerHelper.parse(JBossXBDeployerHelper.java:170)
	at org.jboss.deployers.vfs.spi.deployer.SchemaResolverDeployer.parse(SchemaResolverDeployer.java:132)
	at org.jboss.deployers.vfs.spi.deployer.SchemaResolverDeployer.parse(SchemaResolverDeployer.java:118)
	at org.jboss.deployers.vfs.spi.deployer.AbstractVFSParsingDeployer.parseAndInit(AbstractVFSParsingDeployer.java:256)
	at org.jboss.deployers.vfs.spi.deployer.AbstractVFSParsingDeployer.parse(AbstractVFSParsingDeployer.java:188)
	at org.jboss.deployers.spi.deployer.helpers.AbstractParsingDeployerWithOutput.createMetaData(AbstractParsingDeployerWithOutput.java:323)
	... 27 more
Caused by: org.jboss.xb.binding.JBossXBRuntimeException: Failed to resolve schema nsURI= location=persistence
	at org.jboss.xb.binding.sunday.unmarshalling.SundayContentHandler.startElement(SundayContentHandler.java:313)
	at org.jboss.xb.binding.parser.sax.SaxJBossXBParser$DelegatingContentHandler.startElement(SaxJBossXBParser.java:401)
	at org.apache.xerces.parsers.AbstractSAXParser.startElement(Unknown Source)
	at org.apache.xerces.impl.xs.XMLSchemaValidator.startElement(Unknown Source)
	at org.apache.xerces.xinclude.XIncludeHandler.startElement(Unknown Source)
	at org.apache.xerces.impl.XMLNSDocumentScannerImpl.scanStartElement(Unknown Source)
	at org.apache.xerces.impl.XMLNSDocumentScannerImpl$NSContentDispatcher.scanRootElementHook(Unknown Source)
	at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source)
	at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
	at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
	at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
	at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
	at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source)
	at org.apache.xerces.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source)
	at org.jboss.xb.binding.parser.sax.SaxJBossXBParser.parse(SaxJBossXBParser.java:199)
	... 35 more
2009-02-24 19:28:42,848 WARN  [org.jboss.system.server.profileservice.hotdeploy.HDScanner] (HDScanner) Failed to process changes
org.jboss.deployers.client.spi.IncompleteDeploymentException: Summary of incomplete deployments (SEE PREVIOUS ERRORS FOR DETAILS):

*** CONTEXTS IN ERROR: Name -> Error

vfszip:/home/ivan/jboss-5.0.0.GA/server/all/deploy/titan.jar -> org.jboss.xb.binding.JBossXBRuntimeException: Failed to resolve schema nsURI= location=persistence

	at org.jboss.deployers.plugins.deployers.DeployersImpl.checkComplete(DeployersImpl.java:863)
	at org.jboss.deployers.plugins.main.MainDeployerImpl.checkComplete(MainDeployerImpl.java:665)
	at org.jboss.system.server.profileservice.hotdeploy.HDScanner.scan(HDScanner.java:293)
	at org.jboss.system.server.profileservice.hotdeploy.HDScanner.run(HDScanner.java:221)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441)
	at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:317)
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:150)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$101(ScheduledThreadPoolExecutor.java:98)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.runPeriodic(ScheduledThreadPoolExecutor.java:181)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:205)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
	at java.lang.Thread.run(Thread.java:619)

La solución ha sido más o menos sencilla: definir el espacio de nombres XML tal y como sigue y la unidad de persistencia se ha desplegado sin más problemas:

<?xml version="1.0" encoding="UTF-8"?>
<persistence
    version="1.0"
    xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
   <persistence-unit name="titan">
      <jta-data-source>java:/DefaultDS</jta-data-source>
      <properties>
         <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      </properties>
   </persistence-unit>
</persistence>

Tendré que echar un ojo a la especificación de JPA para ver si definir el espacio de nombres es obligatorio, aunque me parece recordar que esto no era una exigencia en versiones anteriores de JBoss AS…