Viendo videos de Youzee en Linux

Youzee LogoPara los despistados, Youzee es una nueva plataforma web (creada por iniciativa española, aun en fase beta) para la visualización de películas y series de forma extremadamente cómoda. Eso sí, pagando una cuota de unos 7 € mensuales. A mi parecer, el coste puede valer la pena, el catálogo es grande (y sigue creciendo), la calidad de streaming es alta y los films y series están disponibles en varios idiomas.

Aunque no es la única plataforma de este estilo (tenemos por ejemplo a Hulu, Netflix, que desgraciadamente no aterrizará en nuestro país, u otras iniciativas españolas como Wuaki.tv) tengo que admitir que hasta el momento es la que más me ha gustado... salvo por un detalle: Hubo problemas con los monitores VGA, y en especial con Linux (pero tengo la sensación de que lo mismo debe pasar con las otras plataformas estadounidenses).

Por suerte los problemas iniciales se han ido solucionando, y el de Linux se puede sortear con un pequeño apaño. Bastará con instalar el paquete hal con la herramienta aptitude o apt-get (o synaptic, o la que prefiráis vosotros).

Hay un detalle que conviene comentar para los usuarios de Ubuntu. Parece que la forma en que se instala el plugin Adobe Flash puede hacer que la cosa funcione o no. En particular recomiendo instalar el paquete adobe-flashplugin en vez del paquete flashplugin-installer. Además es conveniente señalar que la herramienta aptitude puede ser problemática para instalar el paquete adobe-flashplugin, por lo que es preferible usar apt-get.

Referencias

  1. http://blog.guiloma.es/2012/01/usando-youzee-en-linuxdebianubuntu.html

Mejorar eficiencia en peticiones AJAX

JSON logoLa mayoría de desarrolladores de aplicaciones web desean que éstas sean usadas por miles de personas (y si es a la vez, mejor). Cuando se llega a un determinado número de usuarios, es difícil que la aplicación aguante esa carga si no se han tomado ciertas decisiones. La más simple (y probablemente más cara) pasa por añadir más prestaciones a los servidores con los que trabaja nuestra app, pero evidentemente podemos encontrar soluciones ligeramente mejores.

Hoy en día es extraño encontrar una aplicación web que no haga uso de AJAX, unas lo hacen de manera inteligente y otras no tanto. Vamos a ver qué podemos hacer para reducir un poco(en caso de no haberlo hecho ya) la carga que deben soportar nuestros servidores.

El consejo básico es simple: es preferible que el contenido dinámico sea generado en el lado del cliente con Javascript a que el trabajo lo hagan nuestras máquinas. Es claro que no siempre se podrá hacer eso, pero por lo general sí, como ejemplo tenemos datos que se actualizan periódicamente o que se estructuran de forma tabular o en listados.

La justificación del consejo radica en 2 puntos, en primer lugar podemos reducir el tiempo de CPU en el servidor consumido por petición, en segundo lugar podemos reducir el ancho de banda usado si enviamos datos como JSON en vez de contenido HTML pregenerado. Así dicho suena creíble, pero tenemos que comprobarlo (me fijaré en el caso concreto de PHP).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
 
// Codigo que sirve petición con JSON
$link = mysql_connect('xxx', 'xxx', 'xxx');
 
mysql_select_db('xxx');
 
$dbresult = mysql_query('SELECT nick, passwd, name FROM json');
 
$outresult = array();
 
while ($outresult[] = mysql_fetch_row($dbresult));
array_pop($outresult);
 
echo json_encode($outresult);
 
mysql_close($link);
 
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
 
// Codigo que sirve la petición con HTML
 
$link = mysql_connect('xxx', 'xxx', 'xxx');
 
mysql_select_db('xxx');
 
$dbresult = mysql_query('SELECT nick, passwd, name FROM json');
 
while ($row = mysql_fetch_row($dbresult)) {
    echo '<tr><td>',$row[0],'</td><td>',$row[1],'</td><td>',$row[2],'</td></tr>';
}
 
mysql_close($link);
 
?>

