Archivos de la categoría Proyectos

Monitorizando aplicaciones web con rrdtool

Vamos con uno de esos artículos que le gustan a Álvaro basados en experiencias del trabajo.

En uno de los proyectos en los que estamos trabajando actualmente y del que ya os he hablado, teníamos un problema de rendimiento en una aplicación web, pero no teníamos localizado el punto donde se nos iba el tiempo. Incluso había momentos que la aplicación iba bien pero en otros era lamentable. Se nos ocurrió, entonces, hacer un sencillo sistema de monitorización de los distintos puntos sensibles que manejábamos y crear con ellos una serie de gráficas que nos permitiese hacer un seguimiento visual de los potenciales problemas.

Nuestra aplicación se basa en llamadas a distintos webservices a partir de las cuales se genera un XML al que se le aplican transformaciones XSL para obtener el código html que se muestra al usuario. Algunas de esas llamadas se guardan en la sesión del usuario, con lo que en sucesivos accesos a la aplicación el rendimiento es infinitamente mejor, así que necesitábamos que el control se hiciese con sesiones limpias para que no se nos desvirtuasen las gráficas.

Nuestro sistema consiste en generar por un lado, en la aplicación web, una serie de datos estadísticos que nos ayuden a buscar los puntos conflictivos. Estos datos se insertarán periódicamente en una base de datos rrd (round robin database) que finalmente utilizaremos para generar las gráficas.

Preparando la aplicación web

El primer paso es, entonces, modificar el código de la aplicación para que vaya generando los tiempos de respuesta en los distintos puntos que queremos controlar. Para facilitar las cosas decidimos que cada vez que se pase un parámetro predefinido en la url, en vez de mostrar la página html correspondiente mostraremos las estadísticas que después controlaremos. En nuestro caso decidimos que la url de estadísticas fuese:

http://tudominio.com/index.php?stats

Es decir, añadiendo el parámetro stats a cualquiera de las urls del site obtendremos los tiempos de respuesta que necesitamos.

Tenemos una función que genera el tiempo exacto del servidor cada vez que se llama, así que la utilizaremos después de ejecutar cada uno de los webservices y guardaremos esos datos. En mi caso tengo que obtener ocho puntos de control además del tiempo de inicio y el tiempo final, algo así:

function getTime() {
 $mtime2 = explode(" ", microtime());
 $endtime = $mtime2[0] + $mtime2[1];
 return $endtime;
}
$isStats=isset($_GET['stats']);
if($isStats)
    $time_inicio=getTime();
....
if($isStats)
    $time_ws1=getTime();
.....
if($isStats)
    $time_ws2=getTime();
....
if($isStats)
    $time_ws3=getTime();
....
if($isStats)
    $time_ws4=getTime();
....
if($isStats)
    $time_ws5=getTime();
....
if($isStats)
    $time_ws6=getTime();
....
if($isStats)
    $time_ws7=getTime();
....
if($isStats)
    $time_ws8=getTime();
....
if($isStats)
    $time_final=getTime();
......

Finalmente devolvemos una línea con todos los valores intermedios, es decir, para cada indicador sería el valor del tiempo de ese indicador menos el del anterior, esto nos dará el valor real del tiempo que ha pasado entre el indicador anterior y el actual. Para facilitar la posterior integración de estos valores con rrdtool lo que haremos será generarlos ya en el formato adecuado, separando cada valor con “:”.

if(isset($_GET['stats'])){
	echo number_format($time_ws1-$time_inicio, 7).":".
			number_format($time_ws2-$time_ws1, 7).":".
			number_format($time_ws3-$time_ws2, 7).":".
			number_format($time_ws4-$time_ws3, 7).":".
			number_format($time_ws5-$time_ws4, 7).":".
			number_format($time_ws6-$time_ws5, 7).":".
			number_format($time_ws7-$time_ws6, 7).":".
			number_format($time_ws8-$time_ws7, 7).":".
			number_format($time_final-$time_inicio, 7);
}

Si ahora llamamos a nuestra url obtendremos los valores de nuestros indicadores. El último valor nos devolverá el tiempo completo de ejecución del script entre los valores $time_inicio y $time final.

0.0281749:0.5443010:0.3132501:2.9015441:0.0000241:0.6517198:5.5171580:0.0000379:10.0677590

Vemos cómo esta llamada ha tardado diez segundos, de los cuales 5,5 corresponden al indicador ws7.

Tenemos el primer paso preparado, vamos ahora a crear la base de datos donde almacenaremos toda la información estadística de la url. Tendremos una tarea que se ejecutará cada minuto y que recuperará los valores en ese momento de los identificadores que se han definido.

Creando la base de datos

Para guardar la información he escogido rrd, el mismo que utilizan aplicaciones como mrtg o Cacti y el utilizado en la mayoría de sistemas de monitorización. Según reza en su propia web, es el sistema estándar para gráficas y logs de series de datos temporales.

Para que todo vaya bien nuestra base de datos rrd debe tener la misma cantidad de fuentes de datos que el sistema de estadísticas, nueve en este caso, los ocho puntos de control más el total. Además tenemos que pensar qué graficas vamos a crear. Yo quiero cuatro: última hora, últimas seis horas, últimas 24 horas y última semana. No necesito hacer un sistema con histórico avanzado, lo que quiero en este caso es controlar el rendimiento, así que no necesitaré gráficas mensuales ni anuales, pero se podrían generar de igual modo.

Para crear nuestro archivo rrd ejecutamos el comando siguiente:

rrdtool create estadisticas.rrd --step 60
DS:ws1:GAUGE:120:0:U
DS:ws2:GAUGE:120:0:U
DS:ws3:GAUGE:120:0:U
DS:ws4:GAUGE:120:0:U
DS:ws5:GAUGE:120:0:U
DS:ws6:GAUGE:120:0:U
DS:ws7:GAUGE:120:0:U
DS:ws8:GAUGE:120:0:U
DS:total:GAUGE:120:0:U 

RRA:AVERAGE:0.5:1:60
RRA:AVERAGE:0.5:1:360
RRA:AVERAGE:0.5:5:144
RRA:AVERAGE:0.5:10:202
RRA:MAX:0.5:1:60
RRA:MAX:0.5:1:360
RRA:MAX:0.5:5:144
RRA:MAX:0.5:10:202
RRA:MIN:0.5:1:60
RRA:MIN:0.5:1:360
RRA:MIN:0.5:5:144
RRA:MIN:0.5:10:202

