--- title: "Cómo encontrar cuellos de botella con perf en Ubuntu 24.04" description: "Instala perf en Ubuntu 24.04, mide un proceso con perf stat, localiza las funciones que más CPU consumen con perf record y report y genera un flame graph." category: "Optimización de Rendimiento" language: es source: https://cubepath.com/es/docs/optimización-de-rendimiento/identificacion-de-cuellos-de-botella-con-perf updated: 2026-09-25 --- # Cómo encontrar cuellos de botella con perf en Ubuntu 24.04 perf es el perfilador oficial del kernel Linux. Toma muestras de lo que está ejecutando la CPU miles de veces por segundo y te dice en qué funciones, de tu aplicación, de sus bibliotecas o del kernel, se va el tiempo. En este tutorial instalarás perf en Ubuntu 24.04, lo usarás con un programa de ejemplo para localizar la función más costosa, perfilarás un proceso en marcha y todo el sistema, y generarás un flame graph para visualizar el resultado. ## 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`. - `git` para descargar las herramientas de flame graphs. Si tu servidor es una máquina virtual, es posible que los contadores de hardware (ciclos, instrucciones, fallos de caché) no estén disponibles. El perfilado de CPU funciona igualmente con eventos de software, como verás en el paso 3. ## Paso 1: Instalar perf perf se distribuye con las herramientas del kernel y debe coincidir con la versión del kernel en ejecución. Instala el paquete común y el específico de tu kernel, junto con `gcc` para compilar el ejemplo: ```bash sudo apt update sudo apt install linux-tools-common linux-tools-$(uname -r) gcc git ``` Si usas el kernel `generic` y quieres que perf se actualice con él, instala también `linux-tools-generic`. Comprueba la instalación: ```bash perf --version ``` ```output perf version 6.8.12 ``` Ubuntu restringe perf a usuarios con privilegios mediante `kernel.perf_event_paranoid`: ```bash sysctl kernel.perf_event_paranoid ``` ```output kernel.perf_event_paranoid = 4 ``` Con ese valor tendrás que ejecutar perf con `sudo`, que es lo que hace esta guía. No bajes este valor en un servidor compartido: permitiría a cualquier usuario observar la actividad de otros procesos. ## Paso 2: Crear un programa de ejemplo Para aprender a leer perf conviene un programa donde ya sabes dónde está el problema. Este ejemplo tiene dos funciones: una hace una operación costosa (una división por cada iteración interna) y otra hace un trabajo ligero. ```bash nano hotspot.c ``` ```c #include #include #define N 200000 __attribute__((noinline)) static unsigned long slow_sum(const unsigned *data, size_t n) { unsigned long s = 0; for (size_t i = 0; i < n; i++) for (unsigned j = 1; j <= 2000; j++) s += data[i] % j; return s; } __attribute__((noinline)) static unsigned long fast_sum(const unsigned *data, size_t n) { unsigned long s = 0; for (int r = 0; r < 200; r++) for (size_t i = 0; i < n; i++) s += data[i]; return s; } int main(void) { unsigned *data = malloc(N * sizeof *data); if (!data) return 1; for (size_t i = 0; i < N; i++) data[i] = (unsigned)rand(); printf("%lu %lu\n", slow_sum(data, N), fast_sum(data, N)); free(data); return 0; } ``` Compílalo con optimizaciones, símbolos de depuración (`-g`) y puntero de marco (`-fno-omit-frame-pointer`), que permite a perf reconstruir la pila de llamadas de forma barata: ```bash gcc -O2 -g -fno-omit-frame-pointer -o hotspot hotspot.c ``` La ejecución tarda unos segundos, según tu CPU. ## Paso 3: Medir el proceso con perf stat `perf stat` ejecuta un comando y cuenta eventos durante toda su ejecución. Es la primera medición que conviene hacer, porque dice si el programa está limitado por la CPU o pasa el tiempo esperando: ```bash sudo perf stat ./hotspot ``` ```output Performance counter stats for './hotspot': 5,812.44 msec task-clock # 0.999 CPUs utilized 9 context-switches # 1.548 /sec 0 cpu-migrations # 0.000 /sec 250 page-faults # 43.012 /sec 23,114,560,212 cycles # 3.977 GHz 2,811,490,207 instructions # 0.12 insn per cycle ... 5.818203114 seconds time elapsed 5.809882000 seconds user 0.001998000 seconds sys ``` Cómo leerlo: - **CPUs utilized** cerca de 1 en un programa de un solo hilo significa que está limitado por la CPU. Un valor muy por debajo indica que espera a disco, red o bloqueos, y entonces strace o las herramientas de E/S son mejores que un perfil de CPU. - **insn per cycle** (instrucciones por ciclo) bajo, por debajo de 1, sugiere que la CPU espera a memoria o a instrucciones lentas como la división de este ejemplo. - **user** frente a **sys**: casi todo el tiempo está en modo usuario, en el código del programa. Si en tu máquina virtual aparece `` en `cycles` e `instructions`, el hipervisor no expone los contadores de hardware. El resto de la guía funciona igual con el evento de software `cpu-clock`. ## Paso 4: Localizar las funciones costosas con perf record y perf report `perf record` toma muestras periódicas de qué se está ejecutando y las guarda en `perf.data`. Usa `-F 99` (99 muestras por segundo, suficiente y con poca sobrecarga) y `-g` para registrar la pila de llamadas: ```bash sudo perf record -F 99 -g ./hotspot ``` ```output [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.067 MB perf.data (577 samples) ] ``` Si tu VPS no tiene contadores de hardware, añade `-e cpu-clock` para muestrear con el temporizador: ```bash sudo perf record -e cpu-clock -F 99 -g ./hotspot ``` Analiza el resultado en modo texto. `--no-children` asigna a cada función solo el tiempo que pasa en su propio código, sin sumar el de las funciones a las que llama, que es lo más directo para encontrar el punto caliente: ```bash sudo perf report --stdio --no-children ``` ```output # Overhead Command Shared Object Symbol # ........ ....... ................ ........................... # 96.53% hotspot hotspot [.] slow_sum | ---__libc_start_main main slow_sum 2.95% hotspot hotspot [.] fast_sum 0.17% hotspot [kernel.kallsyms] [k] clear_page_erms ... ``` `[.]` indica código de usuario y `[k]` código del kernel. El informe confirma que `slow_sum` consume el 96 % del tiempo, aunque `fast_sum` recorre los datos 200 veces: el coste está en la división dentro del bucle interno, no en el número de accesos a memoria. Para ver qué líneas exactas de la función son costosas, usa `perf annotate`. Gracias a `-g` al compilar, perf muestra el código fuente junto a las instrucciones: ```bash sudo perf annotate --stdio slow_sum | head -n 40 ``` Las instrucciones con mayor porcentaje aparecerán junto a la instrucción de división (`div`) que genera `data[i] % j`. Sin `--stdio`, `perf report` abre una interfaz interactiva: navega con las flechas, pulsa `Enter` para expandir una función y `a` para anotarla. ## Paso 5: Perfilar un proceso en marcha o todo el sistema En un servidor real normalmente no lanzas el programa desde perf, sino que investigas un servicio que ya consume CPU. Primero, `perf top` muestra en tiempo real las funciones más activas de todo el sistema, como `top` pero por función: ```bash sudo perf top ``` Pulsa `q` para salir. Si ves que un proceso concreto domina, obtén su PID (por ejemplo con `pgrep -a nginx`) y graba 30 segundos de muestras solo de ese proceso. El comando `sleep 30` marca la duración de la captura: ```bash sudo perf record -F 99 -g -p your_pid -- sleep 30 sudo perf report --stdio --no-children | head -n 50 ``` Cuando no sabes qué proceso es el responsable, graba todo el sistema con `-a` y ordena el informe por proceso y biblioteca: ```bash sudo perf record -F 99 -a -g -- sleep 30 sudo perf report --stdio --no-children --sort comm,dso | head -n 30 ``` ```output # Overhead Command Shared Object 41.20% php-fpm8.3 libphp8.3.so 18.75% mysqld mysqld 9.10% php-fpm8.3 [kernel.kallsyms] ... ``` Este resumen te dice rápidamente si el tiempo se va en la aplicación, en la base de datos o en el kernel (red, sistema de ficheros). ### Pilas de llamadas incompletas Ubuntu 24.04 compila sus paquetes con puntero de marco, así que `-g` reconstruye bien las pilas de la mayoría de programas del sistema. Si perfilas un binario compilado sin él (muchos binarios de terceros), las pilas aparecerán cortadas o con `[unknown]`. En ese caso usa el método DWARF, que es más preciso pero genera ficheros mucho más grandes: ```bash sudo perf record -F 99 --call-graph dwarf -p your_pid -- sleep 10 ``` ## Paso 6: Generar un flame graph Un flame graph representa todas las pilas muestreadas en una sola imagen: cada rectángulo es una función, el ancho es proporcional al tiempo que aparece en las muestras y las funciones que llama se apilan encima. Las funciones más anchas en la parte superior son las que consumen la CPU directamente. Descarga los scripts de Brendan Gregg, el autor de la técnica: ```bash git clone https://github.com/brendangregg/FlameGraph.git ~/FlameGraph ``` Convierte las muestras de `perf.data` en texto, agrupa las pilas y genera el SVG: ```bash sudo perf script > out.perf ~/FlameGraph/stackcollapse-perf.pl out.perf > out.folded ~/FlameGraph/flamegraph.pl out.folded > flamegraph.svg ``` Comprueba que se ha generado: ```bash ls -lh flamegraph.svg ``` Descarga `flamegraph.svg` a tu equipo, por ejemplo con `scp`, y ábrelo en un navegador. Es interactivo: al hacer clic en una función se amplía su rama y puedes buscar funciones por nombre. ```bash scp your_user@your_server_ip:~/flamegraph.svg . ``` Para comparar antes y después de una optimización, genera un flame graph en cada estado con la misma duración de captura y compáralos, o usa `difffolded.pl` del mismo repositorio para obtener un flame graph diferencial. ## Paso 7: Validar una optimización El objetivo de perfilar es cambiar algo y comprobar que mejora. En el ejemplo, el cálculo de `slow_sum` no puede evitar la división, pero en una aplicación real la corrección típica es sacar trabajo repetido de un bucle, cachear un resultado o cambiar de algoritmo. Después de cada cambio: 1. Repite `perf stat` varias veces con `-r` para obtener la media y la variación: ```bash sudo perf stat -r 5 ./hotspot ``` 2. Compara el tiempo total (`seconds time elapsed`) y la variación indicada con `+-`. 3. Repite `perf record` y `perf report` para ver si el punto caliente ha cambiado de sitio. Cambia una sola cosa cada vez; si cambias varias, no sabrás cuál ha tenido efecto. ## Solución de problemas **`WARNING: perf not found for kernel ...`.** El paquete `linux-tools` no corresponde al kernel en ejecución, normalmente tras una actualización del kernel sin reiniciar o al revés. Instala `linux-tools-$(uname -r)` o reinicia al kernel nuevo. **`Access to performance monitoring and observability operations is limited`.** Estás ejecutando perf sin `sudo` con `kernel.perf_event_paranoid` en 4. Usa `sudo`. **`` en ciclos e instrucciones, o `perf record` sin muestras.** La máquina virtual no expone la PMU. Usa `-e cpu-clock`. **Funciones como `[unknown]` o direcciones hexadecimales.** El binario no tiene tabla de símbolos (se ha aplicado `strip`) o no tiene puntero de marco. Instala los símbolos de depuración del paquete si existen, compila con `-g -fno-omit-frame-pointer` o usa `--call-graph dwarf`. **`perf.data` enorme.** Reduce la frecuencia (`-F 49`), la duración de la captura o evita `--call-graph dwarf` salvo que lo necesites. ## Conclusión Has usado `perf stat` para saber si un proceso está limitado por la CPU, `perf record` y `perf report` para encontrar la función responsable, `perf annotate` para bajar hasta la línea de código, y has perfilado procesos en marcha y todo el sistema antes de visualizarlo en un flame graph. Como siguientes pasos, puedes combinar perf con una prueba de carga con wrk para perfilar un servicio web bajo tráfico, usar strace cuando `perf stat` indique que el proceso espera en lugar de calcular, o explorar `perf sched` para analizar latencias del planificador.