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.

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:

  1. Resumen primero. strace -c (o -c -w) sobre el proceso o comando te dice qué tipo de llamada domina.
  2. Detalle filtrado después. Rastrea solo ese grupo (-e trace=%file, %network...) con -T y -tt, guardando en fichero con -o.
  3. Ordena por duración y busca repeticiones: miles de llamadas idénticas o unas pocas que tardan milisegundos.
  4. 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.