Vamos a explicarlo un poco.

  • –step=60: indica que los datos los actualizaremos cada 60 segundos. Nuestro sistema guardará estadísticas cada minuto.
  • DS:ws1:GAUGE:120:0:U: Fuentes de datos, hay una línea de este tipo para cada valor que vamos a mantener.
    1. wsX es el nombre que tendrá esa fuente.
    2. GAUGE es el tipo de fuente de datos, puedes consultar la documentación completa para tener más idea.
    3. 120 es el parámetro heartbeat, tiempo máximo entre dos actualizaciones antes de que se asuma que es desconocido. He puesto el doble de step, si pasado ese tiempo no hay una actualización es que se ha perdido por cualquier razón.
    4. Los últimos datos son los valores mínimo y máximo que habrá en la fuente de datos. En nuestro caso el mínimo será cero y el máximo no lo sabemos, le ponemos U de unknown.
  • RRA:AVERAGE:0.5:1:60: Round robin archives. El objetivo de una rrd es guardar datos en los rra que mantienen valores y estadísticas para cada una de las fuentes de datos definidas.
    1. AVERAGE/MAX/MIN/LAST: la función utilizada para consolidar los datos. Como su propio nombre indica son la media, máximo, mínimo y el último valor.
    2. 0.5; el xff (xfiles factor). De 0 a 1, indica el número de datos UNKNOWN que se permiten por intervalo de consolidación.
    3. 1: define el número de datos utilizados para crear un intervalo de consolidación.
    4. 60: Cuantos datos se mantienen en cada RRA. Esto depende de la gráfica que vamos a generar y se relaciona con el dato anterior.

La última parte es la más complicada de comprender. Intentaré explicarlo lo mejor que pueda para que se entienda. En nuestro caso tomamos un dato cada 60 segundos y queremos tener la gráfica de la última hora (60 segundos x 60 minutos, 3600 segundos). Necesitamos, por tanto, 60 puntos de control (3600/60 segundos de intervalo), utilizaremos cada uno de los datos que se toman. Esto es lo que indica la creación RRA:AVERAGE:0.5:1:60, queremos utilizar 60 puntos de control tomados cada 60 segundos. Este es bastante fácil, veamos el siguiente.

RRA:MIN:0.5:1:360

Ahora estamos guardando 360 puntos generados cada 60 segundos, es decir, cubrimos 60×360=21600 segundos=6 horas. Era sencillo.

RRA:AVERAGE:0.5:5:144

En la siguiente gráfica queremos obtener datos de las últimas doce horas (12 horas x 60 minutos x 60 segundos, 43200 segundos). Ahora ya no necesitamos saber el comportamiento exacto en cada momento sino que necesitamos una visión global de lo que ha ocurrido durante esas doce horas, tomamos, por tanto, un punto de control cada cinco datos de registro, es decir, estamos manteniendo un dato cada 5×60 segundos, 300 segundos. Necesitaremos por tanto 43200/300=144 valores para obtener nuestra gráfica de las últimas 12 horas

RRA:MIN:0.5:10:202

La última gráfica es semanal, 7x24x60x60=604800 segundos. Acepto que con tener un valor cada 10 minutos será suficiente, tendré entonces un valor cada 10×300=3000 segundos. 604800/3000=~202.

Es algo difícil de entender al principio, pero una vez lo piensas, todo tiene sentido.

Guardando las estadísticas

Tenemos ya nuestra base de datos preparada, creemos ahora la tarea que guardará en este archivo rrd los datos estadísticos. En mi caso es una tarea PHP que lo hace todo muy sencillo.

tarea.php

$url="http://dominio.com/?stats";
$cont=file_get_contents($url);
system("rrdtool update estadisticas.rrd N:".$cont);

Añadimos la tarea al cron para que se ejecute cada 60 seguntos.

*/1 * * * * /usr/bin/php /path/to/tarea.php > /dev/null 2>&1

¡Y ya está! Nuestra base de datos de estadísticas se está alimentando automáticamente.

Creando las gráficas

Lo más importante a fin de cuentas, lo que realmente vamos a utilizar. Rrdtool viene con un script de ejemplo que debes colocar en el directorio cgi-bin de tu servidor web y realizar los ajustes adecuados. Yo he puesto como ejemplo la gráfica de la última hora, las demás se harían igual variando el parámetro “–start” a los segundos anteriores que queremos mostrar.

/cgi-bin/stats.cgi

#!/usr/bin/rrdcgi

Tu Dominio - Home Stats

<RRD::GRAPH /usr1/www/www.genteirc.com/htdocs/imagenes/daily-visitas.png --imginfo '<img src="http://tudominio.com/%s" alt="" width="%lu" height="%lu" />' --start -3600 --end -0 -h 400 -w 600 --vertical-label="Tiempo" --title="Last Hour" -a PNG -l 0 DEF:ws1=/path/to/estadisticas.rrd:ws1:AVERAGE LINE1:ws1#00FF00:WebSer1t GPRINT:ws1:AVERAGE:"Media : %.4lft" GPRINT:ws1:MAX:"Max : %.4lft" GPRINT:ws1:MIN:"Min : %.4lfl" DEF:ws2=/path/to/estadisticas.rrd:ws2:AVERAGE LINE1:ws2#800000:WebSer2t GPRINT:ws2:AVERAGE:"Media : %.4lft" GPRINT:ws2:MAX:"Max : %.4lft" GPRINT:ws2:MIN:"Min : %.4lfl" DEF:ws3=/path/to/estadisticas.rrd:ws3:AVERAGE LINE1:ws3#FF8000:WebSer3t GPRINT:ws3:AVERAGE:"Media : %.4lft" GPRINT:ws3:MAX:"Max : %.4lft" GPRINT:ws3:MIN:"Min : %.4lfl" DEF:ws4=/path/to/estadisticas.rrd:ws4:AVERAGE LINE1:ws4#400080:WebSer4t GPRINT:ws4:AVERAGE:"Media : %.4lft" GPRINT:ws4:MAX:"Max : %.4lft" GPRINT:ws4:MIN:"Min : %.4lfl" DEF:ws5=/path/to/estadisticas.rrd:ws5:AVERAGE LINE1:ws5#0000FF:WebSer5t GPRINT:ws5:AVERAGE:"Media : %.4lft" GPRINT:ws5:MAX:"Max : %.4lft" GPRINT:ws5:MIN:"Min : %.4lfl" DEF:ws6=/path/to/estadisticas.rrd:ws6:AVERAGE LINE1:ws6a#00FFF0:WebSer6t GPRINT:ws6a:AVERAGE:"Media : %.4lft" GPRINT:ws6:MAX:"Max : %.4lft" GPRINT:ws6:MIN:"Min : %.4lfl" DEF:ws7=/path/to/estadisticas.rrd:ws7:AVERAGE LINE1:ws7#FF00FF:WebSer7t GPRINT:ws7:AVERAGE:"Media : %.4lft" GPRINT:ws7:MAX:"Max : %.4lft" GPRINT:ws7:MIN:"Min : %.4lfl" DEF:ws8=/path/to/estadisticas.rrd:ws8:AVERAGE LINE1:ws8#FFFF00:WebSer8t GPRINT:ws8:AVERAGE:"Media : %.4lft" GPRINT:ws8:MAX:"Max : %.4lft" GPRINT:ws8:MIN:"Min : %.4lfl" DEF:total=/path/to/estadisticas.rrd:total:AVERAGE LINE2:total#FF0000:'Total t' GPRINT:total:AVERAGE:"Media : %.4lft" GPRINT:total:MAX:"Max : %.4lft" GPRINT:total:MIN:"Min : %.4lfl" >

