Cerrar descriptores de fichero abiertos en tiempo de ejecución en GNU/Linux

Cuando una aplicación tiene muchos ficheros abiertos y hace un uso intenso de CPU puede ser útil cerrar determinados ficheros o directorios con los que este trabajando sin tener que matar el proceso. Por ejemplo, si logrotate tuviera un directorio con varios millones de ficheros logs, este consumiría una tasa muy elevada de recursos, entre ellos un tiempo de espera de entrada y salida elevado.

Lo interesante en este caso sería quitarle trabajo cerrando algunos o todos los descriptores de ficheros abiertos con los que trabaja. Veamos como sería el proceso basándonos en dicho supuesto con logrotate. Para cerrar ficheros abiertos de un determinado proceso utilizaremos gdb.

Consumo de procesos.

Tasks: 130 total,   2 running, 127 sleeping,   0 stopped,   1 zombie
Cpu(s):  9.9%us,  9.0%sy,  3.7%ni, 83.3%id,  0.6%wa,  0.0%hi,  0.1%si,  0.0%st
Mem:  32880924k total, 31808928k used,  1071996k free,  1091368k buffers
Swap:        0k total,        0k used,        0k free,  7597524k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 7227 root      30  10 2335m 2.2g 1048 R 250.1  16.9   2410:41 logrotate
32596 root      20   0  124g  19g 4.4g S  2.0 62.2  43131:35 java
32643 root      20   0 4359m 1.0g 7336 S  2.0  3.2   1515:56 java
    1 root      20   0 19232  992  708 S  0.0  0.0   3:10.78 init
...

El problema de CPU con logrotate viene del número de ficheros de uno de sus directorios configurados para rotar logs.

find /var/log/logstash/ | wc -l
3596509

Limpiar fichero de estado de logrotate.

wc -l /var/lib/logrotate.status
3159814 /var/lib/logrotate.status
 
du -m /var/lib/logrotate.status
598     /var/lib/logrotate.status
 
grep -ci logstash /var/lib/logrotate.status
2153799
 
# Eliminamos las lineas que contengan el patrón logstash.
sed -i '/logstash/d' /var/lib/logrotate.status
grep -ci logstash /var/lib/logrotate.status
0
 
du -m /var/lib/logrotate.status
1       /var/lib/logrotate.status

Listar los descriptores de ficheros para el proceso logrotate 7227 (Número 3)

ls -lt /proc/7227/fd
total 0
lr-x------ 1 root root 64 Sep 17 14:33 0 -> /dev/null
l-wx------ 1 root root 64 Sep 17 14:33 1 -> /dev/null
l-wx------ 1 root root 64 Sep 17 14:33 2 -> /dev/null
lr-x------ 1 root root 64 Sep 17 14:33 3 -> /var/log/logstash

Cerrar un descriptor de fichero abierto con la llamada al sistema close(), para el ejemplo el número 3.

echo -e "call close(3)\nquit" > gdb_commands
gdb -p 7227 --batch -x gdb_commands

NOTA: Por supuesto también se puede hacer desde la terminal de gdb.

Comprobamos que el uso de recursos a mermado.

Cpu(s): 7.2%us,  0.3%sy,  0.0%ni, 72.3%id,  0.0%wa,  0.0%hi,  0.1%si,  0.0%st