lunes, 21 de diciembre de 2009

Análisis Gráfico de Excepciones Java en Bitácoras (Logs)

En ocasiones nos va tocar analizar qué está sucendiendo con nuestra aplicación en vivo porque aparentemente estamos teniendo "muchas" excepciones. Entrecomillo "muchas" porque como desarrolladores o encargados del monitoreo de la aplicación, alguien puede reportarnos que está viendo muchas excepciones en las bitácoras (logs) pero en realidad no sabemos si son muchas hasta que las contemos y veamos cuál es el promedio normal de apariciones de cierta excepción; en otras palabras, ver cuál es la tendencia de cierto tipo de excepciones. Si tenemos que analizar bitácoras enormes donde el comando buscar del editor de texto ("CTRL + F") no es suficiente para tener una perspectiva a gran escala, es entonces donde tenemos que atacar el problema de una forma más automatizada.

Quiero compartir en este artículo la fórmula que he utilizado en el pasado para analizar grandes cantidades de excepciones en grandes extenciones de bitácoras. Para generar el gráfico que vamos analizar uso un script de Perl para formatear los datos de una manera resumida y adecuada para luego procesarlos en una hoja de cálculo. En mi caso uso Calc de Open Office pero una hoja de Excel podría ser usada de igual manera.

El siguiente script de Perl extrae el nombre de la excepción y la fecha con la hora en que fue lanzada, y salva estos dos datos en dos columnas separadas por el carácter "~":


#!/usr/local/bin/perl

my $dir = $ARGV[0];

print "\n\n\t\tReading directory: " . $dir . " ...\n\n";
opendir(DIR, $dir);

my @files = readdir(DIR);
my $output = $dir."_results.txt";
closedir(DIR);

open (RESULTS, '>'.$output);
print RESULTS "date~exception\n";
$exception_counter = 0;

foreach $file( @files){
unless ( ($file eq ".") || ($file eq "..") ) {
my $total_size_read = 0;
my $file_path = $dir . '\\' . $file;
my $filesize;
{
use strict;
use warnings;
$filesize = -s $file_path ;
$filesize = $filesize / 1000; #Kbytes
}

print "\nReading file " .$file_path . "(" . $filesize . " kb)\n";
open (LOG, $file_path) || die "couldn't open the file!";

$line_number = 1;

while ($record = ) {

if ($record =~ m/([a-zA-Z1-9\.]*Exception)+/) {
$exception_description = $1;

if ($record =~ m/(((\d{4})-(\d{2})-(\d{2})\s(\d{2}))(:\d{2}:\d{2}))/) {
$year= $3;
$month=$4;
$day=$5;
$hour=$6;
print RESULTS $year . "-". $month . "-" . $day . " " . $hour . ":00~" . $exception_description ."\n" ;
$exception_counter++;
}
}

$line_number++;
if ($line_number % 1000 eq 0) {
my $progress = sprintf("%.2f",(($total_size_read /1000) / $filesize) * 100);
print "\rTotal lines read: " . $line_number . " | Exceptions found: " . $exception_counter . " | Progress:" . $progress . "%";

}
{
use bytes;
my $byte_size = length($record);
$total_size_read = $total_size_read + $byte_size;
}
}

close(LOG);
}
}

close (RESULTS);


No voy a detallar mucho la lógica del script la cual de todas maneras es bastante simple. Recibe como argumento el nombre de un directorio y procesa todos los archivos que hayan dentro de él para extraer las aparaciones de excepciones de cada archivo.



El resultado final del script es un archivo con el siguiente formato:

date~exception
2009-08-16 21:00~Exception
2009-08-16 21:00~Exception
2009-08-16 21:00~java.lang.NullPointerException
2009-08-16 21:00~Exception
2009-08-16 21:00~Exception
2009-08-16 21:00~Exception
2009-08-16 21:00~Exception
2009-08-16 21:00~Exception
2009-08-16 21:00~Exception
2009-08-16 21:00~Exception
2009-08-16 22:00~Exception
2009-08-16 22:00~Exception
2009-08-16 22:00~Exception
2009-08-16 22:00~Exception
2009-08-16 22:00~java.lang.NullPointerException


En este punto abrimos Calc de Open Office, seleccionamos los resultados del script, los copiamos y pegamos en la hoja de cálculo. Automáticamente se abrirá una caja de dialogo como la siguiente en la cual marcamos la caja de otros ("others") y escribimos el carácter de "raba de chancha" para separar los datos en dos columnas.

Una vez hecho esto es recomendable ordenar los datos de manera ascendente acorde con la columna de fecha para tener una secuencia cronológica de las excepciones. Seleccionamos las dos columnas excepto por la primera fila y usamos la opción de ordenamiento (Data->Sort...).



Seguidamente usamos, en mi opinión, una de las herramientas más poderosas de una hoja de cálculo: el DataPilot o Tabla Pivot en lenguaje Excel. El DataPilot nos permite agrupar datos de distintas columnas y contar o sumar el número de apariciones de acuerdo a las categorias que definimos con las columnas que seleccionamos. Seleccionamos las dos columnas, Data->DataPilot->Start...


En el diálogo que se abre arrastramos la caja "date" en los campos de columna, y la caja "exception" en los campos de fila. Tomamos nuevamente la caja "exception" y la arrastramos en el medio y damos clic en opciones. La opción por defecto es "Sum"; la cambiamos por "Count" para contar el número de apariciones por fecha y excepción. Más abajo hay otras opciones útiles que debemos usar:
  1. Resultados en una nueva hoja: "Results to - new sheet -".
  2. Ignorar filas vacias: "Ignore empty rows".

A partir de este momento podemos comenzar a analizar la línea cronológica de excepciones, o verificar un punto discreto de nuestra línea de tiempo si sabemos de antemano que algo sucedió en determinado día u hora. Para completar nuestro análisis vamos a generar un gráfico con la tabla que recien generamos.

Seleccionamos el rango de nuestra tabla para adelantarnos en uno de los pasos en la creación de nuestro gráfico. Buscamos el icono de gráfico ("chart") y lo presionamos.


1. Lo primero que tenemos que hacer es seleccionar el tipo de gráfico. En este caso uso el de puntos y líneas:


2.
Dado que ya había seleccionado el rango de datos, la primera caja ya contiene nuestra selección previa. Debemos seleccionar la opción de series de datos en filas ("data series in rows") y chequear las dos opciones de primera fila y columna como etiquetas.


3. En las series de datos no hay mucho que modificar. Podemos quitar la serie de "Total Result " si nos causa algún ruido en nuestro análisis. Siempre podemos regresar a modificar esta sección después de generado el gráfico.


4. En elementos del gráfico no cambiamos nada relevante. En mi caso me gusta cambiar la posición de las leyendas del eje Y, o sea cada uno de los nombre de excepción, para situarlas arriba del gráfico, de otra manera quedan posicionados a la derecha y resulta a veces que son demasiadas leyendas con nombres extensos que acortan el área del gráfico que nos interesa analizar.


Generamos finalmente nuestro gráfico el cual como podrán notar, sale un poco desordenado por la gran cantidad de elementos en el eje X. Para solucionar esto podemos editar el gráfico en las propiedades del eje X.

Clic derecho al gráfico -> Edit -> Clic derecho al eje X -> Object Properties
Vamos a la sección de etiqueta ("label") y rotamos el texto desde su posición de 0° hasta algún nivel de inclinación que nos permita apreciar mejor nuestro gráfico. En mi caso uso 45°. 90° sería también útil para no escalar tanto nuestro gráfico.


Finalmente quedamos con un producto final del cual podemos sacar algunas conclusiones interesantes. Por ejemplo podemos ver una tendencia hasta cierto punto normal de número de excepciones desde el periodo del 14 Agosto pero después súbitamente el número de excepciones de NullPointerException se disparan el 17 del mismo mes. ¿Habrá sucedido algún evento especial en esa fecha?


Como ven el gráfico a escala nos permite iniciar nuestro análisis de manera más focalizada sin comenzar a dar tumbos a ciegas. De aquí en adelante nos tocará tomar otros pasos para continuar la investigación, pero te aseguro que si presentas en una reunión a tu jefe y/0 cliente un gráfico de este tipo, vas a lucir mucho más profesional que simplemente decir:

-"Tenemos muchas excepciones de NullPointer"