Para cada línea que queremos mostrar en la gráfica le indicamos de qué archivo rrd saldrán los datos y la fuente (DS) que vamos a utilizar. Añadimos además como leyenda los datos de media, máximo y mínimo de cada fuente, de manera que en una sola gráfica tenemos toda la información necesaria. Si ahora vas a la url donde has dejado el cgi tendrás:

http://tudominio.com/cgi-bin/stats.cgi

stats

Y tu te preguntarás, con lo pesado que te pones con Cacti, ¿por qué no meterla ahí también? Buena pregunta :P. Si tengo un ratito os enseño cómo integrarlo también con Cacti :).

Me he pasado con este artículo, no se lo va a leer nadie :P.

SCRUM de dos equipos en distintas ciudades

El pasado mes de mayo comenzamos el desarrollo de un nuevo proyecto que ha terminado recientemente, al menos la primera fase del mismo. Partimos casi de cero, los requerimientos eran muy básicos y poco documentados, pero parte del equipo teníamos en la cabeza exactamente lo que teníamos que hacer, de hecho era plasmar en una única aplicación todo nuestro trabajo de los últimos cuatro años.

Desde el principio nadie tuvo dudas, SCRUM era la mejor metodología posible para cumplir los plazos que nos habían impuesto. Planteamos sprints de dos semanas y dailys diarios (obviamente) a las 10 de la mañana de alrededor de 10 minutos. Como scrum master se quedó uno de nuestros project managers y como product owner otro del departamento de operaciones. La mayoría nos habíamos leído ya el Scrum desde las trincheras, pero una cosa es la teoría y otra muy diferente la práctica, y ahí casi nadie teníamos experiencia.

No trataré en este artículo de enseñaros SCRUM, no soy un experto, como mucho un poco “evangelizador” :P, simplemente trataré de explicar mis sensaciones tras cinco meses de SCRUM intensivo.

El Equipo

En el proyecto participaron dos equipos de desarrollo, uno en Valencia de 6 personas y otro en Madrid en el que llegaron a trabajar más de 30. Ninguna de estas casi 40 personas tenía disponibilidad completa para este proyecto sino que hubo que redistribuir toda la carga de trabajo para, con el mismo equipo, asumir un nuevo proyecto de cinco meses de duración. Salió bastante bien. Los dailys se hacían vía conferencia entre una sala de reuniones en Madrid y otra en Valencia. Se hicieron, además, infinidad de reuniones a dos bandas entre los dos equipos para decidir funcionalidades, discutir los puntos donde no había acuerdo y resolver dudas.  No sólo participaban desarrolladores sino también jefes de equipo, project managers, diseñadores, “expertos” en usabilidad, gente de testing y calidad, documentalistas, gente de sistemas, de datawarehouse

En la parte tecnológica tuvimos otro grave problema. Desde Madrid desarrollaban en .NET y desde Valencia en PHP. ¿Cual es el problema? :P. Se diseñó la arquitectura de manera que la aplicación trabajase indistintamente en uno u otro lenguaje. Dentro de un mismo entorno web, unos módulos se cargan de un lado y los otros del otro de forma completamente transparente al usuario. Se diseñó un sistema de single sign on que pudiesen compartir y consultar ambas tecnologías y, asociado a éste, todo un mecanismo de seguridad de acceso a distintos menús y opciones de la aplicación. El resultado fue formidable, todo funciona perfectamente de una manera integrada, robusta, eficiente y segura.

La Pila de Producto (product backlog)

Con la poca documentación que se tenía al principio se elaboró una pequeña pila de producto que fue creciendo a medida que las tareas de análisis evolucionaban. A esto debemos sumar requerimientos que iban llegando por parte de los departamentos comerciales y de operaciones. Al final el backlog era considerable. En agosto hubo que decidir quitar algunas historias de usuario ya que era imposible acometer todo lo que se quería, de hecho los requerimientos a estas alturas eran infinitamente superiores a los que se habían supuesto en mayo. Aún así, eliminando cosas, se hizo un producto mucho más ambicioso de lo esperado. Las historias de usuario eliminadas no se olvidaron, simplemente se trasladaron a una segunda versión.

Para el seguimiento del SCRUM, en vez de la clásica pizarra y dada la dispersión de los equipos, decidimos utilizar una herramienta de software. Comenzamos con un sencillo Excel y a mitad del proyecto incorporamos ScrumWorks, no creo que sea el programa ideal pero cumple su función.

Los Sprints

Los tres primeros sprints fueron prácticamente de análisis. Se hicieron documentos funcionales y orgánicos de todos los módulos requeridos y se perdió bastante tiempo definiendo la arquitectura de la aplicación, en realidad de las dos aplicaciones (.NET y PHP). Todo esto nos llevó a plantarnos a mediados de junio, tras 3 sprints, con muy poco que mostrar en la demo, y nos condujo a la consabida reprimenda de los que mandan, nos estábamos desviando (teóricamente) de la planificación estipulada. Sin embargo el tiempo nos daría la razón, el tiempo perdido en el diseño de la arquitectura se recuperó con creces en sprints siguientes y llegamos a la fecha con el proyecto terminado. Bueno, en realidad dejamos para el final un pequeño detalle, el rendimiento, que se solucionó en un sprint adicional.

