perf: el perfilador todo-en-uno
El binario perf como navaja suiza de la observabilidad de Linux: perf stat para contar eventos e interpretar el IPC, perf record y perf report para descubrir dónde se va el tiempo, perf top para el perfil en vivo, y la llamada perf_event_open que unifica por debajo la PMU, los tracepoints y los kprobes en un solo objeto: el evento.
Hay quien colecciona una herramienta para cada pregunta: una para contar fallos de caché, otra para ver la pila caliente, otra para rastrear llamadas al sistema. Linux resolvió esa dispersión con un solo binario que habla a la vez con la unidad de monitorización de rendimiento del procesador y con el subsistema de rastreo del kernel. perf cuenta, muestrea, rastrea y anota código máquina; es a la observabilidad lo que el bisturí a la cirugía. Y bajo sus decenas de subcomandos late una única abstracción —el evento— y una única llamada al sistema que la materializa.
- Contar eventos de una carga con
perf state interpretar el IPC. - Muestrear con
perf recordy navegar el perfil conperf report. - Ver el consumo en vivo por función con
perf top. - Entender que por debajo todo es la llamada
perf_event_open.
perf stat: el recuento exacto
perf stat no muestrea: cuenta. Envuelve la ejecución de un programa y, al terminar, te entrega el total exacto de cada evento durante esa ventana. Es el primer diagnóstico que debes correr, porque una sola línea —el IPC, instrucciones por ciclo— ya te dice si el problema es que la CPU trabaja mucho o que se pasa el día esperando memoria.
$ perf stat ./mi_programa
Performance counter stats for './mi_programa':
1234,56 msec task-clock # 0,998 CPUs utilized
12 context-switches # 9,720 /sec
3 cpu-migrations # 2,430 /sec
142 page-faults # 115,02 /sec
4.512.334.900 cycles # 3,655 GHz
8.901.223.145 instructions # 1,97 insn per cycle
1.678.442.001 branches # 1,359 G/sec
12.334.567 branch-misses # 0,73% of all branches
1,236900000 seconds time elapsed
Lee ese 1,97 insn per cycle: cada ciclo retira casi dos instrucciones, un IPC sano para código con buena localidad. Si ese número cayera a 0,4, la CPU estaría parada tres de cada cuatro ciclos esperando —típicamente memoria— y sabrías dónde mirar. Puedes elegir eventos con -e, repetir la medición para obtener desviación con -r, o contar toda la máquina con -a.
# cuenta eventos concretos, repite 5 veces y da media con desviacion
perf stat -e cycles,instructions,cache-misses -r 5 ./mi_programa
# cuenta en todo el sistema durante 10 segundos
perf stat -a sleep 10
perf record y perf report: dónde se va el tiempo
Contar dice cuánto; muestrear dice dónde. perf record interrumpe periódicamente la CPU, anota el puntero de instrucción y, con -g, la pila de llamadas completa, y lo vuelca todo a un fichero perf.data. Luego perf report te abre ese perfil en una interfaz navegable ordenada por peso.
# muestrea a 99 Hz, con pila de llamadas, toda la maquina, 10 s
perf record -F 99 -a -g -- sleep 10
# navega el perfil interactivo, o vuelcalo a texto
perf report --stdio
# Overhead Command Shared Object Symbol
# ........ ....... ................. ...............................
#
38,21% mi_prog mi_prog [.] hash_bucket
17,04% mi_prog [kernel.kallsyms] [k] copy_user_enhanced_fast_string
9,88% mi_prog libc.so.6 [.] __memmove_avx_unaligned
4,10% mi_prog mi_prog [.] parse_line
La columna Overhead es el porcentaje de muestras que cayeron en cada símbolo, es decir, la fracción de tiempo de CPU que se gasta allí. La marca [.] es espacio de usuario y [k] es kernel: aquí el 17% del tiempo se va copiando datos entre usuario y núcleo, una pista de que quizá se lee de más. Con la interfaz interactiva puedes entrar en hash_bucket y perf annotate te muestra el ensamblador con el porcentaje pegado a cada instrucción, hasta señalar la línea exacta que arde.
perf top: el perfil en vivo
Cuando no quieres capturar y analizar después, sino ver qué quema la CPU ahora mismo, perf top es el top de las funciones: una lista que se refresca en tiempo real con los símbolos más calientes del sistema, ordenados por porcentaje de muestras.
# perfil global en vivo; -p limita a un proceso
sudo perf top
sudo perf top -p $(pgrep mi_prog) -g
Es la herramienta de la sala de máquinas: un servidor va lento, lanzas perf top, y en dos segundos ves si el 60% se lo come una función de compresión, el spinlock de un cerrojo contendido o el manejador de una interrupción desbocada. No sustituye a perf record para el análisis profundo, pero es insuperable para la primera hipótesis.
Todo es perf_event_open
Cada subcomando anterior es azúcar sobre una única llamada al sistema: perf_event_open. Le pasas un struct perf_event_attr que describe el evento, y recibes un descriptor de fichero. Contar es leer ese descriptor; muestrear es mapear un búfer en anillo donde el núcleo deposita las muestras. Este es el perf stat mínimo, escrito a mano:
#include <linux/perf_event.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <unistd.h>
struct perf_event_attr attr = {
.type = PERF_TYPE_HARDWARE,
.config = PERF_COUNT_HW_INSTRUCTIONS,
.size = sizeof(attr),
.disabled = 1,
.exclude_kernel = 1, /* solo cuenta en espacio de usuario */
.exclude_hv = 1,
};
int fd = syscall(SYS_perf_event_open, &attr, 0, -1, -1, 0);
ioctl(fd, PERF_EVENT_IOC_RESET, 0);
ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
/* ... aqui corre la carga de trabajo a medir ... */
ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
long long instrucciones;
read(fd, &instrucciones, sizeof(instrucciones));
Esa .type y .config seleccionan el evento; para un tracepoint pondrías PERF_TYPE_TRACEPOINT y el identificador del punto; para un kprobe, el tipo dinámico que registró tracefs. El primer argumento es el atributo, luego el pid, la CPU y un descriptor de grupo. Para muestrear en vez de contar, rellenas .sample_freq y .sample_type, y en lugar de read mapeas con mmap un búfer en anillo del que drenas las muestras. Todo perf no es más que esta llamada, repetida y orquestada.
mindmap
root((perf))
contar
perf stat
IPC y eventos
muestrear
perf record
perf report
perf annotate
en vivo
perf top
por debajo
perf_event_open
buffer en anilloNo todo usuario puede leer la PMU ni muestrear el kernel. El sysctl kernel.perf_event_paranoid gobierna el permiso: con 2 solo mides tus propios procesos en espacio de usuario, con 1 añades datos del kernel, con 0 los contadores por CPU, y con -1 todo. Si perf record -a se queja de permisos, ese sysctl o la capacidad CAP_PERFMON es la puerta que hay que abrir. Además, perf vive en tools/perf del árbol y conviene que su versión case con la del kernel en ejecución.
Reflexiona sobre la unificación que acabas de presenciar, porque es una de las abstracciones más logradas del kernel. Antes de perf_event, cada fuente de información sobre el rendimiento vivía en su propio mundo con su propia interfaz: los contadores de hardware se leían por registros del modelo específico del procesador, el rastreo estático tenía su tubería, los breakpoints la suya. perf_event disolvió esa balcanización bajo un solo concepto: el evento. Un ciclo de reloj de la CPU, un fallo de caché de última línea, una conmutación de proceso, el retorno de vfs_read interceptado por un kprobe —fenómenos de naturalezas radicalmente distintas, unos medidos por silicio dedicado y otros por software del núcleo— se modelan todos como lo mismo: algo que ocurre, que se puede contar y sobre lo que se puede muestrear. Y una vez que son la misma clase de cosa, una sola llamada al sistema los abre, un solo descriptor de fichero los representa, un solo búfer en anillo transporta sus muestras, y una sola herramienta los combina. Ahí está la lección de diseño que trasciende a perf: el poder no viene de acumular funciones, sino de encontrar la abstracción correcta bajo la cual fenómenos aparentemente inconexos resultan ser instancias de una misma idea. Cuando puedes decir “un fallo de caché y una llamada al sistema son ambos eventos”, has ganado la capacidad de correlacionarlos, de contarlos juntos, de preguntarte qué pila de código estaba corriendo cuando el silicio falló una lectura de memoria. La navaja suiza no es potente por tener muchas hojas; es potente porque todas se pliegan sobre el mismo eje.
- Corre
perf statsobre un programa tuyo y sobreopenssl speed, compara sus IPC y explica qué te dice la diferencia sobre cuán limitada por memoria está cada carga. - Captura un perfil con
perf record -F 99 -gde una carga real y usaperf reportpara bajar hasta la función más caliente; luegoperf annotatesobre ella y localiza la instrucción exacta que concentra las muestras. - Lanza
perf topmientras generas carga constress-ngy describe qué símbolos del kernel emergen y por qué. - Escribe el programa en C con
perf_event_openque cuenta instrucciones de un bucle, y verifica que el total casa con el que reportaperf stat -e instructionssobre el mismo bucle. - Baja
kernel.perf_event_paranoidy documenta qué mediciones nuevas se te habilitan y qué riesgo de seguridad justifica que por defecto esté restringido.