Antes de hablar sobre como se procesa la petición en el lado del cliente haré algunas observaciones. Aunque en este código no está, añadí código para medir cuantos milisegundos tardaba cada script en ejecutarse. Para aumentar la fiabilidad de las pruebas conté solo el tiempo que se tarda en procesar el resultado de la consulta a la base de datos y enviarla. Así pues excluyo el acceso a la base de datos y el cierre de la conexión de las medidas efectuadas, que son las siguientes (había 200 registros en la base de datos):

  • Petición servida en JSON (10 muestras):
    • Número de bytes enviados: 18830
    • Pico de memoria consumida: 305128 bytes ~ 297.976 Kb
    • Tiempo mínimo: 0.62799 ms
    • Tiempo máximo: 1.2610 ms
    • Tiempo medio: 0.9064 ms
    • Desviación típica: 0.1436 ms
  • Petición servida en HTML (10 muestras):
    • Número de bytes enviados: 23854
    • Pico de memoria consumida: 100008 bytes ~ 97.664 Kb
    • Tiempo mínimo: 0.7529 ms
    • Tiempo máximo: 2.4579 ms
    • Tiempo medio: 1.2909 ms
    • Desviación típica: 0.4956 ms

Lo más destacable de estas medidas es el número de bytes enviados, con JSON podemos ahorrar (como mínimo) un 21.06% de ancho de banda. Digo como mínimo porque en este ejemplo el código HTML generado es más bien austero, ya que no incorpora clases CSS ni identificadores para facilitar el manejo del DOM.

El segundo dato nos muestra la cantidad máxima de memoria consumida durante la petición procesada. Como era esperable, la primera versión del código consume más memoria debido al array que va construyendo y a la cadena JSON que ensambla finalmente a partir de la lista generada. En este punto hay que tener en cuenta que, si somos listos, esto no tiene por qué suponer un gran problema ya que ese mismo array podríamos (y de hecho es recomendable) guardarlo usando (por ejemplo) Memcached de forma que ahorramos posteriores consultas a la base de datos.

Respecto a los otros datos tengo que decir que no son significativos en lo tocante al asunto JSON vs HTML, pues la comparativa no es del todo justa. Sin embargo, lo que sí podemos ver es qué supone usar repetidas veces la primitiva echo frente a un solo uso. Por un lado aumentan los tiempos de ejecución, y por otro aumenta la dispersión de las medidas (ver desviación típica), lo que puede tocar un poco las narices cuando queremos hacer cálculos de cara a escalar nuestra infraestructura.

Otra versión del código que genera HTML es la siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
 
$link = mysql_connect('xxx', 'xxx', 'xxx');
 
mysql_select_db('xxx');
 
$dbresult = mysql_query('SELECT nick, passwd, name FROM json');
 
$outresult = '';
 
while ($row = mysql_fetch_row($dbresult)) {
    $outresult .= '<tr><td>'.$row[0].'</td><td>'.$row[1].'</td><td>'.$row[2].'</td></tr>';
}
 
echo $outresult;
 
mysql_close($link);
 
?>

Los datos que arroja esta versión son los siguientes:

  • Petición servida en HTML (2ª versión, 10 muestras):
    • Número de bytes enviados: 23854
    • Pico de memoria consumida: 126432 bytes ~ 123.469 Kb
    • Tiempo mínimo: 0.3531 ms
    • Tiempo máximo: 0.8111 ms
    • Tiempo medio:  0.5435 ms
    • Desviación típica: 0.1577 ms

Podemos extraer algunas conclusiones más de esta tercera lista de cifras. En primer lugar se nos podría ocurrir adoptar el mismo enfoque para la generación de JSON, es decir, olvidarnos de construir un array y pasar a construir un string directamente (de hecho obtendríamos mejores resultados, pues la cadena JSON es más corta que la cadena HTML). Si no vamos a usar más esos datos en un lapso de tiempo breve parece lo más acertado, pero si tenemos planeado acceder a ellos otra vez puede que sí nos convenga construir la lista como en el primer ejemplo de código.

Un detalle interesante es que teniendo un array probablemente sea más rápido generar un texto JSON con la función json_encode que concatenando strings en un bucle. El problema que tenemos (en este caso) es que el driver nativo de MySQL para PHP no permite obtener una lista con todas las filas directamente, y consecuentemente nos vemos forzados a construirla nosotros (que es menos eficiente que usar un método nativo de PHP programado en C, y de ahí que el tercer ejemplo rinda mejor que el segundo).

Tengo que admitir que no conozco por qué no hay un método nativo de PHP para obtener en forma de array todos los resultados de una consulta a una base de datos MySQL, pero creo que, en caso de no haber razones de peso... puede ser una buena jugada implementar en C una extensión del lenguaje que haga precisamente eso para ahorrar un valioso tiempo de ejecución.