A todo esto hemos de añadir, como ya he dicho, que el equipo no estaba dedicado al proyecto, cada día variaba la gente que entraba al daily ya que no todos estaban trabajando en el proyecto en ese momento. A eso hay que sumarle lo que llamo “contingencias del trabajo diario“, es decir, nuevos proyectos que van surgiendo durante todo ese tiempo y que implican dedicarle tiempo que tienes que quitarle a lo verdaderamente importante. Por si eso no fuera poco, a finales de junio se nos informa que una parte de la aplicación, un módulo de reporting, tiene que estar operativa a finales de julio puesto que se necesita para dar servicio en otros proyectos. Esto, que de palabra suena sencillo, nos obligó a modificar la planificación, el product backlog y la pila de sprint ya que el funcionamiento de este módulo implicaba a otros (login, seguridad, diseño…). Y aún así cumplimos todos los plazos, increíble. Eso sí, otros proyectos tuvo que decidirse entre descartarlos o aprobar una desviación de una o dos semanas en el proyecto del que hablamos, con lo que de igual manera fueron descartados :P.

Las Estimaciones

Uno de los mayores problemas es, sin duda, estimar las horas de las tareas. Comenzamos quedándonos cortísimos y terminamos quedándonos cortos también, creo que rara vez se hizo una estimación cercana a la realidad. Es muy complicado, sin duda. Las tareas de análisis se sobrestimaron en su mayoría y las de desarrollo (la mayoría) se quedaron cortas. Para planificar los primeros sprints utilizamos Poker-Scrum, pero acabamos haciéndolo directamente de viva voz ya que decidimos que no aportaba nada. En los primeros sprints hubo que arrastrar bastantes tareas de uno a otro porque se habían estimado muy a la baja, sin embargo hacia el final se recuperó el tiempo perdido y las estimaciones no eran tan malas.

Las Demos

Las demos al cliente (interno en nuestro caso) son el peor momento del sprint, no pocos días nos tocó quedarnos hasta altas horas de la noche para llegar a la demo del día siguiente con el trabajo terminado o, al menos, visible. Si esto lo hacíamos cada dos semanas, imaginaos que hubiese pasado si utilizásemos metodologías tradicionales, hubiésemos llegado al final del tiempo de desarrollo sin hacer ninguna demo, sin haber probado las cosas de una manera real. Habría sido imposible, de hecho es lo que ocurre habitualmente :P.

En nuestro caso nos sirvió no sólo para ver la reacción del cliente ante el avance del producto sino también para ver las fortalezas y debilidades del trabajo que estábamos desarrollando y reorientar los siguientes sprints.

Las demos están para que el cliente vea el avance del producto que se le está desarrollando. El cliente en nuestro caso era una persona en representación de uno o varios departamentos internos de la organización. ¿Qué utilidad tienen las demos si sólo asiste el product owner? Digo esto porque de vez en cuando aparecía por allí algún despistado que se dedicaba a criticar el producto, en concreto partes del mismo que llevaban dos o tres meses terminadas y validadas por el product owner. ¿Por qué esa actitud revienta-demos? O mejor aún, ¿por qué no le prestas el debido interés a un producto en el que se basará todo tu trabajo los próximos años? Ah, ya, es más fácil dejar la responsabilidad al product owner y después quejarse. Sin la implicación de toda la organización da igual qué metodología se utilice, ninguna conseguirá triunfar. Estamos hablando de media hora cada quince días, sólo eso.  Otro punto importante a tener en cuenta es la opinión. La gente que asiste a la demo debería estar obligada a decir algo y no quedarse callados como si no fuera con ellos porque al final ocurre lo mismo, cuando tienen que opinar no opinan y después, cuando ya no se puede hacer nada, es cuando hablan.

Retrospectiva y planificación

Las reuniones de retrospectiva fueron bastante interesantes. Por un lado comentábamos la indiferencia de los asistentes a las demos y por otro discutíamos sobre los problemas que se habían presentado durante el sprint, unas veces con más energía y otras con menos. En realidad casi podríamos decir que servían como válvula de escape al pasotismo de los asistentes a la demo.  Son necesarias las retrospectivas, sirven precisamente para evaluar no sólo lo que ha ocurrido sino también los ánimos del equipo.

Tras la retrospectiva llega la reunión de planificación de sprint. Tal como indicaba más arriba, las estimaciones se hicieron muy complicadas, muchísimas tareas por historia de usuario, muchísimas dependencias entre ellas y no siempre tenemos todos en la cabeza las implicaciones que pueden tener unas con otras, lo que termina en estimaciones muy poco aproximadas por no decir aleatorias. Aún así poco a poco se fueron afinando bastante. En las reuniones de planificación se ponían sobre la mesa también nuevos requerimientos que habían ido surgiendo y modificaciones sobre los ya terminados que obligaban a modificar las prioridades de la pila de producto ajustando el calendario a las nuevas necesidades y objetivos hasta llegar a la pila del sprint siguiente.

Conclusiones

Sin duda la experiencia ha sido muy positiva. En otras ocasiones habíamos hecho intentos de aplicar SCRUM a determinados proyectos  que resultaron en utilizar solamente algunos conceptos de SCRUM, pero la falta de implicación del cliente (casi siempre interno) restaba utilidad a la metodología. En esta ocasión se hizo una aplicación completa, real y efectiva, implicando a todos los elementos de la organización que tuviesen algo que decir dentro del proyecto y, aunque siempre se pueden hacer críticas a la implicación de algunos, podemos afirmar que, en general, todos cumplieron con su parte.

Una de las cosas de SCRUM que todos alabamos cinco meses después son, sin duda, los dailys. Nos sirvieron a todos los partícipes del proyecto para conocer de buena mano qué estaban haciendo los demás, qué problemas había,  cuándo se iban a resolver, etc. Creo que cualquier equipo de desarrollo, independientemente de la metodología que utilice, debería realizar reuniones diarias para compartir ideas y problemas.