miércoles, 16 de diciembre de 2009

Provincias, Cantones y Distritos

Un típico problema a resolver para los programadores de Costa Rica en aplicaciones locales ,es manejar los catálogos de provincias, cantones y distritos (PCD's). La relación de estos tres catálogos refleja una relación jerárquica donde una provincia agrupa cantones y un cantón agrupa distritos.

Así lucen los scripts para crear estas tablas en una base de datos MySQL:

Provincia:
DROP TABLE IF EXISTS `PROVINCIA_CR`;
CREATE TABLE `PROVINCIA_CR` (
  `codigo_provincia` smallint(5) unsigned NOT NULL auto_increment,
  `nombre_provincia` varchar(45) NOT NULL,
  PRIMARY KEY  USING BTREE (`codigo_provincia`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=latin1;

Cantón:
DROP TABLE IF EXISTS `CANTON_CR`;
CREATE TABLE `CANTON_CR` (
  `codigo_canton` smallint(5) unsigned NOT NULL,
  `codigo_provincia` smallint(5) unsigned NOT NULL,
  `nombre_canton` varchar(45) NOT NULL,
  PRIMARY KEY  USING BTREE (`codigo_canton`),
  KEY `FK_CANTON_PROVINCIA` (`codigo_provincia`),  
  CONSTRAINT `FK_CANTON_PROVINCIA` FOREIGN KEY (`codigo_provincia`) REFERENCES `PROVINCIA_CR` (`codigo_provincia`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Distrito:


DROP TABLE IF EXISTS `DISTRITO_CR`;
CREATE TABLE `DISTRITO_CR` (
  `codigo_distrito` int(10) unsigned NOT NULL,
  `codigo_canton` smallint(5) unsigned NOT NULL,
  `nombre_distrito` varchar(45) NOT NULL,  
  PRIMARY KEY  (`codigo_distrito`),
  KEY `FK_DISTRITO_CANTON` (`codigo_canton`),
  CONSTRAINT `FK_DISTRITO_CANTON` FOREIGN KEY (`codigo_canton`) REFERENCES `CANTON_CR` (`codigo_canton`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


Hace poco necesitaba crear estos catálogos y tuve que darme a la tediosa tarea de manipular una página html con el contenido de las PCD's para poder generar las inserciones a la base de datos. Así que dejo a disposición dos documentos para cualquiera que les sea útil:
  1. Documento hoja de cálculo con toda la información necesaria. Se puede editar para generar las inserciones para otro administrador de BD que no sea MySQL.
  2. Documento con los scripts para crear las tablas y las inserciones.
En este otro artículo muesto como crear la serie de selectores para una interfaz web.

viernes, 11 de diciembre de 2009

Iterando sobre estructuras complejas de mapas

¿Te has encontrado alguna vez con estructura de datos anidadas y para colmo con un colocho de genéricos ("Generics")? Aún peor ¿has sido el autor intelectual de estas sentencias confusas? Dejame mostrar un ejemplo de lo que me refiero y que he encontrado al interactuar con las prácticas "inusuales" de algunos compañeros de proyecto.

En nuestra aplicación de ejemplo necesitamos guardar y mostrar la información de matrícula para un cuatrimestre de universidad. La información está estructurada de una manera jerárquica donde el primer nodo es el año en curso, luego le siguen los cuatrimestres, seguidamente los cursos y en el nivel más bajo los estudiantes enlistados en esos cursos.

Año (Year)
++ Cuatrimestre (Quatrimester)
+++ Curso (Course)
++++ Estudiante (Student)

Pareciera que podríamos sentarnos con una buena tacita de café, un hoja de papel y lapiz para sentarnos gustosamente a diseñar nuestras clases; bueno voy a mostrar lo que puede pasar
cuando alguien quiere brincarse este paso y "ahorrar" un poco de tiempo de diseño:


Map<Integer, Map<Integer, Map<String, List<Student>>>> studentsByQuarter = new HashMap<Integer, Map<Integer, Map<String, List<Student>>>>();


¿Para qué complicar más nuestra aplicación agregando más archivos para representar nuestras clases cuando todo se puede resumir en una sola linea? Ese es el acercamiento implicito al problema cuando alguien decide crear este nivel de complejidad que para él o ella puede ser comprendible pero pobre de la persona que tenga que mantener ese código y comenzar por comprender ese colocho.

Voy a desarrollar más esta manera de codificar para mostrar la complejidad intrínseca que se obtiene al no aislar los distintos componentes del problema que queremos resolver.

La clase "Student" as bastante simple con solamente un atributo "fullName":


public class Student {
private String fullName;

public Student(String fullName) {
this.fullName = fullName;
}

public String getFullName() {
return fullName;
}

public void setFullName(String fullName) {
this.fullName = fullName;
}
}


Ahora agregamos un clase "EnrollmentProcessVr1" con la ya introducidad variable "monstruo" y dos métodos adicionales:

+ public static void startEnrollment(int year, int quarter){...} // ¨Para guardar los datos de matrícula.
+ public static void showEnrollment() {...} // Muestra los datos contenidos en la estructura de datos.

Veamos lo complicado que se vuelve tanto agregar como mostrar datos desde esta estructura:


import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class EnrollmentProcessVr1 {
private static Map<Integer, Map<Integer, Map<String, List<Student>>>> studentsByQuarter =
new HashMap<Integer, Map<Integer, Map<String, List<Student>>>>();

public static void startEnrollment(int year, int quarter) {
// Create a Year.
Integer currentYear = new Integer(year);

// Create a quatrimester.
Integer quatrimester = new Integer(quarter);

// Create courses.
String course1 = "Discrete Maths";
String course2 = "Programming I";

// Create List of Students.
List<Student> students = new ArrayList<Student>();
students.add(new Student("Cristobal Colón"));
students.add(new Student("Juan SantaMaría"));

// Create a Map of Courses/Students.
Map <String, List<Student>> studentsByCourses = new HashMap<String, List<Student>>();

// Add Students to courses.
studentsByCourses.put(course1, students);
studentsByCourses.put(course2, students);

// Create a Map of Quarter/Courses.
Map<Integer, Map <String, List<Student>>> coursesQuarterMap =
new HashMap<Integer, Map<String,List<Student>>>();

coursesQuarterMap.put(quatrimester, studentsByCourses);

// Add quarter to year.
studentsByQuarter.put(currentYear, coursesQuarterMap);
}

public static void showEnrollment() {
Iterator studentsByQuarterIterator = studentsByQuarter.entrySet().iterator();
while (studentsByQuarterIterator.hasNext()) {
Map.Entry studentsByQuarterPairs = (Map.Entry)studentsByQuarterIterator.next();
System.out.println("Year: " + String.valueOf(studentsByQuarterPairs.getKey()));

Map<Integer, Map<String, List<Student>>> quarterCoursesMap =
(Map<Integer, Map<String, List<Student>>>) studentsByQuarterPairs.getValue();

Iterator quarterCoursesIterator = quarterCoursesMap.entrySet().iterator();

while (quarterCoursesIterator.hasNext()) {
Map.Entry coursesByQuarterPairs = (Map.Entry)quarterCoursesIterator.next();
System.out.println("\tQuarter: " + String.valueOf(coursesByQuarterPairs.getKey()));

Map<String, List<Student>> coursesStudentsMap =
(Map<String, List<Student>>) coursesByQuarterPairs.getValue();

Iterator coursesStudentsIterator = coursesStudentsMap.entrySet().iterator();

while (coursesStudentsIterator.hasNext()) {
Map.Entry coursesStudentsPairs = (Map.Entry)coursesStudentsIterator.next();
System.out.println("\t\tCourse: " + String.valueOf(coursesStudentsPairs.getKey()));

List<Student> studentsByCourse = (List<Student>) coursesStudentsPairs.getValue();

for(Student student : studentsByCourse) {
System.out.println("\t\t\tStudent: " + student.getFullName());
}
}
}
}
}

public static void main(String[] args) {
EnrollmentProcessVr1.startEnrollment(2009, 3);
EnrollmentProcessVr1.showEnrollment();
}
}


Salida del método main:



Mi foco en este post será mostrar una manera de resolver la complejidad intrisica de manejar estructuras de mapa, no necesariamente como hacer "refactoring" de un mal diseño de estructura de datos, pero el desarrollo de este ejemplo ayuda a probar un punto de que incluso con un buen diseño de clases podemos tener mucho código para hacer un recorrido de las estructuras internas de mapa de nuestras clases.

Volvamos entonces al modo diseño de clases:




EnrollmentTrackVr1:


import java.util.LinkedHashMap;
import java.util.Map;

public class EnrollmentTrackVr1 {
private Map<Integer, QuatrimestersVr1> quatrimesters;

public EnrollmentTrackVr1() {
quatrimesters = new LinkedHashMap<Integer, QuatrimestersVr1>();
}

public Map<Integer, QuatrimestersVr1> getQuatrimesters() {
return quatrimesters;
}


public void addStudent(Student student, int year, int quatrimesterNumber, String courseName) {
if (!quatrimesters.containsKey(year)) {
quatrimesters.put(year, new QuatrimestersVr1());
}
quatrimesters.get(year).addCourse(quatrimesterNumber, courseName, student);
}
}



QuatrimestersVr1:


import java.util.HashMap;
import java.util.Map;

public class QuatrimestersVr1 {

private Map<Integer, CoursesVr1> courses;

public QuatrimestersVr1() {
courses = new HashMap<Integer, CoursesVr1>();
}

public void addCourse(int quatrimesterNumber, String courseName, Student student) {
if (!courses.containsKey(quatrimesterNumber)) {
courses.put(quatrimesterNumber, new CoursesVr1());
}
courses.get(quatrimesterNumber).addStudent(courseName, student);
}

public Map<Integer, CoursesVr1> getCourses() {
return courses;
}
}


CoursesVr1:


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class CoursesVr1 {
private Map<String, List<Student>> students;

public CoursesVr1() {
students = new HashMap<String, List<Student>>();
}

public void addStudent(String courseName, Student student) {
if (!students.containsKey(courseName)) {
students.put(courseName, new ArrayList<Student>());
}
students.get(courseName).add(student);
}

public Map<String, List<Student>> getStudents() {
return students;
}

}


En la nueva versión de la clase EnrollmentProcess vemos como el código para agregar nueva información se simplifica significativamente pero la iteración de la estructura
sigue siendo un código a mi gusto complejo porque se necesita accesar los iteradores de cada una de las estructuras internas de cada clase de nuestro diseño:


import java.util.Iterator;
import java.util.List;
import java.util.Map;


public class EnrollmentProcessVr2 {
private static EnrollmentTrackVr1 enrollmentTrackVr1;

public static void startEnrollment(int year, int quarter) {

String course1 = "Discrete Maths";
String course2 = "Programming I";
Student student1 = new Student("Cristobal Colón");
Student student2 = new Student("Juan Santamaría");

enrollmentTrackVr1 = new EnrollmentTrackVr1();
enrollmentTrackVr1.addStudent(student1, year, quarter, course1);
enrollmentTrackVr1.addStudent(student2, year, quarter, course1);
enrollmentTrackVr1.addStudent(student1, year, quarter, course2);
enrollmentTrackVr1.addStudent(student2, year, quarter, course2);

}

public static void showEnrollment() {
Iterator enrollmentIterator =
enrollmentTrackVr1.getQuatrimesters().entrySet().iterator();

while (enrollmentIterator.hasNext()) {
Map.Entry enrollmentPairs = (Map.Entry)enrollmentIterator.next();
System.out.println("Year: " + String.valueOf(enrollmentPairs.getKey()));

QuatrimestersVr1 quatrimesters =
(QuatrimestersVr1) enrollmentPairs.getValue();

Iterator quatrimestersIterator =
quatrimesters.getCourses().entrySet().iterator();

while (quatrimestersIterator.hasNext()) {
Map.Entry quatrimestersPairs = (Map.Entry)quatrimestersIterator.next();
System.out.println("\tQuarter: " + String.valueOf(quatrimestersPairs.getKey()));

CoursesVr1 courses = (CoursesVr1) quatrimestersPairs.getValue();

Iterator coursesIterator =
courses.getStudents().entrySet().iterator();

while (coursesIterator.hasNext()) {
Map.Entry coursesPairs = (Map.Entry)coursesIterator.next();
System.out.println("\t\tCourse: " + String.valueOf(coursesPairs.getKey()));

List<Student> studentsByCourse = (List<Student>) coursesPairs.getValue();

for(Student student : studentsByCourse) {
System.out.println("\t\t\tStudent: " + student.getFullName());
}
}
}
}
}

public static void main(String[] args) {
EnrollmentProcessVr2.startEnrollment(2009, 3);
EnrollmentProcessVr2.showEnrollment();
}
}



¿No sería más pura vida que nuestras clases pudieran ser iteradas en la misma manera que hacemos con una lista de Java Collections?


List<String> fooList = new ArrayList<String>();
...
for (String fooObject : fooList) {
System.out.println(fooObject);
}


Es posible hacer esto si aprendemos a usar las interfaces "Iterable" y "Iterator". En el diseño que voy a mostrar a continuación uso mi propia interface la cual extiende de estas dos y además agregamos un nuevo método que nos será útil para retornar la llave del objeto en curso que estamos iterando (explicaré en detalle más adelante).

Así es como luce el nuevo diseño:



La nueva interface recibe un nuevo elemento genérico "K" que será el tipo/clase de la llave del mapa.


import java.util.Iterator;

public abstract interface MapIterable<K, V> extends Iterable<V>, Iterator<V>{
public K currentKey();
}


Explicamos a continuación en detalle cuáles métodos necesitamos implementar para que nuestras clases sean iterables:

+ public boolean hasNext() : este método indica si la clase puede avanzar en la estructura que está iterando. En mi implementación agrego una variable privada (currentKeyYearIndex)
para mantener el estado del índice actual del arreglo. Esta variable es incrementada con el método next().

+ public V next(): retorna el siguiente objeto de la iteración. En esta implementación obtengo el keyset del mapa interno de la clase y lo paso a un arreglo para obtener el elemento
de la locación indicada por el índice actual.

+ public void remove(): remueve el objeto actual. No necesitaba esta funcionalidad así que solamente arrojo una excepción de tipo UnsupportedOperationException;

+ public Iterator iterator(): Obtiene el iterador. Simplemente retorna la clase.

+ public K currentKey(): el nuevo método que agregué extra para retornar la llave actual de la iteración.

EnrollmentTrackVr2:


import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

public class EnrollmentTrackVr2 implements MapIterable<Integer, QuatrimestersVr2> {

private Map<Integer, QuatrimestersVr2> quatrimesters;
private int currentKeyYearIndex;

public EnrollmentTrackVr2() {
quatrimesters = new LinkedHashMap<Integer, QuatrimestersVr2>();
currentKeyYearIndex = 0;
}

public Map<Integer, QuatrimestersVr2> getQuatrimesters() {
return quatrimesters;
}


public void addStudent(Student student, int year, int quatrimesterNumber, String courseName) {
if (!quatrimesters.containsKey(year)) {
quatrimesters.put(year, new QuatrimestersVr2());
}
quatrimesters.get(year).addCourse(quatrimesterNumber, courseName, student);
}

public boolean hasNext() {
if (quatrimesters.keySet().toArray().length > currentKeyYearIndex) {
return true;
}
return false;
}

public QuatrimestersVr2 next() {
int currentKeyYear = (Integer)
quatrimesters.keySet().toArray()[currentKeyYearIndex++];
return quatrimesters.get(currentKeyYear);
}

public void remove() {
throw new UnsupportedOperationException();
}

public Iterator<QuatrimestersVr2> iterator() {
return this;
}
public Integer currentKey() {
return (Integer)
quatrimesters.keySet().toArray()[currentKeyYearIndex-1];
}
}


QuatrimestersVr2:


import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class QuatrimestersVr2 implements MapIterable<Integer, CoursesVr2> {

private Map<Integer, CoursesVr2> courses;
private int currentQuatriIndex;
private int year;

public QuatrimestersVr2() {
courses = new HashMap<Integer, CoursesVr2>();
currentQuatriIndex = 0;
}

public QuatrimestersVr2(int year) {
this();
this.year = year;
}

public void addCourse(int quatrimesterNumber, String courseName, Student student) {
if (!courses.containsKey(quatrimesterNumber)) {
courses.put(quatrimesterNumber, new CoursesVr2());
}
courses.get(quatrimesterNumber).addStudent(courseName, student);
}

public Map<Integer, CoursesVr2> getCourses() {
return courses;
}

public boolean hasNext() {
if (courses.keySet().toArray().length > currentQuatriIndex) {
return true;
}
return false;
}

public CoursesVr2 next() {
int currentQuatri = (Integer)
courses.keySet().toArray()[currentQuatriIndex++];
return courses.get(currentQuatri);
}

public void remove() {
throw new UnsupportedOperationException();
}

public Iterator<CoursesVr2> iterator() {
return this;
}

public int getYear() {
return year;
}

public void setYear(int year) {
this.year = year;
}

public Integer currentKey() {
return (Integer)
courses.keySet().toArray()[currentQuatriIndex-1];
}
}



CoursesVr2:


import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class CoursesVr2 implements MapIterable<String, List<Student>> {

private Map<String, List<Student>> students;
private int currentCourseIndex;
private int quatrimester;

public CoursesVr2() {
students = new HashMap<String, List<Student>>();
currentCourseIndex = 0;
}

public CoursesVr2(int quatrimester) {
this();
this.quatrimester = quatrimester;
}

public void addStudent(String courseName, Student student) {
if (!students.containsKey(courseName)) {
students.put(courseName, new ArrayList<Student>());
}
students.get(courseName).add(student);
}

public Map<String, List<Student>> getStudents() {
return students;
}

public boolean hasNext() {
if (students.keySet().toArray().length > currentCourseIndex) {
return true;
}
return false;
}

public List<Student> next() {
String currentCourse =
students.keySet().toArray()[currentCourseIndex++].toString();
return students.get(currentCourse);
}

public void remove() {
throw new UnsupportedOperationException();
}

public Iterator<List<Student>> iterator() {
return this;
}

public int getQuatrimester() {
return quatrimester;
}

public void setQuatrimester(int quatrimester) {
this.quatrimester = quatrimester;
}

public String currentKey() {
return students.
keySet().toArray()[currentCourseIndex-1].toString();
}
}



Finalmente veamos como queda nuestro proceso de iteración:

EnrollmentProcessVr3:


import java.util.List;

public class EnrollmentProcessVr3 {
private static EnrollmentTrackVr2 enrollmentTrackVr2;

public static void startEnrollment(int year, int quarter) {

String course1 = "Discrete Maths";
String course2 = "Programming I";
Student student1 = new Student("Cristobal Colón");
Student student2 = new Student("Juan Santamaría");

enrollmentTrackVr2 = new EnrollmentTrackVr2();

enrollmentTrackVr2.addStudent(student1, year, quarter, course1);
enrollmentTrackVr2.addStudent(student2, year, quarter, course1);

enrollmentTrackVr2.addStudent(student1, year, quarter, course2);
enrollmentTrackVr2.addStudent(student2, year, quarter, course2);

}

public static void showEnrollment() {
for(QuatrimestersVr2 quatrimesters : enrollmentTrackVr2) {
System.out.println("Year: " + enrollmentTrackVr2.currentKey());
for (CoursesVr2 courses : quatrimesters) {
System.out.println("\tQuatrimester: " + quatrimesters.currentKey());
for (List<Student> students : courses) {
System.out.println("\t\tCourses: " + courses.currentKey());
for (Student student : students) {
System.out.println("\t\t\tStudent: " + student.getFullName());
}
}
}
}
}

public static void main(String[] args) {
EnrollmentProcessVr3.startEnrollment(2009, 3);
EnrollmentProcessVr3.showEnrollment();
}
}



El código puede que tenga algunas pulgas, creo que olvidé reinicializar la llave actual en algu punto, pero la idea no era tener un código a prueba de balas sino demostrar el poder de usar la interface "Iterable". Espero haya sido de interés este tema y ojalá puedan poner en práctica el uso de Iterable.

jueves, 3 de diciembre de 2009

Análizando el desempeño de nuestra aplicación web con Eclipse TPTP

Usar una herramienta de profiler (perdón por el anglisismo pero no encuentro una término adecuado en español, se puede decir "perfilador" supongo) es una buena manera de encontrar esos cuellos de botella de nuestra aplicación web.

Hoy quisiera recomendar una herramienta que he utilizado en el pasado. Puede que hayan otras más adecuadas dependiendo del tipo de análisis que se quiera efectuar, pero por ahora describiré rápidamente la que conozco: TPTP (Test and Perfomance Tools Platform) o "Prueba y Plataforma de Herramientas de Desempeño".

En mis propias palabras, un profiler es una herramienta que nos permite tener datos estadísticos para probar aquello que ya sabíamos antes (talvez de manera intituiva) por mucho tiempo, pero necesitabamos alguna herramienta sofisticadilla para culpar el mal desempeño de un código, esperando que no sea el que creamos nosotros :). Poniéndonos serios, el uso de estas herramientas para recopilar datos y resumirlos por nosotros realmente nos quita una carga cuando necesitamos apuntar de manera certera, qué parte de nuestra aplicación está degradando el desempeño.

Primero descargamos e instalamos TPTP en Eclipse. Puede ser descargado en un paquete "todo-en-uno" con la versión Galileo.




Abrimos el proyecto al que deseamos crear el perfil ("profile") y usamos la opción "profile" para comenzar el proyecto:

Clic derecho en el proyecto -> “Profile As“ -> “Profile Configurations...”



Seleccionar la opción de “Tomcat 5.x”:



Seleccionar el tipo de análisis. En este caso estamos seleccionando el Análisis de Tiempo de Ejecución (“Execution Time Analysis”).



Seleccionamos Editar Opciones ("Edit Options") para seleccionar el nivel de detalle del análisis:



Hay algunas otras opciones que pueden ser configuradas pero las dejamos a exploración del lector. Finalmente damos clic on "Profile". Eclipse iniciará el servidor Tomcat y la perspectiva "Profiling and Logging" se abrirá.



Doble clic en "Execution Time Analysis" para abrir la tabla de resumen:



Una vez que el profiler está corriendo, necesitamos navegar localmente en el sitio para que la mayoría, o al menos las partes de interés, de nuestro código sea examinado y así recolectar todos los datos de las diferentes clases de la aplicación.

Este análisis mide la cantidad de tiempo (en segundos) consumido por cada método de cada clase. En la tabla de resumen solamente los 10 primeros paquetes más altos en tiempo base ("base time") son mostrados. No obstante se puede ver el detalle de todos lo paquetes ejecutados si se desea.

Tiempo base (Base Time): La cantidad de tiempo (en segundos) que un método especifico acumuló en todas las llamadas de la apliación. Esto no incluye el tiempo de ejecución de otros métodos anidados (sub llamados a otros métodos).

Tiempo promedio base: (Average base time): Es el tiempo base divido entre el número total de llamados hechos por la aplicación.

Tiempo base acumulado (Cumulative base time): La cantidad de tiempo (en segundos) que al método le toma ejecutarse incluyendo todos los sub llamados hechos por el mismo.
Siempre: Tiempo base acumulado >= Tiempo base (Base Time)

Cabe concluir diciendo que esta operación de recolección de datos por parte del profiler es una operación sumamente pesada que consume muchos recursos del equipo dependiendo de lo complejo de la aplicación web. Es recomendable no hacer otras cosas en el equipo porque sino, por experiencia propia, el Eclipse tiende a congelarse y no responder más.

lunes, 30 de noviembre de 2009

Tu Primer Proyecto Web con Eclipse & Tomcat

Tu primer proyecto web usando Eclipse y Tomcat

Requerimientos:

  1. Instalación de Tomcat (Descarga).
  2. Eclipse IDE. En este post usaré EasyEclipse Expert Java Version: 1.3.1.1 (Descarga).

Siguiendo la línea "Para Newbies", explicaré en cortos pasos una manera rápida de crear tu primer proyecto web con java usando Eclipse como IDE y Tomcat como servidor web. En este caso no se usará un "Super Wizard" para configurar el proyecto sino que se seguiran ciertos pasos manuales para
entender un poco lo que se está haciendo.


1. Primero tenemos que crear nuestro proyecto Java.

File -> New -> Java Project





2. Ahora creamos una clase simple que sera invocada por nuestro código JSP.

Clic derecho en el directorio "src" -> New -> class
Right click on “src” folder ->New ? class

package newbie;  

public class HolaMundo {
public static String getHola() {
return "Hola From Costa Rica, Pura Vida!!!";
}
}


3. Es tiempo de crear la estructura de nuestro directorio "WebContent". Para este simple caso crearemos la siguiente estructura:

HolaMundo
+WebContent
++ pages
++ WEB-INF

4. Dentro del directorio WEB-INF agregaremos un archivo llamado "web.xml". Por el momento vamos a dejarlo simple, pero en el futuro, a medida en que crezca el proyecto, más configuraciones se agregaran a este archivo.

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<!--> More Settings Here <-->

</web-app>


5. Dentro del directorio de "pages" agregaremos un JSP llamado “HolaMundo.jsp”:

<%@ page import="newbie.HolaMundo" %>
<%
String message = HolaMundo.getHola();
%>
<h1><%=message%></h1>

El código de este JSP basicamente incluye nuestra clase Java y despliega el String retornado por el método "getHola()".

6. Antes de configurar nuestro Tomcat necesitamos configurar nuestro "build path". Por defecto Eclipse establece el directorio de salida o "outout folder" (donde los "*.class" para cada uno de los "*.java" se localizan) in "<nombre_proyecto/bin>". Ya que nuestro proyecto es web, necesitamos mover el path a:

“HolaMundo/WebContent/WEB-INF/classes”

Clic derecho en el proyecto ->Build Path-> Configure Build Path... ->Source Tab



7. El último paso es configurar nuestro Tomcat. La manera más fácil es simplemente abrir el archivo "server.xml" localizado en:

<Tomcat Installation>\conf\server.xml

y agregar las siguientes líneas entre las etiquetas “<Host> </Host>”

<Context path="/HolaMundo" reloadable="false" docBase="E:\HolaMundo\WebContent" />

El atributo "path" es un path virtul que apunta a nuestro proyecto indicado por el atributo "docBase".

Si todo fue hecho correctamente lo único que resta es arrancar nuestro Tomcat. Es recomendado instalar un plugin a eclipse, "Tomcat Launcher Plugin", para facilitar las tareas de arrancar y parar el servidor.

Normalmente Tomcat corre en el puerto 8080:


viernes, 20 de noviembre de 2009

Propagando comillas en funciones de Javascript

Me encontré con un problema uno de estos días que representaba recibir en una función de Javascript un texto con comillas adecuadamente escapadas (uso de "\"). Ese mismo texto tenía que ser incluido dinámicamente como otro parámetro de otra función.


<html>
<head>
<script language="javascript">
function A(texto_escapado) {
var divContenido=document.getElementById("contenido");
divContenido.innerHTML = "<a href=\"javascript:B(\'" + texto_escapado +"\');\">clic aca!</a>";
alert(texto_escapado);
}

function B(texto_escapado) {
alert(texto_escapado);
}
</script>
</head>
<body>
<p><a href="javascript:A('\'Esto está escapado\'');">Paso 1</a></p>
<div id="contenido">...</div>
</body>
</html>


El problema que se genera es que a la hora de usar el texto para incluirlo en una concatenación, el texto escapado se pierde y se usa el carácter causando el siguiente
problema:


'Missing ) after argument list'





La solución que encontré es usar la función "replace" de una forma que parece muy particular:


texto_escapado = texto_escapado.replace(/\'/g, '\\\'');


Las dos primeras barras inclinadas ("slashes") son para escapar la primera barra en el texto y la última barra inclinada con la comilla es para escapar la comilla.
(\\="\") + (\'=') = \'


<html>
<head>
<script language="javascript">
function A(texto_escapado) {
var divContenido=document.getElementById("contenido");
alert(texto_escapado);
texto_escapado=texto_escapado.replace(/\'/g, '\\\'');
divContenido.innerHTML = "<a href=\"javascript:B(\'" + texto_escapado +"\');\">clic aca!</a>";
}

function B(texto_escapado) {
alert(texto_escapado);
}
</script>
</head>
<body>
<p><a href="javascript:A('\'Esto está escapado\'');">Paso 1</a></p>
<div id="contenido">...</div>
</body>
</html>


viernes, 6 de noviembre de 2009

7 tips básicos para surfear sobre tu código con Eclipse!

En este corto artículo enfocado a los "newbies" de Eclipse, voy a enseñar unos tips básicos del IDE de Eclipse que considero esenciales en grandes proyectos donde
necesitamos "surfear" en grandes "olas" de código.

1. Buscando por recursos

Olvidemonos de la manera anticuada de navegar a travez de la jerarquía de directorios o paquetes. Si conoces el nombre del recurso que andas buscando, o al menos una parte de él,
usa la opción de Eclipse para buscar un recurso (CTRL + SHIFT + “R”). Puedes usar "wild cards" en caso de que olvidaras el nombre completo de tu recurso.





2. Abre un tipo.

Deseas ir más allá de las fronteras de tus (*.java)'s locales? Si quieres abrir una clase contenida en un JAR (un .class), entonces utiliza el siguiente comando que te permite
ver la clase contenida en algún jar: (CTRL + SHIFT + “T”). Por supuesto que solo puedes abrir una clase contenida en un JAR si el .java esta contenido en el mismo JAR, o si
tienes configurado un decompilador de Java (fuera de nuestro foco en este artículo).



3. Apariciones de Clases y Objectos

A veces queremos ver a gran escala dónde hay apariciones de ciertas clases, variables u objetos. Simplemente haciendo doble clic sobre la clase u objeto el IDE marcará
todas las apariciones de la misma facilitandonos el trabajo en lugar de buscar línea por línea o usar el a veces incomodo comando de buscar.


4. Si dentro de una clase existe una referencia a otra clase, solo presiona la tecla CONTROL y has clic sobre la clase seleccionada. Esto te permitirá brincar sobre la
clase seleccionada.



5. Referencias

Quieres saber que tan popular es tu clase o método? Usa el abridor de referencias para descubrirlo. Selecciona tu clase o métod, clic derecho > References > [Workspace | Project | ...]


6. Jerarquía de llamados

Una larga línea entre tu método y su originador? No juegues de paleontólogo, usa la jerarquía de llamados para averiguarlo. Clic derecho en tu método > Open Call Hierarchy (or CTRL + SHIFT + “H”) y “voila”.





7. Búsqueda de Archivos

Cuando el texto que buscas está bastante escondido dentro de tu código, incluyendo XML, JavaScript, JSP, *, lo que necesitas es usar el buscador de archivos: CTRL + “H”


jueves, 17 de septiembre de 2009

Ciclo de Vida de JCS

En esta última parte acerca de JCS voy a explicar el ciclo de vida básico de un objeto guardado en cache y las diferentes clases que se ocupan para poder cumplir con cada tarea básica.

En el post anterior describí como establecer la configuración básica para tener nuestro cache listo para ser llenado con nuestros preciosos objetos.Un punto más que necesitamos dejar listo antes de comenzar nuestro ahorro de recursos es tomar en cuenta los manejadores de expiración o en inglés “Expiration Handlers”.

Un manejador de expiración es una clase encargada de ejecutar las respectivas acciones cuando un objeto termina su largo o corto periodo de vida (R.I.P.). Como creo que un código dice más que mil palabras, veamos un ejemplo.

Vamos a crear primero nuestra clase AbstractExpirationHandler la cual va ser extendida por nuestras clases de expiración “concreto”.

import java.util.HashSet;
import java.util.Set;

import org.apache.jcs.engine.CacheElement;
import org.apache.jcs.engine.control.event.ElementEvent;
import org.apache.jcs.engine.control.event.behavior.IElementEvent;
import org.apache.jcs.engine.control.event.behavior.IElementEventHandler;

public abstract class AbstractExpirationHandler implements IElementEventHandler {

private static final Set events = new HashSet();

{
events.add(ELEMENT_EVENT_EXCEEDED_IDLETIME_BACKGROUND);
events.add(ELEMENT_EVENT_EXCEEDED_IDLETIME_ONREQUEST);
events.add(ELEMENT_EVENT_EXCEEDED_MAXLIFE_BACKGROUND);
events.add(ELEMENT_EVENT_EXCEEDED_MAXLIFE_ONREQUEST);
}

public void handleElementEvent(IElementEvent elementEvent) {
ElementEvent event;
CacheElement element;

if (events.contains(elementEvent.getElementEvent())) {
event = (ElementEvent) elementEvent;
element = (CacheElement) event.getSource();
handleEvent(element);
}
}
/*To be implemented by concrete classes.*/
protected abstract void handleEvent(CacheElement element);
}

Nuestra clase concreto simplemente invocará las funciones necesarias para actualizar nuestro cache.

import org.apache.jcs.engine.CacheElement;

public class ConcreteExpirationHandler extends
AbstractExpirationHandler {


protected void handleEvent(CacheElement element) {
ConcreteKey key = (ConcreteKey) element.getKey();
/**
TODO: Update cache object identified by its key.
*/
}
}

La clase manejador concreto necesita ser asociada a una region especifica. Podemos hacer esto agregando el siguiente método a la clase CacheManager.

public static boolean addEventHandler(String region,
IElementEventHandler handler) {
boolean result = true;
JCS cache;
IElementAttributes attributes;
try {
cache = JCS.getInstance(region);
attributes = cache.getDefaultElementAttributes();
attributes.addElementEventHandler(handler);
cache.setDefaultElementAttributes(attributes);
} catch (CacheException ce) {
result = false;
}
return result;
}

Y usarla en neutro método init():

CacheManager.addEventHandler(“default”, new ConcreteExpirationHandler());

Si el lector está poniendo atención, habrá notado que se introdujo un nuevo concepto en el último pedazo de código, el elemento llave de cache (“Cache Key” en inglés). Como expliqué antes en la segunda parte de esta serie, un objeto de cache necesita ser identificado con una llave única. En un mundo simple, un objeto puede ser identificaso con una sola variable, probablemente un String, pero como vivimos en un mundo complejo, las llaves para nuestros objetos pueden ser un conjunto entero de pequeñas llaves, similar a una tabla de una base de datos que tiene una llave compuesta.

Así que para tener una llave necesitamos tener una clase con tres características básicas:
  1. Implemente la interface Serializabe
  2. Implemente equals()
  3. Implemente hascode()

import java.io.Serializable;


public class ConcreteKey implements Serializable {
private static final long serialVersionUID = 3599612490238955657L;

private String keyParam1;
private String keyParam2;

public String getKeyParam1() {
return keyParam1;
}

public void setKeyParam1(String keyParam1) {
this. keyParam1 = keyParam1;
}

public String getKeyParam2() {
return keyParam2;
}

public void setKeyParam1(String keyParam1) {
this. keyParam1 = keyParam1;
}

public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (this == obj) {
return true;
}
if (!(obj instanceof ConcreteKey)) {
return false;
}
ConcreteKey other = (ConcreteKey) obj;
boolean equals;
equals = (this.keyParam1.equals(other.keyParam1))
&& (this.keyParam2.equals(other.keyParam2));
return equals;
}

public int hashCode() {
StringBuilder builder = new StringBuilder();
builder.append(this.keyParam1).append(this.keyParam1);
String objectStr = builder.toString();
return objectStr.hashCode();
}
}

Ahora con nuestra llave podemos consultar y actualizar nuestro cache:

public class FooService {

public FooObject getFooObject(String keyParam1, String keyParam2) {
FooObject foo = null;
ConcreteKey key = new ConcreteKey();
key.setKeyParam1(keyParam1);
key.setKeyParam1(keyParam2);

JCS cache = JCS.getInstance("default");
foo = cache.get(key); // Query cache.

if (foo == null) { // Not available in cache right now?
// Get object from normal source.
foo = FooFactory.getFoo(keyParam1, keyParam2);
// Put it in cache to be available next time.
cache.put(key, foo);
}
}
}
Hasta aquí llegamos, terminé en este punto explicando los pasos básicos para configurar el cache, pero hay un montón de opciones más a explorar como auxiliares de cache, la consola de administración, etc. Si tienen alguna pregunta no duden en enviarme un correo o escribir un comentario en este blog.

lunes, 24 de agosto de 2009

Almacenamiento en caché de objetos Java: JCS y la inicialización de las regiones de cache

En este post voy a explicar cómo inicializar las regiones de JCS en el contexto de una aplicación web. Cualquier otro contexto como una aplicación de escritorio debería ser un caso más simple con el cual trabajar. (Para más detalles o documentación oficial es preferible visitar directamente el sition de Jakarta's)

Una vez que se han incluido todos los jars necesarion para correr JCS, necesitamos defininir cómo vamos a cargar las regiones del cache. Una region en JCS es un grupo de configuraciones que son aplicadas a cualquier objeto que guardado bajo esa región de cache. Por ejemplo en una región está definido el tiempo de expiración de un objeto, la cantidad máxima de objetos que se permite guardar, uso de un auxiliar de cache para reducir la carga de memoria, etc.
Podemos definir tantas regiones como requiramos, sin embargo es necesario pensar en un buen diseño de las regiones para establecer una buena categorarización de manera que en el futuro se nos facilite la monotirización de nuestro cache.

Por ejemplo, si definimos solamente una región para nuestra implementación de cache y decidimos guardar 5 clases distintas de objetos bajo una región, un tiempo después cuando estemos monitoreando la “proporción de éxito de cache” (“cache hit rate”), se nos resultará dificultoso determinar cuáles clases están contribuyendo en buen o mal “hit rate”.

En caso de que alguién no esté familiarizado con el concepto de CACHE HIT. Se refiere al evento en el cual hacemos una consulta al cache por un objeto en especifico y resulta que satisfactoriamente el objeto estaba guardado en cache. El caso contrario se le conoce como un “CACHE MISS” o “desacierto de cache”. Es siempre deseable para un buen rendimiento de la aplicación mantener una buena proporción de cache hits. El uso de cache implica un nuevo consumo de recursos como RAM (almacenamiento de objetos) y tiempo de CPU (cálculos para encontrar los objetos en los arreglos del cache) así que si solo vamos a obtener unos cuantos hits, podríamos estar gastando nuestros recursos innecesariamente.

Mi opinion personal, dada la experiencia que he tenifo usando JCS en la aplicación web con la que trabajo, es que es mejor definir las regiones acorde con las clases de objetos usados porque en nuestro caso guardamos objetos de cache provenientes de distintos servicios web. Es mejor saber cuáles clases “cachiables” están contribuyendo al mejoramiento del rendimiento total de la aplicación.

Un colega una vez sugerió que deberíamos definif las regiones acorde con los periodos de vida, por ejemplo:

* ShortLifeRegion
* MediumLifeRegion
* LongLifeRegion
* EternalLifeRegion

Este es otro tipo de categorización y realmente no hay una receta para determinar cuál es la mejor estratefia. Es una decisión técnica que tiene que ser tomada en cada caso particular.

El caso más comun para definir las regiones es trabajar con un archivo de propiedades y cargarlas una única vez.


import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.InvalidPropertiesFormatException;
import java.util.List;
import java.util.Properties;
import org.apache.jcs.JCS;
import org.apache.jcs.access.exception.CacheException;
import org.apache.jcs.engine.behavior.IElementAttributes;
import org.apache.jcs.engine.control.CompositeCacheManager;
import org.apache.jcs.engine.control.event.behavior.IElementEventHandler;

public class CacheManager {

public static void init(String cacheConfigPath) {

InputStream in = null;
CacheInitializerThread initializerThread =
new CacheInitializerThread();
try {
in = new FileInputStream(cacheConfigPath);
Properties prop = new Properties();
prop.load(in);
CompositeCacheManager ccm = CompositeCacheManager
.getUnconfiguredInstance();
ccm.configure(prop);

} catch (FileNotFoundException e) {
logger.logError(e);
} catch (InvalidPropertiesFormatException e) {
logger.logError(e);
} catch (IOException e) {
logger.logError(e);
}
finally {
try {
if (in != null) {
in.close();
in = null;
}
}
catch (Exception e) {}
}
}
...
}

Así que en nuestra aplicación podríamos llamar es métofo init en una clase Plugin.


public class CachePlugin extends Plugin {

private void init(ActionServlet servlet) {
String cacheConfigPath=
servlet.getServletContext().getRealPath(“cache.cff”);
CacheManager.init(cacheConfigPath);

}

}

Las siguientes propiedades definen una región llamada “medium”:

# medium live time refresh objects
jcs.region.medium=
jcs.region.medium.cacheattributes=org.apache.jcs.engine.CompositeCacheAttributes
jcs.region.medium.cacheattributes.MaxObjects=10000
jcs.region.medium.cacheattributes.MemoryCacheName=org.apache.jcs.engine.memory.lru.LRUMemoryCache
jcs.region.medium.cacheattributes.UseMemoryShrinker=true
jcs.region.medium.cacheattributes.ShrinkerIntervalSeconds=300
jcs.region.medium.cacheattributes.MaxMemoryIdleTimeSeconds=18000
jcs.region.medium.cacheattributes.MaxSpoolPerRun=100
jcs.region.medium.elementattributes=org.apache.jcs.engine.ElementAttributes
jcs.region.medium.elementattributes.IsEternal=false
jcs.region.medium.elementattributes.MaxLifeSeconds=18000


Cualquier objeto guardado en esta región expirará en 5 horas (MaxLifeSeconds=18000) y permitirá guardar solamente un máximo de 10000 objetos.

viernes, 14 de agosto de 2009

Muestra tu progreso a la hora de leer tus archivos

Perl nos permite muchas facilidades para procesar archivos y precisamente por eso lo he usado para examinarciertos logs de servidor cuando necesito saber
la causa de un problema. En una ocasión tuve que procesar varios cientos de megas de un solo archivo y dado que usaba varias expresiones regulares, tardaba un buen rato en terminar de leerlo todo.
Así que tuve la necesidad de saber cuánto llevaba procesado por el momento del archivo porque en ocasiones pensaba que se había quedado pegado el script, así que me puse la tarea de investigar como podía hacer esto y aquí dejo mi solución por si a alguien le sirve.


#!/usr/local/bin/perl

my $dir = $ARGV[0];

print "\n\n\t\tReading directory: " . $dir . " ...\n\n";
opendir(DIR, $dir);
my @files = readdir(DIR);
closedir(DIR);

foreach $file( @files){
unless ( ($file eq ".") || ($file eq "..") ) {
my $total_size_read = 0;
my $file_path = $dir . '\\' . $file;
my $filesize;
{
use strict;
use warnings;
$filesize = -s $file_path ;
$filesize = $filesize / 1000; #Kbytes
}

print "\nReading file " .$file_path . "(" . $filesize . " kb)\n";
open (CURRENT_FILE, $file_path) || die "couldn't open the file!";

while ($record = ) {

$line_number++;
if ($line_number % 100 eq 0) {
my $progress = sprintf("%.2f",(($total_size_read /1000) / $filesize) * 100);
print "\rTotal lines read: " . $line_number . " | Progress:" . $progress . "%";

}
{
use bytes;
my $byte_size = length($record);
$total_size_read = $total_size_read + $byte_size;
}
}

close(CURRENT_FILE);
}
}


  

jueves, 30 de julio de 2009

Almacenamiento en caché de objetos Java: Cache vs Pool

Abriendo un paréntesis, algunos pueden confundirse con un concepto similar pero a su vez diferente: Pool de Objetos. Ambos, "cacheo" y "pooleo" son usados para un mismo propósito general, mejorar el rendimiento al reducir el costo de crear ciertos objetos que consumen recursos vitales (tiempo, ancho de banda, memoria, etc).
La diferencia principal es que el pool de objectos no requiere "unicidad" de los objetos que son almacenados. En otras palabras, para cierta necesidad, cualquier objeto guardado en el pool puede hacer el trabajo. De esta manera el pool puede balancear las solicitudes para cierto objeto al mantener varias instancias para devolverlos de manera inmediata. Cache por contraste requiere que un objeto sea mapeable, esto es que pueda ser identificado por una llave única (esta llave puede ser una llave compuesta como veremos después en los siguientes posts). Para este caso, cualquier objeto no puede hacer el trabajo, se necesita un objeto especifico y debemos poder tener la facultad de contruit la llave para el almacenamiento y recuperación del objeto.

Para ejemplificar esto podemos pensar en una aplicación hipotética que trabaje con instancias de una clase llamada "Persona". Supongamos que un proceso de negocio requiere obtener una persona, cualquier persona de manera aleatoria. Debido a que el proceso para construir a la persona requiere de varios llamados a una base de datos, un pool es implementado de manera que cierto número de instancias de la clase Persona son mantenidas in memoria local. La aplicación por tanto haría un llamado similar a esto:



PersonaPool.solicitarPersona();



Ahora si otro proceso requiere obtener una persona en especifico identificable por un ID, requieriendo tambi[en hacer una serie de llamados a la base de datos para la construcci[on del objeto, un diseño de cache seria necesario para evitar el alto costo de realizar todos estos llamados a la base de datos. La aplicación por tanto haría un llamado como este:



PersonaCache.obtenerPersona('125677');



El pool de objetos es una técnica usada ampliamente en aplicaciones que requieren abrir conexiones con una base de datos a traves de objetos de conexión. No obstante debemos tener siempre la mente abierta para aprovechar cualquier buena oportunidad para usarla en nuestras aplicaciones.

martes, 30 de junio de 2009

Almacenamiento en caché de objetos Java: Introducción


Si usted va a una tienda en busca de algo de ropa, digamos un par de pantalones y tres camisetas, probablemente al igual que el resto de nosotros usted hará lo siguiente: camina alrededor, toma algunas piezas que le gusta y las lleva al cambiador. Usted no puede tomar todo el departamento consigo a esta pequeña habitación de modo que tiene que elegir cuidadosamente las prendas que más llaman su atención. Una vez que pruebe algunas piezas, se quedará con algunas y descartará otras, así que tendrá que repetir el proceso el número de veces que sea necesario para conseguir todo lo que anda buscando.

Y ahora, ¿qué ocurre si en lugar de tomar todas las piezas que se permite al cambiador, usted toma sólo una pieza a la vez. Es probable que dure tres o cuatro veces más, sin mencionar la curiosidad de la persona que administra el cambiador. Esta actividad de sentido común: aumentar al máximo la cantidad de ropa al alcance de la mano a la hore de probarse la ropa, es a veces no tan evidente para algunos desarrolladores de software . Tengo que admitir que no era tan evidente para mi tampco.

El concepto es bien concocido no obstante, CACHE. La primera vez que escuché la palabra cache fue cuando tuve mi primer PC. El vendedor muy orgulloso con los tecnisismos de su vocabulario, mencionaba que el micro procesador tenía ~ ~ k bytes de memoria caché. Siendo yo uno adolescente todo eso me sonaba a chino, aunque me dio la sensación de que caché era algo super importante.

Unos pocos años pasaron y aterricé en un curso de Arquitectura de Computadores en la universidad en el que se me explicó la importancia de una buena estrategia de caché para el buen desempeño de un micro-procesador. Incluso tuvimos que programar un emulador de caché así como un administrador de la jerarquía de memoria (cómo gestionar los datos que se asignan a los diferentes niveles de memoria: registros, caché, RAM, disco). Sin embargo, parece que mis neuronas no estaban muy productivas ese semestre porque no hicieron una conexión con mis neuronas de arquitectura de software.

No es como si hubiera un problema de rendimiento antes en mis aplicaciones, sólo fue un tipo de nuevo descubrimiento al averiguar acerca de la existencia de sistemas Java especializados en almacenamiento de objetos en caché En el próximo post voy a seguir describiendo el sistema que he estado usando por un tiempo: Jakarta: JCS (“Java Caching System”) y por qué considero que es una necesidad en aplicaciones distribuidas.

lunes, 15 de junio de 2009

Ordenamiento de mapas

Hace un tiempo me tocó buscar como forzar el orden FIFO ("First in First out") en un mapa. Cuando usaba un HashMap siempre obtenía un orden aleatorio y el TreeMap no me servía por su ordenamiento alfabético. Para estos casos existe la clase LinkedHashMap. Veamos el comportamiento para cada uno de los mapas mencionados.

HashMap



import java.util.HashMap;

public class Mapas{

public static void main(String[] args) {
Map<string, Integer> promedios = new HashMap<string, Integer>();
promedios.put("Blanca", 95);
promedios.put("Gabriel", 90);
promedios.put("Daniela", 85);
promedios.put("Juana", 70);
promedios.put("Alejandro", 50);

for (Iterator<string> keys=promedios.keySet().iterator(); keys.hasNext();) {
String estudiante = keys.next();
int promedio = promedios.get(estudiante);
System.out.println(estudiante + ": " + promedio);
}
}
}
Salida:

Blanca: 95
Juana: 70
Gabriel: 90
Alejandro: 50
Daniela: 85



TreeMap


import java.util.TreeMap;

public class Mapas{

public static void main(String[] args) {
Map<string, Integer> promedios = new TreeMap<string, Integer>();
promedios.put("Blanca", 95);
promedios.put("Gabriel", 90);
promedios.put("Daniela", 85);
promedios.put("Juana", 70);
promedios.put("Alejandro", 50);

for (Iterator<string> keys=promedios.keySet().iterator(); keys.hasNext();) {
String estudiante = keys.next();
int promedio = promedios.get(estudiante);
System.out.println(estudiante + ": " + promedio);
}
}
}
Salida:

Alejandro: 50
Blanca: 95
Daniela: 85
Gabriel: 90
Juana: 70


LinkedHashMap

import java.util.LinkedHashMap;

public class Mapas{

public static void main(String[] args) {
Map<string, Integer> promedios = new LinkedHashMap<string, Integer>();
promedios.put("Blanca", 95);
promedios.put("Gabriel", 90);
promedios.put("Daniela", 85);
promedios.put("Juana", 70);
promedios.put("Alejandro", 50);

for (Iterator<string> keys=promedios.keySet().iterator(); keys.hasNext();) {
String estudiante = keys.next();
int promedio = promedios.get(estudiante);
System.out.println(estudiante + ": " + promedio);
}
}
}

Salida:

Blanca: 95
Gabriel: 90
Daniela: 85
Juana: 70
Alejandro: 50

martes, 2 de junio de 2009

Resaltador de Sintáxis para tu código


Recientemente implementé un resaltador de sintáxis en este blog para que el código que publicara fuera fácilmente leíble por cualquiera. Anteriormente intentaba yo darle formato usando las herramientas de formato de blogger pero me di cuenta que perdía demasiado tiempo en esta tarea y al final el código seguía luciendo mal.
Así que un día de tantos mientras leía un blog, ví un comentario de un lector que sugería usar un "Sintax Highlighter", vaya como cuesta a veces buscar algo en Google cuando no tienes la frase correcta. Ahí estaba la solución para mi código plano y sin color.

De esta manera aterricé al proyecto: "Sintax Highlighter" que hace justamente lo que necesitaba.

Implementación:

Para usar el resaltador necesitas descargar el proyecto del sitio que incluye todos los javascripts y CSS's que debes subir a tu servidor para referenciarlos en tu código.

Para los pobres o tacaños como su servidor que no hemos adquirido los servicios de un hosting o demasiado vagos para buscar uno gratuito, también se puede referenciarlos directamente del proyecto como lo hago yo en este blog:


<link href='http://syntaxhighlighter.googlecode.com/../SyntaxHighlighter.css' rel='stylesheet' type='text/css'/>
<script language='javascript' src='http://syntaxhighlighter.googlecode.com/../shCore.js'/>
<script language='javascript' src='http://syntaxhighlighter.googlecode.com/../shBrushCSharp.js'/>
<script language='javascript' src='http://syntaxhighlighter.googlecode.com/../shBrushXml.js'/>
<script language='javascript' src='http://syntaxhighlighter.googlecode.com/..s/shBrushPython.js'/>
<script language='javascript' src='http://syntaxhighlighter.googlecode.com/../shBrushJScript.js'/>
<script language='javascript' src='http://syntaxhighlighter.googlecode.com/../shBrushPhp.js'/>

Claro está que esta referencia hace un poco más lenta la carga de la página y no garantiza que los archivos referenciados estarán siempre en el servidor del proyecto. Lo recomendable es subirlo en tu propio servidor.

Para activar el resaltador en tu página debes de correr el siguiente código inmediatamente después de la etiqueta <body>:


<script language='javascript'>
dp.SyntaxHighlighter.BloggerMode();
dp.SyntaxHighlighter.HighlightAll('code');
</script>
</html>
La primera línea indica al resaltador que mi página es un blog (necesario para adecuar el formato correctamente). Después se invoca al resaltador con la función HighlightAll.

Cómo usarlo?


Para resaltar un código simplemente abres una etiqueta "pre" y le asignas al atributo "name" el valor "code" y al atributo "class" el nombre del lenguaje que estás utilizando:
  • javascript
  • html
  • java
  • c-sharp
<pre name="code" class="html">
Put html code here!
<pre>

Ejemplos

HTML


<html>
<body>
<head>
</head>
Body Code here!
</body>
</html>


Javascript


function pow(int x) {
return x * x;
}


Java (Collapsed)


public class Hello {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}

martes, 26 de mayo de 2009

Partiendo el "pegado" o "paste" en diferentes campos

Hace unos días se me asignó una tarea para corregir un fallo de validación de un formulario donde el usuario intenta pegar un número telefónico completo en el campo que solo recibe el código de área. El código ya restringía el campo a solo tres carácteres al ser digitados, y cuando se llegaba a este límite el cursor era puesto al siguiente campo del número telefónico. No obstante la validación solo funcionaba con el evento donde se teclea un caracter. Quize agregarle un plus además para que partiera todo el número telefónico en los tres campos del teléfono con solo pegar el número desde el código de área. Esto se asemeja a lo que hacen algunos paquetes de instalación de software donde el código serial del producto se tiene que digitar en varios campos, pero que permite hacer copy/paste si se tiene el código en un archivo de texto en una sola tira de caracteres.

Me puse a investigar como obtener el valor del "clipboard", es decir, el texto resultante de ejecutar un "copiar" o "copy" en el navegador. Como es usual en la diversidad de la web, para Internet Explorer existe un objeto "clipboardData" que hace esto mismo y más pero que no es compatible con otros navegadores como FireFox:

window.clipboardData.setData('Text', data);
window.clipboardData.getData('Text', data);


De hecho hay un debate existencial en si se debería permitir este libre acceso al clipboard por cuestiones de seguridad.

Surfeando por la web encontré una solución diferente que funciona en todos los navegadores.

La receta incluye los siguientes ingredientes:

+ evento: onPaste

+ window.setTimeout

+ Función para evaluar si un carácter es un digito (opcional al gusto)


function isDigit(num) {

var string="1234567890";
if (string.indexOf(num)!=-1){
return true;
}
return false;
}

+ Lógica para recorrer cada uno de los caracteres de la tira

Preparación:

+ Crear una función que será llamada en el evento onPaste que lo único que hace es establecer un timeout con la función que partira la tira
en los demás campos.


function managePhonePaste() {
window.setTimeout("checkPastedValue();", 10);
}


+ Programe la lógica para partir la tira y distribuirla en los demas campos. Opcionalmente puede agregar lógica para establecer donde debería quedar el cursor al final de la operación.


function checkPastedValue() {
var pastedValue = $('areacode').value;
var areaCode='';
var phone1='';
var phone2='';

for(i=0; i <= pastedValue.length; i++) {
if (isDigit(pastedValue.charAt(i))) {
if (areaCode.length < 3) {
areaCode += pastedValue.charAt(i);
} else if(phone1.length < 3) {
phone1 += pastedValue.charAt(i);
} else if (phone2.length < 4) {
phone2 += pastedValue.charAt(i);
}
}
}
if (areaCode.length > 0) {
$('areacode').value = areaCode;
$('areacode').focus();
}
if (phone1.length > 0) {
$('phone1').value = phone1;
$('phone1').focus();
}
if (phone2.length > 0) {
$('phone2').value = phone2;
$('phone2').focus();
}
}


+ Agregue la función al campo de código de área para el evento onPaste():


<input id="areacode" name="areaCode" value="" type="text" onPaste="managePhonePaste();">
<input id="phone1" name="phone1" value="" type="text">
<input id="phone2" name="phone2" value="" type="text">



Cómo funciona?

Lo que estamos haciendo es dejar que la tira se pegue en el campo del código de área aunque exceda el límite establecido, pero al establecer un timeout tan corto para manejar el valor que se pega y repartirlo apropiadamente en los demás, se tiene la impresión de que todo se hace de manera inmediata, lo cual es cierto para el ojo humano.