En el lado del cliente las diferencias no han sido estadísticamente significativas para mi ejemplo, aunque es de esperar que los tiempos de descarga sean menores con JSON, mientras que los de proceso sean menores con HTML. El tiempo total dependerá de la potencia de cómputo del cliente y del ancho de banda de la conexión, por lo que algunos usuarios saldrán beneficiados mientras que otros puede que tarden algunos milisegundos más en obtener la página web completamente renderizada.

Sin más rodeos, resumen rápido: JSON permite reducir el ancho de banda consumido en más de un 21%, hacer un solo echo reduce el tiempo de ejecución (la reducción puede sobrepasar fácilmente el 50%, en el caso del ejemplo es un 57.89%) y la dispersión estadística de esos mismos tiempos (lo que nos puede ayudar a realizar mejores cálculos para escalar la plataforma o puede servir para que los balanceadores de carga tengan datos más fiables con los que trabajar).

Como apunte final solo quiero destacar que es importante realizar experimentos y comprobar los hechos por uno mismo, pues la tecnología evoluciona constantemente y es probable que ciertas afirmaciones no se puedan sostener en el tiempo. Además me he dejado algunas técnicas en el tintero, como usar los métodos ob_start y ob_end_clean, que nos permiten aprovechar el buffer de escritura para evitar la necesidad de concatenaciones. Supongo que viendo la extensión del artículo comprenderéis que no lo haya comentado en profundidad. Espero que os haya sido útil :) .

Como instalar escáner USB en Linux

Últimamente Linux ha dado un salto cualitativo en cuanto el soporte que presenta para multitud de dispositivos, incluso es frecuente que todo funcione directamente después de la instalación, sin dar un paso más (cosa que no es tan usual en Windows, donde rara es la vez que no hemos de instalar algún driver).

Sin embargo, como ya apunté, de vez en cuando sucede que hay problemas, y entonces pueden ser bastante gordos. Ayer mismo tuve que ayudar a mi hermano a configurar un escáner (a distancia, yo daba indicaciones), y puedo asegurar que el proceso fue bastante "doloroso" para él. Este tipo de cosas no se le pueden hacer a un newbie. Es por eso que escribo este artículo, para intentar dar unas instrucciones un poco más masticadas que las que encontré y facilitar un poco las cosas a todos aquellos que no saben inglés.

Empecemos, el modelo de escáner de (la novia de) mi hermano es un Packard Bell Diamond 1200 Plus (las instrucciones serán aptas para muchos más modelos) y no está soportado por defecto en Ubuntu 11.10 (así que imaginad el soporte que debe tener en otras distribuciones).

  1. El primer paso que deberéis realizar es buscar si vuestro dispositivo está soportado por el proyecto SANE (en la página [2] que enlazo en las referencias). Podréis ver que hay un código de colores que indica qué nivel de soporte tiene el dispositivo. Complete, good o basic son un buen indicativo, cualquier otra cosa no es buena señal.
  2. Deberemos descargar el fichero de firmware acorde a nuestro escáner. En [1] hay una tabla tal que en su séptima columna (Firmware) están disponibles los enlaces para descargar el fichero correspondiente (la mayoría o todos tienen extensión .usb).
    • Puede que tengamos un pequeño problema: hay algunos dispositivos diferentes bajo el mismo nombre. Para saber qué archivo debemos descargar, abrimos la línea de comandos y ejecutamos la orden lsusb. Aparecerán una serie de líneas parecidas a esta:

      1
      
      Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub

      Debemos fijarnos en la línea que haga referencia a nuestro escáner, y mirar los dos números separados por los dos puntos que están detrás de la palabra ID, estos muy probablemente se corresponderán con los números que aparecen en la quinta y sexta columna (VID y PID) y os ayudarán a escoger el fichero adecuado.

  3. Una vez descargado el fichero pasaremos a trabajar únicamente en la línea de comandos. En primer lugar entraremos como administrador. Dependiendo del sistema deberemos hacerlo con el comando sudo -s, o con el comando su. En segundo lugar, intentaremos crear un nuevo directorio (si ya está creado y recibimos un mensaje de error no pasa nada):

    1
    
    mkdir /usr/share/sane/gt68xx/
  4. Ahora debemos mover el fichero descargado al directorio recién creado (sin olvidar hacerlo como superusuario, en caso contrario el sistema no os dejará hacerlo).

    1
    
    cp /ruta/al/fichero/descargado /usr/share/sane/gt68xx/
  5. Finalmente, lo único que nos queda es dar permisos de lectura al fichero, y ya habremos acabado :) .

    1
    
    chmod a+r /usr/share/sane/gt68xx/ficherodescargado