La idea de tener demos del producto cada dos semanas obliga a todo el equipo a pensar más en hacer cosas que funcionen que en ir avanzando en tareas, es más importante terminar dos historias de usuario para presentar al final del sprint que comenzar 20 tareas que no se terminen.

Uno de los puntos que nos desviaron del objetivo en cada sprint fue el proceso de integración y preparación para la demo. Nunca tuvimos en cuenta estas horas que al final implicaban bastante tiempo de todo el equipo ya que al realizar la integración siempre había cosas que no funcionaban bien. El último día estaba dedicado casi íntegramente a estas labores que nunca estaban planificadas en la pila del sprint.

Los que me conocen saben que llevo ya unos años defendiendo SCRUM como metodología adecuada para el desarrollo de software. Esta experiencia no ha hecho más que confirmar esta opinión, la adaptación del producto al cliente que se consigue no se podría lograr con metodologías clásicas en las que la más mínima modificación de las tareas a realizar puede provocar una desviación enorme.

Supongo que pagamos algo cara la falta de experiencia, sobre todo al principio, pero aún así logramos el objetivo, seguramente con un poco más de experiencia el resultado habría sido aún mejor.

Por cierto, no hemos dejado SCRUM, el proyecto sigue y ya trabajamos en la siguiente release, así que seguimos liados con dailys, sprints, estimaciones…

Regala lotería de Navidad 2009

Como en años anteriores, ya está disponible la creación de participaciones de lotería a medida con el décimo de este año.

Si vas a regalar lotería no dudes en utilizar TusDecimos.com, además de ser una manera original de hacerlo también  podrás enviar las participaciones por email o directamente al móvil de tus amigos y familiares.

tusdecimos2009

En unas semanas os daré alguna sorpresa al respecto 🙂 .
¡Mucha suerte a todos!

Cucharete.com finalista de los Premios de Internet 2009

La Asociación de Usuarios de Internet organiza la XI edición de sus Premios de Internet y menuda sorpresa nos hemos llevado al ver entre los diez finalisdas de la caterogía C1-Mejor Web a Cucharete, la mejor web de restaurantes de Madrid.

cucharete

Sin duda alguna esta candidatura es premio al trabajo y al esfuerzo que están realizando tanto Marcos como su equipo para sacar adelante este bonito y suculento proyecto.

Mi más sincera enhorabuena.

Actualizado a las 16:00

Ahora mismo quedan sólo tres finalistas, Cucharete entre ellos. Increíble.

WordPress Mobile Pack, plugin para movilizar tu WordPress

Menuda sorpresa me he llevado hoy al leer esta noticia.

Ha habido otros intentos de hacer algo similar pero ninguno llegaba a la perfección que roza este nuevo plugin ya que lo ajusta todo al terminal del cliente, desde las imágenes hasta la paginación de artículos. En el link de presentación tenéis toda la información. No podía ser menos viniendo del organismo “oficial” de la web móbil, dotMobi.

Entre las características que me parecen importantes y fundamentales en un producto de este tipo y que hasta ahora otros habían obviado limitándose a presentar un blog menos recargado:

  • Detecta el móvil del cliente y consulta la base de datos de DeviceAtlas para obtener sus características (tamaño de pantalla, colores…).
  • Puedes hacer que cualquier petición a un dominio sea automáticamente móvil independientemente del dispositivo que acceda (m.tudominio.com).
  • Selección de temas concretos para la versión móvil independiente del web. No tienes que empezar de cero.
  • Versión móvil del panel de administración para publicar directamente desde tu dispositivo.
  • Paginación de artículos. En otros productos se mostraban en una sóla página y no todo el mundo escribe artículos pequeños 😛 .

Ahora me toca personalizar el tema, pero aquí tenéis mi blog en un Nokia N95.

Cerebro en la Sombra para móviles

Os recomiendo que le echéis un ojo ya que es muy interesante y hace todo el proceso de movilización de tu blog extremadamente sencillo.

Adobe AIR VIII – Información encriptada y persistente

Ya hemos visto como trabajar con bases de datos con AIR, sin embargo he deubierto un nuevo método para conservar datos que puede resultar especialmente útil para mantener información sensible, como claves de acceso a servicios, ya que se guarda encriptada. Hablamos de la clase EncryptedLocalStore.

Una de las peculiaridades de esta clase es que es persistente mientras la aplicación no borre sus contenidos, ya que aunque se desinstale los datos siguen ahí, lo que resulta especialmente útil si tus aplicaciones son de pago (guardar licencias) o tienen periodos de prueba (así no se podrá utilizar de nuevo al desinstalar y volver a instalarla).

Cada aplicación dispone de su propia EncryptedLocalStore con lo que los datos que se guarden en una no interferirán con los de otra.

EncryptedLocalStore tiene sólamente tres métodos estáticos (no es necesario instanciar la clase) que permiten guardar, leer y eliminar datos.

  • setItem(“nombre”, valor):  guarda un dato.
  • getItem(“nombre”):  lee el dato.
  • removeItem(“nombre”):  elimina los datos.

Para dar mayor versatilidad al sistema, los datos a guardar deben ser del tipo ByteArray, así que se podrá guardar cualquier dato que se pueda convertir a este tipo y no sólo cadenas de texto.

Veamos un ejemplo:

//guardar datos
var clave:String = "password";
var bytes:ByteArray = new ByteArray();
bytes.writeUTFBytes(str);
EncryptedLocalStore.setItem("clave", bytes);

//leer datos
var datos:ByteArray = EncryptedLocalStore.getItem("clave");
var miclave:String=datos.readUTFBytes(datos.length));

//eliminar datos
EncryptedLocalStore.removeItem("clave");

Extremadamente sencillo y útil, no se como no lo había visto antes, creo que pasa bastante desapercibida esta manera de guardar datos en la documentación.

De esta manera podemos guardar datos sin necesidad de bases de datos. Eso sí, al parecer no deben exceder los 10mb o comenzaremos a tener problemas de rendimiento. Recuerda lo que decía al principio, aunque el usuario desinstale la aplicación los datos de EncryptedLocalStore siguen en su sistema operativo.

Funciones útiles en XSLT

Una de las tecnologías que más utilizo en el desarrollo de proyectos para móviles son las transformaciones XSL (XSLT) ya que me dan la versatilidad adecuada para adaptar la capa de presentación a las compatibilidades del terminal del cliente (xHTML Mobile, WML, iMode…) o de la operadora (PML de Vodafone por ejemplo).

