Archivo de Etiquetas de 'scripting'

Mandato PS con salida “personalizada”

Una opción del mandato ps de los sistemas operativos tipo Unix que no suele estar muy bien documentada y que en mi opinión es tremendamente útil es la que permite personalizar la salida del mandato para que muestre la información que te interesa de cada proceso.

La opción es -o, y acepta como argumentos una gran cantidad de posibilidades, que normalmente se encuentran descritas en la página del manual como standard format specifiers (o directamente no se encuentran :D ).

Un ejemplo de cómo lucen normalmente las órdenes de tipo ps que suelo lanzar en mis terminales:

$ ps fax -o user,uid,pid,ppid,pgrp,%cpu,%mem,rss,vsize,size,tname,etime,start_time,args
USER       UID   PID  PPID  PGRP %CPU %MEM   RSS    VSZ    SZ TTY          ELAPSED START COMMAND
root         0  3434     1  3434  0.0  0.1   552   5404   464 ?        12-22:14:08 Feb23 /usr/sbin/sshd
root         0 14466  3434 14466  0.0  0.5  2572   8124   512 ?              36:04 12:13  \_ sshd: deigote [priv]
deigote   1000 14469 14466 14466  0.0  0.2  1440   8280   668 ?              36:01 12:13      \_ sshd: deigote@pts/2
deigote   1000 14470 14469 14470  0.0  0.6  3344   6548  2172 pts/2          36:01 12:13          \_ -bash
root         0 16310 14470 16310  0.1  0.2  1196   4292   472 pts/2          00:06 12:49              \_ su -
root         0 16311 16310 16311  0.0  0.3  1688   4740   364 pts/2          00:03 12:49                  \_ -su
root         0 16315 16311 16315  0.0  0.2   992   4148   580 pts/2          00:00 12:50                      \_ ps fax -o user,uid,pid,ppid,pgrp,%cpu,%mem,rss,vsize,size,tname,etime,start_time,args

Y, de hecho, un par de alias que suelo tener siempre definidos, entre otros, son:

alias ps='ps fax -o user,uid,pid,ppid,pgrp,%cpu,%mem,rss,vsize,size,tname,etime,start_time,args'
alias psg='ps | head -n 1 && ps | grep'

Obtener las extensiones de fichero existentes en un directorio

Una mini-receta rápida para obtener, de forma recursiva, todas las extensiones de fichero que existan a partir de un directorio dado:

find directorio_raiz -type f -exec sh -c 'basename $0 | sed "s/.*\.//"' {} \; | sort | uniq

El truco viene del archipoderoso sed, que permite obtener las extensión de un fichero mediante la orden sed “s/.*\.//”, y funciona que yo sepa, para cualquier Unix con una shell compatible con sh y find, basename y sed instalados.

Refréscame esa caché

Wordpress, el CMS que uso para gestionar mi blog, dispone de varios plugins para acelerar su carga mediante cachés, algo muy necesario en general, y más si, como yo, eres usuario de un servicio de hosting barato.

En mi caso, el plugin de caché que estoy usando es WPSuperCache, que guarda una copia estática de cada página generada por Wordpress, y la sirve directamente en próximas peticiones, siempre y cuando el usuario no esté registrado. Esto evita todas las peticiones contra la base de datos por parte de Wordpress. Además, opcionalmente, el plugin permite guardar las páginas estáticas con una estructura de directorios equivalente a la URL que las representa, y proporciona reescrituras de URL para llegar a dichas páginas directamente, evitando toda intervención por parte de Wordpress (y evitando, por lo tanto, la ejecución de código PHP en el servidor). Finalmente, además, te da la opción de guardar los archivos HTML comprimidos con GZip. Todas estas opciones aceleran notablemente la carga de la página, puesto que el único trabajo que debe realizarse es el del servidor web: encontrar la página html y servirla.

WPSuperCache, como la mayoría de plugins de caché, cuenta con un borrado automático de la caché cada cierto tiempo. Cada vez que se ejecuta esta tarea, las siguientes peticiones (si las hay :-D ) a las distintas páginas del blog harán que se regenere la caché automáticamente.

