strace muestra las llamadas al sistema que hace un proceso (abrir ficheros, leer, escribir, conectar a la red) y ltrace muestra las llamadas a funciones de bibliotecas compartidas como malloc o strlen. Con ambas puedes averiguar por qué un programa tarda en arrancar, qué fichero está buscando sin encontrarlo o en qué operación se queda bloqueado, sin tener su código fuente. En este tutorial usarás strace y ltrace en Ubuntu 24.04 con ejemplos que puedes reproducir y aprenderás a aplicarlos a un proceso en producción.
Requisitos previos
Para seguir esta guía necesitas:
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios
sudo. - Conocimientos básicos de la línea de comandos.
Advertenciastrace y ltrace detienen el proceso en cada llamada que rastrean, lo que puede ralentizarlo mucho (a veces más de 10 veces). Úsalos en producción durante periodos cortos y con filtros, como se explica en el paso 4.
Paso 1: Instalar strace, ltrace y gcc
strace y ltrace están en los repositorios de Ubuntu. Instala también gcc, que usarás para compilar un programa de ejemplo en el paso 5:
sudo apt update
sudo apt install strace ltrace gcc
Comprueba que strace funciona:
strace -V
strace -- version 6.8
...
Paso 2: Obtener un resumen de llamadas al sistema
La opción más útil para empezar es -c, que en lugar de imprimir cada llamada cuenta cuántas veces se ha hecho cada una, cuántas han fallado y cuánto tiempo han ocupado. Compara dos formas de copiar 1 MB con dd: en bloques de 1 byte y en un solo bloque de 1 MB.
strace -c dd if=/dev/zero of=/tmp/test.bin bs=1 count=1000000
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
50.84 1.402113 1 1000000 write
49.14 1.355290 1 1000003 read
0.01 0.000245 13 18 mmap
...
------ ----------- ----------- --------- --------- ----------------
100.00 2.758012 1 2000079 5 total
Ahora con un bloque grande:
strace -c dd if=/dev/zero of=/tmp/test.bin bs=1M count=1
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
72.40 0.000921 921 1 write
...
------ ----------- ----------- --------- --------- ----------------
100.00 0.001272 16 79 5 total
El mismo trabajo pasa de dos millones de llamadas al sistema a menos de cien. Este es el patrón más habitual que revela strace: una aplicación que lee o escribe en trozos diminutos, sin buffer, y pasa su tiempo cambiando entre modo usuario y modo kernel.
Por defecto -c mide el tiempo de CPU del sistema dentro de cada llamada. Si lo que te interesa es cuánto tiempo real ha estado esperando el proceso (por ejemplo, a la red o al disco), añade -w para medir tiempo de reloj:
strace -c -w curl -s -o /dev/null https://www.cubepath.com
Ordena el resumen por número de llamadas con -S calls o por tiempo con -S time (el valor por defecto).
Paso 3: Ver cada llamada con su duración
Cuando el resumen apunta a un tipo de llamada, pasa a la traza detallada. Estas opciones son las que más se usan para rendimiento:
-f: sigue también a los procesos hijos y a los hilos.-tt: marca de tiempo con microsegundos al inicio de cada línea.-T: tiempo pasado dentro de cada llamada, entre< >al final de la línea.-e trace=...: limita la traza a ciertas llamadas o grupos (%file,%network,%process,%memory).-y: muestra la ruta del fichero o el socket en lugar del número de descriptor.-o fichero: escribe la traza en un fichero en lugar de en la terminal.
Por ejemplo, para ver solo la actividad de red de curl con tiempos:
strace -f -tt -T -y -e trace=%network curl -s -o /dev/null https://www.cubepath.com
10:42:17.311204 socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 5<TCP:[118233]> <0.000031>
10:42:17.311592 connect(5<TCP:[118233]>, {sa_family=AF_INET, sin_port=htons(443), ...}, 16) = -1 EINPROGRESS (Operation now in progress) <0.000089>
...
Encontrar las llamadas más lentas
Para un programa que tarda en arrancar, guarda la traza completa en un fichero:
strace -f -tt -T -o /tmp/trace.txt python3 -c "import json"
Después ordena las líneas por la duración que aparece al final. Este comando extrae el valor entre < > y muestra las diez llamadas más lentas:
awk -F'<' '/<[0-9.]+>$/ {print $NF "\t" $0}' /tmp/trace.txt | sort -rn | head -n 10
Las llamadas que tardan milisegundos, y no microsegundos, son las candidatas: un connect a un servidor lento, un read sobre un disco de red, un futex donde un hilo espera a otro o un poll que espera respuesta.
Encontrar ficheros que no existen
Un arranque lento a menudo se debe a que el programa busca un fichero o módulo en decenas de rutas antes de encontrarlo. La opción -Z muestra solo las llamadas que fallan:
strace -f -Z -e trace=%file python3 -c "import json" 2>&1 | head -n 20
openat(AT_FDCWD, "/usr/lib/python312.zip", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/usr/lib/python3.12/json/__pycache__/...", ...) = -1 ENOENT (No such file or directory)
...
Cientos de ENOENT seguidos indican rutas de búsqueda (PYTHONPATH, LD_LIBRARY_PATH, include_path de PHP) demasiado largas o mal ordenadas.
Paso 4: Rastrear un proceso que ya está en marcha
Para diagnosticar un servicio en producción no necesitas reiniciarlo: strace puede engancharse a un proceso existente con -p. En Ubuntu, la política de seguridad Yama solo permite rastrear procesos ajenos con sudo.
Obtén el PID del proceso, por ejemplo de un worker de PHP-FPM o del proceso principal de tu aplicación:
pgrep -a php-fpm
Recoge un resumen durante 30 segundos. timeout envía una señal a strace al terminar el tiempo, strace se desengancha limpiamente y el proceso sigue funcionando:
sudo timeout 30 strace -c -f -p your_pid
Si el resumen muestra mucho tiempo en llamadas de red o de ficheros, mira el detalle solo de esas llamadas, guardándolo en un fichero:
sudo timeout 10 strace -f -tt -T -y -e trace=%network,%file -p your_pid -o /tmp/app-trace.txt
Para un proceso que parece colgado, basta con engancharse sin filtros y mirar la última línea: si se queda en read(7<socket:...>, está esperando datos de una conexión; si es futex(..., espera un bloqueo de otro hilo.
sudo strace -p your_pid
Pulsa Ctrl+C para desengancharte. El proceso no se detiene.
Paso 5: Analizar llamadas a bibliotecas con ltrace
ltrace funciona como strace pero intercepta las llamadas a funciones de bibliotecas compartidas. Tiene una limitación importante en Ubuntu: la versión incluida (0.7.3) no reconoce la tabla de enlaces que genera gcc cuando compila con protección de flujo de control (-fcf-protection) y enlace inmediato, que es la configuración por defecto de los paquetes de Ubuntu. Por eso, con muchos binarios del sistema ltrace no muestra ninguna llamada. Donde mejor funciona es con programas que compilas tú.
Crea un programa de ejemplo que reserva memoria en un bucle:
nano demo.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
size_t total = 0;
for (int i = 0; i < 10000; i++) {
char *buf = malloc(64);
snprintf(buf, 64, "item-%d", i);
total += strlen(buf);
free(buf);
}
printf("%zu\n", total);
return 0;
}
Compílalo desactivando esa protección y con enlace perezoso para que ltrace pueda interceptar las llamadas:
gcc -O0 -fcf-protection=none -Wl,-z,lazy -o demo demo.c
Obtén un resumen con -c:
ltrace -c ./demo
88890
% time seconds usecs/call calls function
------ ----------- ----------- --------- --------------------
27.31 0.412780 41 10000 snprintf
24.66 0.372712 37 10000 malloc
24.12 0.364547 36 10000 free
23.90 0.361204 36 10000 strlen
0.01 0.000121 121 1 printf
------ ----------- ----------- --------- --------------------
100.00 1.511364 40001 total
El resumen confirma 10 000 reservas y liberaciones de memoria. En una aplicación real, este patrón señala que conviene reutilizar un buffer en lugar de pedir memoria en cada iteración. Los tiempos absolutos están inflados por el propio rastreo; fíjate en las proporciones y en el número de llamadas.
Para seguir solo unas funciones concretas, con su duración, usa -e y -T:
ltrace -T -e malloc+free ./demo 2>&1 | head -n 6
demo->malloc(64) = 0x5a1c3f2e22a0 <0.000196>
demo->free(0x5a1c3f2e22a0) = <nil> <0.000125>
demo->malloc(64) = 0x5a1c3f2e22a0 <0.000118>
...
Con -S, ltrace muestra además las llamadas al sistema intercaladas con las de biblioteca, lo que ayuda a ver qué función de biblioteca provoca cada llamada al kernel:
ltrace -S ./demo 2>&1 | tail -n 5
Si ltrace no muestra nada con un binario del sistema, usa perf o uftrace, que no dependen de la tabla de enlaces.
Paso 6: Un método de trabajo
Estas herramientas rinden más si sigues siempre el mismo orden:
- Resumen primero.
strace -c(o-c -w) sobre el proceso o comando te dice qué tipo de llamada domina. - Detalle filtrado después. Rastrea solo ese grupo (
-e trace=%file,%network...) con-Ty-tt, guardando en fichero con-o. - Ordena por duración y busca repeticiones: miles de llamadas idénticas o unas pocas que tardan milisegundos.
- Confirma con otra herramienta. Si el tiempo se va en CPU dentro de la aplicación y no en llamadas al sistema, strace no lo verá; ese es trabajo de
perf.
Como alternativa de bajo coste a strace en producción, perf trace rastrea llamadas al sistema sin detener el proceso en cada una. Está incluido en los paquetes linux-tools del kernel. Este comando lo ejecuta durante 10 segundos y, al recibir la señal de interrupción, imprime un resumen por llamada:
sudo apt install linux-tools-common linux-tools-$(uname -r)
sudo timeout -s INT 10 perf trace -s -p your_pid
Solución de problemas
strace: attach: ptrace(PTRACE_SEIZE, ...): Operation not permitted. Estás intentando rastrear un proceso que no es hijo de tu shell sin privilegios. Usa sudo. Dentro de un contenedor Docker, además, el contenedor necesita la capacidad SYS_PTRACE.
La traza es enorme e ilegible. Filtra con -e trace= por grupo o por llamadas concretas (-e trace=openat,read), usa -o para enviar la salida a un fichero y limita la duración con timeout.
El servicio va muy lento mientras lo rastreas. Es el coste del rastreo con ptrace. Reduce el tiempo de captura, filtra las llamadas o usa perf trace.
ltrace no muestra llamadas. El binario está compilado con -fcf-protection y enlace inmediato, como la mayoría de paquetes de Ubuntu. Recompila tu programa como en el paso 5 o usa otra herramienta.
Conclusión
Has usado strace para resumir y cronometrar llamadas al sistema, localizar las más lentas, detectar ficheros que no existen y engancharte a procesos en marcha, y ltrace para contar llamadas a bibliotecas en un programa propio. Como siguientes pasos, puedes perfilar el uso de CPU de la aplicación con perf, generar carga controlada con wrk mientras rastreas un servicio web o revisar los límites de E/S del disco si las lecturas y escrituras dominan el resumen.