Espero que os haya resultado útil, para más información podéis visitar las páginas que enlazo en las referencias.

Referencias

  1. Instrucciones originales del proyecto SANE
  2. Buscador de escáneres soportados por el proyecto SANE

Añadir filtro de búsqueda en una tabla con jQuery

Gnome System Search IconPara completar el anterior artículo que escribí sobre ordenación de tablas con jQuery [1] hoy os hablaré sobre la creación de un filtro de búsqueda para nuestras tablas, algo prácticamente imprescindible hoy en día en multitud de aplicaciones de gestión que presentan parte de sus datos de forma tabular.

El código que veréis a continuación es bastante sencillo, no permite búsquedas con operadores lógicos (AND, OR, NOT), ni que se restrinjan a columnas concretas, sin embargo sí que aprovecha toda la potencia de las expresiones regulares para realizar búsquedas relativamente complejas. Para mi gusto es más importante la característica de los operadores lógicos y las restricciones a columnas concretas que la posibilidad de usar expresiones regulares, pero creo que no vale la pena complicar el artículo por algo que no tiene demasiada miga a nivel técnico.

El código es tal que así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function setup_searchable (selector) {
	var search_input = $("#"+selector+"-searcher");
 
	search_input.keyup ((function () {
		var tbody = $("#"+selector+"-table").find('tbody');
		var hidden_rows = tbody.find('tr');
 
		var searcher = function () {
			var search_text = search_input.val().toUpperCase();
 
			tbody.html('');
 
			var shown_rows = hidden_rows.filter (function () {
				var re = new RegExp (search_text),
					 matched = false;
 
				$(this).find('td').each(function (i2) {
					matched = matched || ($(this).text().toUpperCase().match(re) != null);
				});
 
				return matched;
			});
 
			tbody.append (shown_rows);
		};
 
		return searcher;
	})());
}

En este ejemplo trabajamos con dos elementos básicos, un elemento input de tipo texto y una tabla. El código asume que el selector se referirá a su identificador único id, y que los respectivos ids tendrán una estructura del tipo selector-searcher para el elemento input, y selector-table para la tabla.

Sobre el código, puede ser interesante notar que uso el evento keyup en vez del evento change, esto permite que la tabla se actualice en tiempo real, en contraposición a lo que sucedería con change, que necesita que apretemos la tecla Enter y cambiemos o cambiemos el foco de nuestro cursor. Otro detalle importante es que guardamos en memoria la totalidad de las filas antes de efectuar el filtrado, de modo que evitamos perderlas, y cuando cambiamos los criterios de búsqueda no tenemos que recargar ningún dato desde el servidor. Este último hecho implica que conviene llamar a la función setup_searchable cada vez que recarguemos la tabla con nuevos datos desde el servidor, pues de lo contrario nuestro buscador trabajará con datos "antiguos".

Algunas mejoras obvias para este código aparte de las mencionadas anteriormente serían las siguientes:

  • Posibilidad de trabajar con selectores más generales (lo que puede permitir, por ejemplo, tener dos campos de búsqueda para una misma tabla, uno en la cabecera y otro en el pie).
  • Añadir un parámetro callback que permita actualizar ciertos datos no mostrados en la tabla en función de lo que haya filtrado nuestra búsqueda. Un ejemplo muy simple sería la actualización de un contador, o en casos más complejos podríamos estar tratando datos como totales, medias, varianzas, etc.

Espero que esto le resulte útil a alguien, agradeceré cualquier comentario y propuesta de mejora, saludos! :)

  1. Ordenación de elementos del DOM con Javascript y jQuery

Ordenación de elementos del DOM con Javascript y jQuery

Shellsort gráfico (from Wikipedia)Hace unos días tuve que lidiar con un problema común en el diseño de aplicaciones web: ordenar bajo demanda las filas de una tabla según el valor de una columna concreta. Evidentemente lo primero que hice fue buscar por Internet alguna solución que me permitiera no tener que programar demasiado... y tengo que decir que lo encontré, pero por pequeños detalles nada de eso me sirvió.