Sin embargo, cuando tienes pocas visitas, no es buena idea hacer que la caché sea regenerada por los lectores. En ese caso, el mío :-) , es probable que un lector obtenga una página no cacheada en vez de una que sí lo está. Una posiblidad es aumentar en gran medida el intervalo de borrado de la caché, pero en general es preferible tener una caché actualizada. La otra posiblidad es, además de borrar la caché, regenerarla automáticamente, de tal manera que todos los lectores del blog se beneficien de la caché del mismo.

En mi caso, he escrito un script que visita todos los enlaces de una página web basándose en el sitemap de la misma, siempre que este cumpla el protocolo descrito por Sitemaps.org. El código es el siguiente:

#!/bin/bash
# Visit all the links provided by a sitemap file.
# Diego Toharia - http://blog.deigote.com

# Verify parameter
if [[ $# -ne 1 ]] ; then
    echo "Error: first parameter (sitemap URL) missing"
    echo "Usage: `basename $0` "
    exit 1
fi

url=$1
links=`wget -q -O - http://blog.deigote.com/sitemap.xml | awk '{ print $1 }' | grep "^http" | awk 'BEGIN { FS=">" } { print $2 }' | awk 'BEGIN { FS="<" } { print $1 }'`

for i in $links ; do
	echo "Visiting " $i
	wget -q -O - $i > /dev/null
done

Seguro que hay formas más elegantes de hacerlo, pero awk es tan potente que es difícil resistirse a usarlo :-D . Lo he llamado visit_site_by_sitemap, original que es uno :roll: .

Una vez tenemos el script, lo más fácil es programar una tarea del cron que cada cierto tiempo (preferiblemente, en franjas horarias con el menor tráfico posible) borre la caché y ejecute el script pasándole como parámetro la URL del sitemap. Por cierto que, en mi caso, el sitemap lo genera el plugin Google Sitemap Generator. El mandato para la regla en mi caso sería algo como:

cd path/al/blog/wp-content ; find cache -type f -name '*.html' -exec rm {} \; -o -name '*.gz' -exec rm {} \; ; ~/bin/visit_site_by_sitemap http://blog.deigote.com/sitemap.xml

La idea es borrar todos los ficheros html y gz generados por la caché y regenerar la misma visitando todos los enlaces proporcionados por el sitemap del blog.

Podemos hacer un par de pruebas que, sin ser demasiado científicas, nos dan una idea del beneficio que obtenemos. La primera sería ejecutar en local el script dos veces, previo borrado de la caché. La primera visitaría todos los enlaces sin disponer de caché, mientras que la segunda lo haría usando la caché.

$ cd path/al/blog/wp-content
$ find cache -type f -name '*.html' -exec rm {} \; -o -name '*.gz' -exec rm {} \;
$ time ~/bin/visit_site_by_sitemap http://blog.deigote.com/sitemap.xml
Visiting  http://blog.deigote.com/
...
real    4m12.102s
$ time ~/bin/visit_site_by_sitemap http://blog.deigote.com/sitemap.xml
Visiting  http://blog.deigote.com/
...
real	0m7.759s

Podemos ver que los resultados son espectaculares. De 4 minutos hemos pasado a 7 segundos. Sin embargo, estamos probando el caso óptimo, en el que la latencia es mínima y la velocidad de conexión con el servidor web es máxima, lo que hacen que la capacidad de proceso sea el único parámetro que influye en los resultados.

Si ejecutamos esta misma prueba en una máquina externa y situada en España, la misma desde la que estoy escribiendo, obtenemos unos resultados bien distintos: casi trece minutos (12m45) frente a casi 9 (8m44s).

Sin embargo, esto es culpa de la lamentable latencia y velocidad de conexión que Dreamhost tiene en España, ya que ejecutando la prueba en una máquina situada en USA (y de otro servicio de hosting distinto), se obtienen 3 minutos (3m02s) frente a 22 segundos, resultados más parecidos a la ejecución en local. Tendré que pensar seriamente en cambiar de hosting :cry: .

La otra prueba que se puede hacer es algo más realista: medir el tiempo de descarga de una única página, que es lo que hace un usuario cualquiera (no creo que tenga ningún lector tan fan como para visitarse todas las entradas del blog una detrás de otra :lol: ). En mi caso, he usado un navegador Firefox con la caché deshabilitada y con la extensión Firebug instalada, usando la pestaña Red de dicha extensión para medir el tiempo de carga del HTML de la página de inicio. He realizado varias pruebas con la caché vacía y con la caché llena y he obtenido de media unos 2.5 segundos frente a medio segundo respectivamente, lo cual no está mal. Sin embargo, el resto de elementos de la página (malditos emoticonos :evil: ) ralentiza la carga total de la misma en gran medida, aunque eso es otra historia :-) .

Gracias a Álvaro por su inestimable ayuda a la hora de lanzar el juego de pruebas :P en un servidor externo a españa.

Si la salida fuese un Excell, lo llamaríamos ingeniería del software

Lo que ha empezado como un simple conteo de ficheros para ver qué proyecto hacía que Eclipse fuese lento, ha terminado en todo un contador de líneas digno de aparecer en cualquier libro de ingeniería del software (nótese la cursiva, por favor :???: ), si no fuera porque es un script de consola bash con salida de texto simple.

Al grano:

#!/bin/bash
EXTENSIONES="$@"
ficheros_total=0
lineas_total=0
vacias_total=0
novacias_total=0
for ext in $EXTENSIONES ; do
   echo EXTENSION $ext
   ficheros=`find . -name "*.$ext" | wc -l`
   lineas=`find . -name "*.$ext" | xargs cat | wc -l`
   novacias=`find . -name "*.$ext" | xargs cat | awk 'BEGIN { lineas = 0 } { if ($0 != "") lineas++} END { print lineas }'`
   vacias=`find . -name "*.$ext" | xargs cat | awk 'BEGIN { lineas = 0 } { if ($0 == "") lineas++} END { print lineas }'`
   porcentaje=`echo $vacias $lineas | awk '{ print $1 / $2 * 100 }'`
   echo Tiene $ficheros ficheros de tipo $ext y $lineas lineas, de las cuales $novacias tienen contenido y $vacias son vacias \(un $porcentaje %\)
   ficheros_total=`expr $ficheros_total + $ficheros`
   lineas_total=`expr $lineas_total + $lineas`
   vacias_total=`expr $vacias_total + $vacias`
   novacias_total=`expr $novacias_total + $novacias`
done
if [[ $# -gt 1 ]] ; then
   porcentaje_total=`echo $vacias_total $lineas_total | awk '{ print $1 / $2 * 100 }'`
   echo TOTAL
   echo Tiene $ficheros_total ficheros en total, con $lineas_total lineas, de las cuales $novacias_total tienen contenido y $vacias_total son vacias \(un $porcentaje_total %\)
fi

Ejemplo de uso (guardandolo en un fichero cuenta_lineas.sh accesible por el path y con los permisos adecuados):

$ cd directorio_raiz_del_proyecto/ ; ./cuenta_lineas.sh java jsp xml
EXTENSION java
Tiene 388 ficheros de tipo java y 60035 lineas, de las cuales 47806 tienen contenido y 12230 son vacias (un 20.3714 %)
EXTENSION jsp
Tiene 792 ficheros de tipo jsp y 91354 lineas, de las cuales 75267 tienen contenido y 16088 son vacias (un 17.6106 %)
EXTENSION xml
Tiene 170 ficheros de tipo xml y 32282 lineas, de las cuales 30437 tienen contenido y 1846 son vacias (un 5.71836 %)
TOTAL
Tiene 1350 ficheros en total, con 183671 lineas, de las cuales 153510 tienen contenido y 30164 son vacias (un 16.4228 %)

Mola :mrgreen: aunque es mejorable sacando porcentajes (ver abajo :-D ), pero no he encontrado un mandato tipo expr que trabaje con floats :neutral: (a ver si algún comentarista me da el tip :-) )

EDITO: Ya se me ha ocurrido cómo hacer los porcentajes mientras contestaba :lol: actualizado el código y la salida del mismo

EDITO de nuevo: Si queréis añadir más filtros a las lineas vacías, tal y como sugiere Kortatu (que yo ya he barajado), tendréis que cambiar la línea

novacias=`find . -name "*.$ext" | xargs cat | awk 'BEGIN { lineas = 0 } { if ($0 != "") lineas++} END { print lineas }'`

por

novacias=`find . -name "*.$ext" | xargs cat | grep -v '^$' | grep -v '^\s*\}\s*$'  | grep -v '^\s*\*.**$' | grep -v '^\s*/\*\*.*$' | grep -v '^\s.//.*$'

Útil para código de la familia Java :D. ¡Gracias!