A lo largo de estos años he ido recopilando algunas funciones (en realidad templates) XSLT que me facilitan la vida a la hora preparar determinadas funcionalidades dentro de un portal wap. Os las dejo por si a alguien le vienen bien.

Capitalize

Un clásico. Convierte una cadena de texto de manera que devuelve el primer carácter en mayúsculas y el resto de la cadena en minúsculas.

<xsl:variable name="lcletters">abcdefghijklmnopqrstuvwxyz</xsl:variable>
<xsl:variable name="ucletters">ABCDEFGHIJKLMNOPQRSTUVWXYZ</xsl:variable>

<xsl:template name="capitalize">
    <xsl:param name="arg"/>
    <xsl:value-of select="concat(translate(substring($arg,1,1), $lcletters, $ucletters), substring($arg,2))"/>
</xsl:template>

Ejemplo de uso:

<xsl:call-template name="capitalize">
     <xsl:with-param name="arg" select="/ruta/al/texto"/>
</xsl:call-template>

 

URL-Encode

Lo que su propio nombre indica. Codifica una cadena de texto para ser enviada como parámetro de una URL. Muy útil en situaciones de integración con sistemas de billing.

<xsl:variable name="ascii"> !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~</xsl:variable>
<xsl:variable name="latin1"> ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ</xsl:variable>
<xsl:variable name="safe">!'()*-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~</xsl:variable>
<xsl:variable name="hex" >0123456789ABCDEF</xsl:variable>
<xsl:variable name="thestring" select="null" />

<xsl:template name="url-encode">
    <xsl:param name="str"/>
    <xsl:if test="$str">
        <xsl:variable name="first-char" select="substring($str,1,1)"/>
        <xsl:choose>
            <xsl:when test="contains($safe,$first-char)">
                      <xsl:value-of select="$first-char"/>
           </xsl:when>
            <xsl:otherwise>
                <xsl:variable name="codepoint">
                    <xsl:choose>
                           <xsl:when test="contains($ascii,$first-char)">
                                 <xsl:value-of select="string-length(substring-before($ascii,$first-char)) + 32"/>
                           </xsl:when>
                           <xsl:when test="contains($latin1,$first-char)">
                               <xsl:value-of select="string-length(substring-before($latin1,$first-char)) + 160"/>
                           </xsl:when>
                           <xsl:otherwise>
                               <xsl:message terminate="no">Warning: string contains a character that is out of range! Substituting "?".</xsl:message>
                               <xsl:text>63</xsl:text>
                           </xsl:otherwise>
                       </xsl:choose>
                </xsl:variable>
                <xsl:variable name="hex-digit1" select="substring($hex,floor($codepoint div 16) + 1,1)"/>
                <xsl:variable name="hex-digit2" select="substring($hex,$codepoint mod 16 + 1,1)"/>
                <xsl:value-of select="concat('%',$hex-digit1,$hex-digit2)"/>
            </xsl:otherwise>
        </xsl:choose>
        <xsl:if test="string-length($str) > 1">
            <xsl:call-template name="url-encode">
                <xsl:with-param name="str" select="substring($str,2)"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:if>
</xsl:template>

Ejemplo de uso:

<xsl:call-template name="url-encode">
    <xsl:with-param name="str" select="/ruta/al/texto"/>
</xsl:call-template>

 

Search and Replace

Otra vez lo que su propio nombre indica. Reemplaza un texto por otro dentro de una cadena.

<xsl:template name="SEARCH-AND-REPLACE">
	<xsl:param name="string" />
	<xsl:param name="search-for" />
	<xsl:param name="replace-with" />
	<xsl:choose>
		<xsl:when test='contains($string,$search-for)'>
			<xsl:value-of select="substring-before($string,$search-for)"/>
			<xsl:value-of select="$replace-with"/>
			<xsl:call-template name="SEARCH-AND-REPLACE">
				<xsl:with-param name="string" select="substring-after($string,$search-for)" />
				<xsl:with-param name="search-for" select="$search-for" />
				<xsl:with-param name="replace-with" select="$replace-with" />
			</xsl:call-template>
		</xsl:when>
		<xsl:otherwise>
			<xsl:value-of select="$string" />
		</xsl:otherwise>
	</xsl:choose>
</xsl:template>

Ejemplo:

<xsl:call-template name="url-encode">
    <xsl:with-param name="string" select="/ruta/al/texto/original"/>
    <xsl:with-param name="search-for" select="texto a buscar"/>
    <xsl:with-param name="replace-with" select="texto por el que se reemplaza"/>
</xsl:call-template>

Si habéis utilizado XSLT en alguna ocasión os habréis dado cuenta de que se echan en falta ciertas funciones “habituales” y que, tal cual, estás muy limitado. Sin embargo XSLT tienes las funcionalidades básicas a partir de las cuales se puede generar casi cualquier otra cosa que se te ocurra, sólo tienes que tener muy claro con lo que cuentas y qué quieres hacer.

Quiero aclarar que estas funciones NO las he hecho yo, las he ido recopilando los últimos años googleando un poco, a veces algo más que un poco, lástima no recordar de donde las fui sacando.

De momento estas tres son las que he localizado. Tengo otra más que suelo utilizar en integración con sistemas de billing para convertir a Base64, pero no la encuentro por ningún lado.

Acelera tus scripts PHP con eAccelerator

Como ya sabréis la mayoría, PHP es un lenguaje de programación de script, es decir, el código fuente no se compila generando un binario o un bytecode sino que el intérprete de PHP lo lee, compila y ejecuta cada vez que se llama a ese script. No hay que ser muy espabilado para darse cuenta de que este proceso de compilación permanente tiene que tener penalizaciones en cuanto a rendimiento, supongo que se compensa con la facilidad de desarrollo al no tener que compilar cada prueba que se quiere hacer.