Así que en este artículo explicaré el código que acabé picando yo por mi cuenta. Lo que he escrito depende de jQuery, aunque en realidad se puede sustituir por su "subconjunto" Sizzle, o incluso por la microbiblioteca Zepto.JS (que tiene una sintaxis compatible con la de jQuery).

Vayamos pues al código directamente, he creado un fichero llamado tablesorter.js en el que está todo el código necesario, salvo la llamada a una función, que se hace allí donde nos convenga :) .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
function row_comparer (row1, row2, index) {
	var r1 = $(row1).find('td').filter(function(){
                        return $(this).index() === index;
        }).text().toUpperCase();
	var r2 = $(row2).find('td').filter(function(){
                        return $(this).index() === index;
        }).text().toUpperCase();
 
	return  (r1 > r2)? 1:
	       ((r1 < r2)?-1:0);
}
 
function shsort (list, comparer, order) { // Shell sort (with Marcin Ciura's gaps)
	var gaps = [701, 301, 132, 57, 23, 10, 4, 1],
	    s = list.length,
	    t;
 
	for (var k=0; k<8; k++) { // 8 is gaps size
		for (var g = gaps[k], i=g; i<s; i++) {
			t = list[i];
			for (var j = i; j >= g && order*comparer(list[j-g], t) == 1; j-= g) {
				list[j] = list[j - g];
			}
			list[j] = t;
		}
	}
}
 
function sort_table (table, index, order) {
	var tbody = table.find ('tbody');
	var rows = tbody.find('tr');
 
	var comparer = function (row1, row2) {
		return row_comparer (row1, row2, index);
	}
 
	shsort (rows, comparer, order);
 
	tbody.html('');
	tbody.append (rows);
}
 
function setup_sortable (selector) {
	$(selector).each (function (i1) {
		$(this).find('th').each (function (i2) {
			var order = 1;
 
			$(this).click ( function () {
				sort_table ($(this).closest('table'), i2, order);
 
				order *= -1;
			})
		});
	});
}

Supongo que algunos de los que leéis esto preferiríais que hubiese creado un plugin para jQuery, siento decir que no es así. Eso no es por ninguna razón en particular, simplemente pasa que todavía no me he tomado la molestia de aprender como se hace, y no sé si es complicado o no, supongo que en breve habrá una versión en forma de plugin para jQuery :) .

En cuanto a como usarlo, la función setup_sortable debe ser llamada utilizando como parámetro un selector para las tablas a las que queramos aplicar ordenación "automática" (con este script las tablas se ordenan cuando se hace click en los elementos th de la tabla, básicamente las cabeceras). En mi caso añadí la clase sortable a todas las tablas que quería añadir esta característica y usé el selector ".sortable".

En cuanto al código, supongo que es destacable que hago un uso intensivo de las funciones anónimas y las clausuras, aunque no es nada que deba sorprender a nadie. En mi humilde opinión, lo más interesante es el algoritmo de ordenación, un "simple" Shell sort (basado en Insertion Sort).

Escogí este algoritmo por las siguientes razones:

  • Es estable, es decir, se mantiene el orden relativo entre elementos con la misma clave (el valor que se usa para realizar las comparaciones) que había en la lista antes de la ordenación. Esto es útil cuando queremos ordenar por distintos criterios a la vez.
  • Es de fácil implementación, solo tenéis que ver el código. Aquí puede parecer un poco más complicado porque uso la función comparer en vez del típico operador de comparación, pero no es una gran dificultad.
  • No usa recursión, ni pilas: los requisitos de memoria son pequeños y constantes.
  • Dependiendo de la implementación (y en particular con esta se consigue), se tiene que para ordenar una lista se deben realizar solo una cantidad de operaciones del orden de O(n·log²(n)).
  • Se trata de una rara avis, le cogí cariño. Aunque no se trata de un algoritmo destacable por su rendimiento, resulta que es tremendamente complicado a nivel teórico. Tanto que su velocidad depende fuertemente de una serie de parámetros de los que casi no se sabe nada a día de hoy. Yo he usado los de Marcin Ciura, que hasta donde yo sé, son los mejores encontrados hasta el momento.

Bien, hasta aquí el apunte de hoy :) . Espero que os pueda resultar útil, agradeceré cualquier comentario.. y si a alguien le interesa, estoy abierto a aceptar ayuda en la jqueryzación de esta minilibrería. Saludos!