30 noviembre, 2011

Respaldo rápido de un disco duro en UNIX con dd

dd es un comando muy potente que realiza copias bit-a-bit de un archivo a otro. Gracias a que en UNIX, todos los dispositivos son un archivo, podemos aprovechar esta característica para hacer copias de seguridad.

El uso básico de dd es:

$dd if={archivo_de_entrada} of={archivo_de_salida} bs={tamaño_bloque}

El tamaño de bloque indica más o menos la cantidad de bytes que se copiaran a la vez. Un tamaño de bloques pequeño permite una mayor tolerancia a fallos en el origen, mientras que un tamaño más grande permite una mayor velocidad.

Por ejemplo, una copia de disco a disco podría ser:

$dd if=/dev/hda of=/dev/sda bs=4096

También es posible usar un archivo de imagen:


$dd if=/dev/hda of=./imagen.img bs=4096 conv=noerror

El parametro conv=noeeror indica que continúe si se presenta un error de lectura.
Especificamos un tamaño de bloque de 4k para hacerlo más tolerante a errores de lectura.

Otra forma es utilizarlo en conjunto con gzip para hacer copias de respaldo comprimidas:

$dd if=/dev/hda conv=noerror bs=4k | gzip -c > ./imagen.img.gz


O con gunzip para restaurar las imágenes de respaldo

$gunzip -z ./imagen.img.gz | dd of=/dev/hda bs=4k conv=noerror


Si se quiere monitorear el estado de copia, se le puede enviar la señal USR1 al PID del comando. 

$kill -USR1 {PID_DE_dd}

Obviamente hay muchas más opciones, que pueden ser investigadas en el manual de dd


$man dd


30 agosto, 2011

Recorriendo Vectores gigantes con multiples hilos

Hay momentos en la vida en que inebitablemente nos enfrentamos a la busqueda de un valor dentro de una coleccion gigante. Hay veces en que el uso de un contains(Object o) aplicado a una Collection puede ser suficiente, pero hay ocasiones en las que incluso esto no es bastante.
Hace poco me vi enfrentado a un problema en que dado un dato y un gran vector, debía aplicar un algoritmo de comparación especial que me impedía recurrir al contains().
El problema fundamental en este caso era que la comparación no se hacía directamente con un metodo equals() o un == sino que ambos datos debían ser entregados a un tercer objeto que se encargaba de la verificación, por lo que me vi enfrentado a un punto en el que mi unica vía era iterar uno a uno los elementos del vector.
En las pruebas de desarrollo el modelo funcionaba perfectamente, pero al ponerlo en producción comenzó a mostrar falencias en el rendimiento. Pese a que el algoritmo de verificación es eficiente, el ciclo for utilizado podía ser realmente demoroso.
Lugo de pensarlo un poco, llegué a una solución bastante aceptable, aprovechando las características multi hilo de Java.
La solución en si es simple: divide y vencerás. Corté el vector en N trozos de pequeño tamaño e inmediatamente lanzo N hilos de verificación simultánea que realizán la búsqueda. El primero que encuentre el dato grita "gané" y detiene a los demas hilos.


Visto paso a paso:


  1. Establezco un tamaño de bloque. Por ejemplo 100 unidades. Cada hilo hará la búsqueda sobre este número de datos.
  2. Divido el tamaño de mi vector en el tamaño del bloque. Si el resultado es menor a uno, significa que mi vector es menor al bloque, por lo que el resultado debe ser 1.
  3. Preparo un espacio para que cada hilo notifique que ha terminado sin encontrar resultados.
  4. Preparo un boolean para indicar que el dato fue encontrado.
  5. Lanzo hilos de busqueda.
  6. Espero...

La implementacion mas simple de esta algoritmo, podria hacerse asi:

public class Buscador  {

    private static final int TAM_BLOQUE = 100; //Tamaño del bloque
    private static Logger logger = Logger.getLogger(Buscador.class.getName()); 
    private boolean encontrado;                //Indica que el dato fue encontrado
    private List datos;                  //Donde se hara la busqueda
    private Map flags;       //Donde cada hilo indica que termino
    private Resultado resultado;               //El dato que fue encontrado 

    public  Buscador(List datos) {
         this.datos = datos;
     }