Hace ya unos años descubrí eAccelerator, fork del conocido Turck MMCache. La primera vez que lo probé e hice tests de rendimiento no me creía la diferencia que había entre el antes y el después, poco a poco y a base de más pruebas me fui convenciendo y hoy en día es parte fundamental de todos los servidores a los que tengo que meter mano. eAccelerator trabaja compilando los scripts PHP y cacheando esta compilación de manera que la siguiente vez que se solicita está ya compilado y no hay que volver a hacerlo. Es importante hacer notar que maneja automáticamente los cambios de versiones a través de la fecha de modificacion (mtime). Si el script es más nuevo que el que tiene cacheado, lo vuelve a compilar, con lo que, de cara al desarrollador, el sistema sigue teniendo la ventaja de no necesitar compilados ni empaquetados.

La instalación es muy sencilla, descargamos el código fuente desde la web oficial y lo compilamos.

phpize
./configure
make
make install

Ya está el módulo instalado, ahora tendremos que activarlo. En mi CentOS es muy sencillo, simplemente debo añadir un nuevo archivo a /etc/php.d y será leído automáticamente en el siguiente reinicio de Apache. El contenido del archivo será algo así:

[osus@servidor ~]# cat /etc/php.d/eacclerator.ini
zend_extension="/usr/lib/php/modules/eaccelerator.so"
eaccelerator.shm_size="16"
eaccelerator.cache_dir="/tmp/eaccelerator"
eaccelerator.enable="1"
eaccelerator.optimizer="1"
eaccelerator.check_mtime="1"
eaccelerator.debug="0"
eaccelerator.filter=""
eaccelerator.shm_max="0"
eaccelerator.shm_ttl="0"
eaccelerator.shm_prune_period="0"
eaccelerator.shm_only="0"
eaccelerator.compress="1"
eaccelerator.compress_level="9"
eaccelerator.allowed_admin_path="/ruta/a/tu/servidor/de/control"

Todos los parámetros están bien explicados en la documentación oficial. El más importante es el último ya que, como veremos más adelante, indica una ruta dentro de tu servidor web desde donde podrás ver un pequeño panel de control de eAccelerator.

Ahora reinicia Apache y comprueba si funciona de verdad:

[osus@servidor ~]# php -v
PHP 5.1.6 (cli) (built: Nov 12 2008 11:22:34)
Copyright (c) 1997-2006 The PHP Group
Zend Engine v2.1.0, Copyright (c) 1998-2006 Zend Technologies
    with eAccelerator v0.9.5.2, Copyright (c) 2004-2006 eAccelerator, by eAccelerator

Parece que sí. Aprovecharemos para configurar la interfaz web que comentaba que nos permite controlar algunas funcionalidades. Para ello copiamos desde la carpeta con el código fuente el archivo control.php a la ruta indicada anteriormente y configuramos los parámetros de autenticación que nos pedirá, usuario y clave, al principio de este archivo.

if (!function_exists('eaccelerator_info')) {
    die('eAccelerator isn't installed or isn't compiled with info support!');
}

/** config **/
$user = "osus";
$pw = "bombona";
/** /config **/

Ya está, sólo queda acceder a esta ruta web para ver algo semejante a esta imagen: información del estado de la caché, posibilidad de desactivar el cacheo, limpiar caché…

Eaccelerator admin

Analizando el rendimiento

Todo esto está muy bien y suena muy bonito, pero ¿mejora de verdad el rendimiento? ¿qué beneficios reales obtenemos? La pregunta del millón.

Utilizaremos Apache Benchmark para hacer pequeñas pruebas de carga sobre el servidor. Esta utilidad viene siempre con Apache, con lo que no tendrás que instalar ningún software adicional. La sintaxis es muy sencilla:

ab -n 100 -c 5 http://dominioentuservidor.com/script.php

Con esto estaremos diciendo a “ab” que lance 100 peticiones contra la url indicada con una concurrencia máxima de 5 solicitudes. El resultado seria algo similar a esto:

[osus@servidor ~]# ab -n 100 -c 5 http://tudominio.com/
This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright 2006 The Apache Software Foundation, http://www.apache.org/

Benchmarking tudominio.com (be patient).....done

Server Software:        Apache
Server Hostname:        tudominio.com
Server Port:            80

Document Path:          /
Document Length:        125397 bytes