    /**
     * Este metodo debe ser lanzado desde un hilo, no desde el event-dispatch 
     * thread ya que si no bloqueara la GUI
     *
     */ 
    public Resultado buscar(Muestra muestra) {
        if (datos == null || datos.isEmpty()) {
            return null;
        }
        //Sincronizo los datos para buscar desde muchos hilos 
        List vector = Collections.synchronizedList(datos);
        int bloques = datos.size() / TAM_BLOQUES;
        if (bloques < 1) {
            bloques = 1;
        }
        flags = Collections.synchronizedMap(new HashMap(bloques)); 
        final int largoSegmento = t / bloques;
        encontrado = false;

        resultado = null;
        /*
         * Este es el punto donde se lanzan los hilos de busquedas. Aprovechamos 
         * las caractristicas de las clases anonimas y el metodo sleep() de la 
         * clase Thread. Por eso este metodo debe lanzarse desde un hilo en 
         * segundo plano. 
         */ 
         for (int hilo = 1; hilo <= bloques; hilo++) {
             final int nHilo = hilo;  //Lo hacemos final para usarlo en la clase anonima
             flags.put(nHilo, false); //El hilo aun no termina
             Runnable busqueda = new Runnable() {

                 public void run() {
                     //Desde donde y hasta donde debe buscar este hilo 
                     int desde = desde = (nHilo - 1) * largoSegmento;
                     int hasta = largoSegmento * nHilo;

                     /*
                      * Instancia del verificador. Es propia de cada clase,  
                      * para asi evitar colisiones entre los hilos
                      */
                      Verificador v = Verificador.newInstance();
                      for (int k = desde; k < hasta; k++) {
                          if (encontrado) {
                              logger.debug("Hilo " + nHilo + ": Otro hilo encontro el registro :(");
                              marcarfin();
                              return;
                          }

                          Object dato = vector.get(k);
                          //Realiza la verificacion. . .. bla bla bla 
                          v.verificar(dato, muestra); 
                          if (v.esValido()) {
                              logger.debug("Hilo " + nHilo + ": Encontre el registro! :D");
                              encontrado = true;

                              resultado = v.getResultado();
                              marcarfin();
                              return; 

                          } 
                      } 
                      logger.debug("Hilo " + nHilo + ": Finalizo sin encontrar registro :(");
                      marcarfin(); 

                 }
                 
                 /**
                  * Coloca en el mapa la marca de que este hilo ya temino.
                  */ 
                 private void marcarfin() {
                     flags.put(nHilo, true);
                 } 

             };
             new Thread(busqueda, "Hilo de busqueda " + hilo).start();
         } 
         while (!encontrado && !finalizado()) {
             try {
                 Thread.sleep(500L);
             } catch (InterruptedException ex) {
                 //algo con la excepcion
             }
          }
         return resultado; 

    } 


    /**
     * Verifica que todos los hilos hayan finalizado. 
     */ 
    private boolean finalizado() {
        for (boolean hiloFinalizado : flags.values()) {
            if (!hiloFinalizado) {
                return false;
            }
        }
        return true;
    } 





 A simple vista puede verse algo confuso, pero si examina con detencion, puede verse que el truco se encuentra dentro del ciclo for. Aqui se lanzan tantos hilos como deban ser lanzados. Cada hilo comparte algunos atributos con los demas hilos: Un mapa para grabar su estado, la variable donde se graba el resultado, un boolean para indicar que el dato fue encontrado y un vector sincronizado que es copia del original.

Cada hilo realiza la búsqueda en su espacio asignado dentro del gran vector. Asi, por ejemplo si nuestro vector tiene 1000 unidades y cada bloque es de 100, el primer hilo hará la busqueda desde el 0 al 99, el segundo desde el 100 al 199, el tercero desde el 200 al 299 y asi sucesivamente.

Obviamente los valores aqui expresados son para ejemplo, quiza puedan modificarse los valores para ajustarse a un escenario más real. La clase Verificador y sus metodos son ficiticias y la idea es que sea reemplazado por lo que se necesite. Del mismo modo puede optimizarse el uso de Generics en el buscador y hacer otras mejoras como eliminar el boolean "encontrado".

También puede limitarse el número de hilos lanzados aplicando un mecanismo de pausa y espera  para el control de los hilos y varias mejoras más que se me van ocurriendo.

Actualmente esta implementación está funcionando perfectamente y mis usuarios no han vuelto a quejarse por lentitud.

=) 

26 julio, 2011

Reflexión

Un buen programador no hace la pega.... hace un programa que haga la pega por él . . . .

Controlar JSpinner con la rueda del mouse

Una caracteristica de una buena aplicacion con interfaz gráfica es la capacidad de responder a eventos que el usuario supone debe realizar para una determinada acción.

Me explico: Si un usuario ve un botón, la acción natural es hacer clic en el, pero también sería válido hacer que el botón responda a la tecla 'enter'.

Del mismo modo, en los JSpinner la acción natural es utilizar las flechitas y hacer clic en ellas para modificar el valor, pero si el mouse tiene rueda probablemente el usuario girará la rueda esperando modificar el valor del JSpinner.

Para que esto ocurra, se debe agregar al JSpinner un listener que implemente la interfaz MouseWheelListener. Java tiene por defecto una implementación vacía llamada MouseAdapter que simplifica la creación de listener ya que debe sobrecargarse sólo el método que necesitamos.

Manos a la obra: Una vez instanciado nuestro JSpinner agregamos un MouseAdapter con un código similar a éste:

JSpinner spinner = new JSpinner();
SpinnerModel modelo = new SpinnerNumberModel(0, 0, 100, 1);
spinner.setModel(modelo);
spinner.addMouseWheelListener(new MouseAdapter() {


    @Override
    public void mouseWheelMoved(MouseWheelEvent e) {
        try {
            Object mov = null;
            if (e.getWheelRotation() == 1) {
                //la rueda baja
                mov = modelo.getPreviousValue();
            } else if (e.getWheelRotation() == -1) {
                //la rueda sube
                mov = modelo.getNextValue();
            }
            if (mov == null) {
                return;
            }
            modelo.setValue(mov);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
});


Como puede observarse, se agrega una clase anónima que extiende a MouseAdapter y sobrecargamos el método mouseWheelMoved(). En este método leemos el evento y determinamos la dirección en que se giró la rueda. Para ello invocamos el método getWheelRotation() que retornará un entero: -1 si la rueda "sube" y 1 si la rueda "baja".

Luego, sabiendo la direccion en que el usuario giró la rueda, le solicitamos al modelo del JSpinner el valor siguiente o anterior según el caso. Cuando se ha llegado al tope de valores del modelo (0 o 100 en el ejemplo) el modelo retorna null.

Teniendo el valor que debemos aplicar, se lo entregamos al modelo. Fin del problema.

Se pueden hacer varias mejoras al ejemplo (por ejemplo usar un switch en lugar de un if, pero eso queda a gusto del programador)