Concurrency Level:      5
Time taken for tests:   52.549041 seconds
Complete requests:      100
Failed requests:        0
Write errors:           0
Total transferred:      12559900 bytes
HTML transferred:       12539700 bytes
Requests per second:    1.90 [#/sec] (mean)
Time per request:       2627.452 [ms] (mean)
Time per request:       525.490 [ms] (mean, across all concurrent requests)
Transfer rate:          233.40 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:      195  232  45.7    211     417
Processing:  1687 2356 401.0   2257    3609
Waiting:      342  620 240.6    546    1756
Total:       1933 2589 400.7   2487    3815

Percentage of the requests served within a certain time (ms)
  50%   2487
  66%   2611
  75%   2798
  80%   2903
  90%   3090
  95%   3475
  98%   3791
  99%   3815
 100%   3815 (longest request)

Bien, pues a partir de esta información he hecho un pequeño estudio estadístico para valorar el cambio de rendimiento.  Se hicieron dos baterías de pruebas, primero con eAccelerator desactivado y después activado. En cada grupo se lanzaron diez tests utilizando “ab” con 100 peticiones. Vale, sí, la muestra es pequeña, pero veréis que suficiente para ver el cambio. Estos fueron los resultados.

eaceleratorstats.gif

La última fila establece los porcentajes de las pruebas con eAcclerator respecto de no utilizarlo. Interpretemos los resultados:

  • Tiempo total: utilizando el acelerador las pruebas se han ejecutado en el 73% del tiempo empleado al no utilizarlo. Eso supone un 27% menos de tiempo, una cifra nada despreciable.
  • Peticiones por segundo: con eAccelerator se ejecuta un 36% más de peticiones por segundo que sin él. Sobran más comentarios.
  • Tiempo mínimo: El tiempo mínimo empleado para atender una petición desciende un 22% al utilizar eAccelerator.
  • Tiempo máximo: este valor es sumamente llamativo, con eAccelerator se necesita un 42% menos de tiempo en el peor de los casos.

¿Necesitas más argumentos? 😛 . Otro día hablaremos de memcached.

El pasado de las redes sociales

Hace unas semanas quedé a tomar unas cañas con un viejo conocido de la Red, el señor Juanma Evaristo. Nos conocimos allá por el año 2000 en Madrid, en una cena organizada por Metropoli2000, hoy MetropoliGlobal, el origen de muchos de los webmasters de este país (Yonkis o ElOtroLado por ejemplo), por aquél entonces no era fácil ni barato acceder a un hosting. Nos sentamos juntos y dió la casualidad que era de Valencia. En aquella época era aficcionado al atletismo y llevaba la web CorreryTirar.com, hoy desaparecida. A pesar de vivir en la misma ciudad no nos habíamos vuelto a ver desde entonces, sí que habíamos mantenido el contacto durante estos años, bien por email bien por “redes sociales”, pero todos los intentos de quedar se quedaban en pura palabrería. Al final lo conseguimos y su primer comentario nada más vernos fue

Tío, con la movida de las redes sociales hoy en día, creía que ya serías millonario. Vosotros fuisteis los primeros.

Ojalá, le dije 😛 . Las cosas me van muy bien, pero no tanto 🙂 .

La verdad es que tenía razón. Hoy que se habla tanto de Facebook y de Tuenti  (los líderes), me hace gracia pensar que nosotros tuvimos un dia la potencia que hoy tienen ellos. Cuando aún nadie había acuñado el término “redes sociales”, antes de que existiese ningún otro portal ni “social” ni de “contactos”, existió GenteIRC.com (hoy GenteLive.com). Así éramos en diciembre de 2000.

Fué en abril del año 2000 cuando mi socio Marcos y yo decidimos crear un portal donde la gente pudiese publicar su perfil, sus gustos, sus preferencias y, sobre todo, su foto. Ya en aquellos tiempos vimos que para las personas era muy importante publicar sus fotos y hacerse ver a los demás. Eran tiempos sin “messengers” ni webcams (¿quién no recuerda los horrores de intentar utilizar el NetMeeting?). De hecho comenzaba a aparecer la banda ancha y las tarifas planas.

En dos años generábamos 30 millones de páginas mensuales (auditadas por OJD) y teníamos un contrato de exclusividad con lo que entonces era Eresmas (después Wanadoo y ahora Orange). Como se puede ver en el siguiente gráfico, en abril de 2002 rondábamos el millón de páginas diario.

daily_usage_200204.png

Crecimos todo lo que pudimos por nuestra cuenta. En Eresmas no daban crédito a la fuerza que tenía GenteIRC, cómo devoraba páginas y se recibían cientos y cientos de registros nuevos cada día. Eran tiempos donde sólo interesaban las páginas vistas, el valor de una web eran las páginas que podía generar. Pero el apoyo que esperábamos que ellos nos diesen y nos ayudase a impulsar nuestro proyecto nunca llegó y nos quedamos sólos. Hubo acuerdos, promesas, estudios, proyectos de lanzamiento a nivel internacional del producto y mil y una situaciones más, pero la burbuja estalló y todo quedó en el aire, y un producto que necesitaba pasta para crecer quedó aparcado, y la única manera de continuarlo era con medios propios.  Obviamente eso no nos frenó, seguimos hacia adelante con ilusión y confianza en nuestra idea, pero llegó un momento en que aparecieron los grandes competidores a nivel mundial completando nuestro producto, con capital para invertir y desarrollar su negocio. Ahí no podíamos pelear, no era nuestra batalla. Nos quedamos en nuestro nicho, que nos funciona bien todavía, pero vemos con resignación cómo una idea que tuvimos nosotros antes que nadie, una idea que llegó en el momento adecuado, se ha quedado en el camino por no sé si falta de contactos, de interés o de compromiso por parte de quien tiene la pasta. Siempre pensamos aquello de “¿no se me podía haber ocurrido esto a mi?”, y cuando te ocurre no puedes aprovecharlo como se debería.

Según el informe 10 años de Internet España y el mundo de la consultora Tatum, ésta ha sido la evolución de la penetración de Internet en la población española hasta 2006. En 2008 se sitúa alrededor del 50%.

 penetracion_internet.gif

Según las estadísticas, la población española a principios de 2002 se situaba en torno a los 41 millones de habitantes. En 2008 éramos 46. Combinando esta información tendríamos unos 8 millones de internautas en 2001. En 2008 llegábamos a 23 millones, un 287%.

Otro dato más. Según Tatum, en 2003 el tiempo medio de conexión mensual era de 24horas. Hoy en día, según el EIAA (European Interactive Advertising Association),  Internet ha desplazado a la televisión como medio de entretenimiento más utilizado con 48 horas de conexión mensuales (12 semanales según el estudio).

Añadamos un último dato. En 2002 éramos unos privilegiados los que disponíamos de banda ancha (128k), la mayoría utilizaba un modem de 54k, hoy nos parecen lentos 3Mb 😐 . Antes, ver fotos era “lento“, hoy es como comer pipas.

En un país con el índice de bares por metro cuadro que tenemos y vistos los datos de penetración de Internet ¿alguien dudaba del éxito que tendrían las “redes sociales” tarde o temprano?. Ah, sí, los bares, aunque te tomes una caña, son un fenómeno de comunicación social al que no renunciaremos nunca, por mucho que se diga que Internet nos encierra, los bares siguen abriéndonos. Quedamos en un bar, charlamos, pasamos el rato, conocemos gente y nos relajamos después de un duro día de trabajo.

De todos modos ahí estamos todavía. El año que viene cumplimos ¡10 años! generando beneficios, ¿cuántos pueden decirlo mismo? 😀 .

Cajas con esquinas redondeadas con CSS y sin tablas

Uno de los problemas más importantes a los que se ha tenido que enfrentar un maquetador web en los últimos años ha sido el de crear cajas con bordes redondeados sólo con CSS, sin utilizar tablas. Puede parecer algo muy simple, pero sabemos que no lo es. Con tablas era extremadamente sencillo, pero con CSS se complica bastante hacer algo así:

redondo.jpg

La verdad es que hay muchísima literatura al respecto, pero la cosa sigue siendo bastante complicada. Aquí tienen un tutorial muy descriptivo, la idea queda clara, pero la práctica…

Al final dí con un sitio que es perfecto para estos momentos, de hecho lo he utilizado bastante los últimos años: roundedcornr.com.

roundedcornr.com

Desde roundedcornr.com puedes generar directamente el código html, css y las imágenes necesarias para tus cajas. Tienen hasta tres tipos completamente configurables, normales, con borde y con degradado, increíblemente completo todo, sólo tienes que indicar grosores en píxeles y colores y te lo genera todo. ¿El resto? copiar y pegar.