SDD 25 §9.5: el iterador BPF, medido — y H7 pasa a tener precio

Cae el quinto de los seis experimentos nombrados. Un `iter/task` que emite
registros binarios saca la foto de procesos cerca de 7x mas barato que el
barrido de /proc (6,9-8,0x en cuatro corridas de 33 rondas y las dos
direcciones), pero el numero que decide es otro: cuesta 1,75x el piso de
`readdir` contra 12,1x de /proc. Leer los campos deja de tener precio; lo que
se paga es enumerar.

Dos barreras comprobadas, no supuestas: el programa se ata por id de tipo BTF
del vmlinux (attach_btf_id=80137, prog_type=26) — sin DEBUG_INFO_BTF no hay a
que atarse, no hay version de esto que esquive H7 —, y hace falta
CAP_BPF+CAP_PERFMON, o sea que el consumidor es el supervisor, no la Card.

Y los dos caminos NO dicen lo mismo: 24 de 245 comm difieren, todos kworkers,
porque /proc SINTETIZA el nombre pegandole la workqueue. Un reemplazo vendido
como «la misma informacion mas rapido» tenia una diferencia de contenido, y
solo aparecio cotejando registro contra registro.

H7 deja de decir «pahole en el lab y un re-hasheo». Son cuatro cosas:
DEBUG_INFO_NONE=y en los .config sellados (BTF exige DWARF antes), el kernel
no declara la receta dwarves aunque este sellada, ese pahole es DINAMICO pese
a link="static" (INTERP + 5 NEEDED) y corre dentro del sandbox del build, y
BTF mete la version de pahole en la bit-repro con el lab fuera de hash_inputs.
Ya hay dos consumidores nombrados, no cero: este iterador y sched-ext.

Lo que sigue SIN medir es el precio en bytes del bzImage, que es como se pago
H1 — se mide construyendo, y esa es la primera tarea el dia que se decida.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
This commit is contained in:
Sergio
2026-08-31 14:08:40 +00:00
co-authored by Claude Opus 5
parent 8fef37dd19
commit 09168bf1e3
4 changed files with 511 additions and 9 deletions
+103 -9
View File
@@ -36,6 +36,10 @@ El espejo de este documento para el lado tawasuyu es
cada 100 ms vio **cero**; el proc connector, los 200. Y **el «copiar en O(1)» existe pero no en
ext4**: en xfs/btrfs un reflink de 512 MiB tarda 0,020 ms contra 512,7 ms de `read+write`
(§9.2).
5. **Cuando `/proc` sí se sondea entero, hay algo cerca de 7× más barato** (§9.5, medido
2026-08-31): un iterador BPF `task` saca la misma foto **al precio de listar el directorio**
—1,75× el piso de `readdir` contra 12,1× de `/proc`—. Su portón es `DEBUG_INFO_BTF`, que sigue
apagado, y H7 ahora dice **cuánto cuesta encenderlo** en vez de sólo que está apagado.
## 1. Método y condiciones — leer antes de citar un número
@@ -101,7 +105,9 @@ Leer el CPU propio por `/proc` cuesta **11× más** que `getrusage`. Y la mitad
abrir la ruta, no el texto (1 271 vs 2 692 con el fd cacheado).
**Barrido completo** (291 procesos vivos): `readdir` solo **106 µs**; `readdir` + `stat` de
cada pid **991 µs**, o sea **3,4 µs por proceso**.
cada pid **991 µs**, o sea **3,4 µs por proceso**. Ese barrido tiene alternativa medida: un
iterador BPF hace el mismo trabajo por **0,80 µs por proceso** contra 5,54 µs del mismo barrido
medido en esa corrida (§9.5), pero exige BTF (H7).
### T4 · Contabilidad por unidad: cgroup v2 es O(1) donde `/proc` es O(procesos)
@@ -466,8 +472,39 @@ Su precio, concreto:
reflink (§9) alimenta SDD 18 (wawafs), donde el store como FS vivo cambiaría el cociente.
- **H6 · No `madvise(MADV_HUGEPAGE)` en el camino de build** mientras `defrag=madvise` y la
granja corra apretada de RAM (T13).
- **H7 · `DEBUG_INFO_BTF` sigue apagado** hasta que haya un consumidor real de BPF; cuando lo
haya, la deuda es pahole en el lab y un re-hasheo, no un rediseño.
- **H7 · `DEBUG_INFO_BTF` sigue apagado — pero ya no por falta de número.** §9.5 midió qué
compra: un iterador `task` saca la foto de procesos **cerca de 7× más barato** que el barrido de
`/proc` (6,9-8,0× en cuatro corridas y dos órdenes), y —lo que de verdad decide— **al precio de
listar el directorio**: 1,75× el piso de `readdir`, contra 12,1× de `/proc`. Leer los campos deja de costar; sólo se paga enumerar.
El precio, en cambio, es más grande de lo que decía esta línea («pahole en el lab y un
re-hasheo»). Son **cuatro** cosas, y ninguna es un rediseño:
1. **Los kernels no compilan con información de depuración**: `CONFIG_DEBUG_INFO_NONE=y` en los
`.config` sellados. BTF exige DWARF, así que primero hay que encender `DEBUG_INFO_DWARF5`.
Eso encarece el build, **no el `bzImage`**: el DWARF vive en `vmlinux`, que la receta no
empaqueta — lo único que entra a la imagen es la sección `.BTF`.
2. **pahole está en el catálogo, pero el kernel no lo declara.** `recipes/dwarves.toml` (1.30)
se selló al cerrarse el frente libdw/musl, y su propia cabecera lo dice: «El kernel NO la
declara todavía».
3. **Ese pahole es DINÁMICO pese a `link = "static"`.** El binario del artefacto vigente
(`8057bcd…`) trae `INTERP` y cinco `NEEDED`: `libelf`, `libz`, `liblzma`, `libbz2`, `libc`.
Corre **dentro del sandbox del build del kernel**, así que esas librerías tienen que estar
ahí o no arranca — y el fallo saldría como «pahole no está» estando.
4. **BTF mete a pahole en el juego de la bit-repro.** El `.BTF` lo emite pahole ⇒ su versión
pasa a ser parte de la identidad del artefacto, y el lab **no está en `hash_inputs`**: dos
labs con pahole distinto sellarían bytes distintos en la misma dirección sin que el store lo
note. Es el agujero conocido, con una herramienta más adentro.
**Y ya hay dos consumidores nombrados, no cero.** El iterador de §9.5, que H8 aceptaría porque
trae ruta y símbolo (`bpf_iter_create`, `attach_btf_id` resuelto contra el BTF del vmlinux); y
**sched-ext**, que es por lo que existe la receta de dwarves — `SCHED_CLASS_EXT` depende de
`DEBUG_INFO_BTF`. El primero está medido; el segundo todavía no tiene su símbolo.
Lo que **no** está medido es el precio en bytes del `bzImage`, que es como se pagó H1. No se
puede estimar del kernel de Artix: sus 6,4 MB de BTF son de un kernel modular enorme y no dicen
nada de uno monolítico y pelado. **Se mide construyendo, y esa medición es el primer paso el día
que se decida encenderlo** — no antes, porque enciende el re-hasheo de los cuatro kernels.
- **H8 · El contrato de capacidades es parte del release del kernel** (§4.ter). Toda receta de
kernel nueva declara su `[[target]]` con perfil; un kernel sin perfil sale **SIN COMPROBAR**, no
aprobado. Y toda capacidad nueva entra con consumidor —ruta y símbolo— o no entra: «estaría bueno
@@ -498,10 +535,10 @@ Detalle en el handoff espejo. En una línea cada una:
la lista de símbolos garantizados (verificable con `hammer kernel probe`); tawasuyu elige
backend en runtime.
## 9. Los experimentos nombrados — cuatro medidos, dos abiertos
## 9. Los experimentos nombrados — cinco medidos, uno abierto
La primera corrida los dejó nombrados y sin medir, todos por privilegios o por herramienta
ausente. **Cuatro cayeron el mismo día, con root.** Condiciones distintas a la corrida de arriba
ausente. **Cuatro cayeron el mismo día, con root**, y el quinto —el iterador BPF— el 2026-08-31. Condiciones distintas a la corrida de arriba
(load ~1,3 sobre 4 vCPU, 4,3 GiB libres) ⇒ los números de acá **no se cruzan** con los de §2.
### 9.1 · proc connector y `taskstats` — MEDIDO (`bench_connector.c`, `connector.txt`)
@@ -588,11 +625,60 @@ Dos lecturas:
`strace -c` le atribuye es espera **bloqueada**, no CPU — y por eso `strace -c` no sirve para
comparar compiladores, sólo para contar llamadas.
### 9.5 · Lo que sigue abierto
### 9.5 · Iterador BPF contra `/proc` — MEDIDO (`bench_bpf_iter.c` + `bpf_iter_task.bpf.c` → `bpf-iter.txt`)
1. **Iterador BPF contra `/proc`** (T3): exige BTF y privilegios. Va detrás de H7
`DEBUG_INFO_BTF` está apagado en las cuatro recetas y su deuda (pahole) ya está medio pagada.
2. **Un Intel con PTI**: todos los pisos de syscall de acá son de un AMD sin PTI. En el laptop
Medido el **2026-08-31**, sobre el kernel de desarrollo (Artix 7.1.4, que sí trae BTF)
**no sobre un kernel de hammer, que es justamente lo que está en discusión**. Tercera corrida,
condiciones propias: **sus números no se cruzan con los de §2 ni con los de §9.1-9.4.**
Los dos caminos hacen **el mismo trabajo**: sacar `{pid, ppid, estado, utime, stime, comm}` de
todos los procesos vivos, que es la foto que sandokan necesita de la máquina. Uno lo hace con
`readdir` + `open`+`read`+parse de `/proc/<pid>/stat`; el otro con un `iter/task` que escribe
**registros binarios de tamaño fijo** con `bpf_seq_write`, leídos de un solo fd.
| 245 procesos vivos, 33 rondas, mínimo | total | por proceso | × sobre el piso |
|---|---|---|---|
| A) `/proc` readdir+`stat`+parse | 1 356,1 µs | 5,54 µs | 12,1× |
| B) iterador BPF (binario) | **196,1 µs** | **0,80 µs** | **1,75×** |
| C) piso: `readdir(/proc)` a secas | 112,1 µs | 0,46 µs | 1× |
**6,9× a favor del iterador** — pero el número que decide no es ése: **el iterador cuesta casi lo
mismo que listar el directorio y no abrir nada** (1,75× el piso, contra 12,1× de `/proc`). Leer
los campos deja de tener precio; lo que se paga es enumerar.
Siete corridas, dos órdenes (A→B y B→A, para descartar que el primero de cada ronda pague el
enfriamiento de las dentries de `/proc`): **6,9-8,0× en las cuatro de 33 rondas**, en las dos
direcciones. La de 15 rondas dio 4,50× y es de la que hay que desconfiar, no de la mayoría: con
pocas rondas el mínimo no converge y la máquina estaba cargada.
**Amortización**: cargar y verificar el programa cuesta **13-17 ms una vez**; cada barrido ahorra
más de 1 ms ⇒ **se paga solo en 12-40 barridos** según la corrida, 15 en la citada. Para un
sandokan que mire la máquina una vez por segundo, medio minuto.
**Lo que la ganancia cuesta, comprobado y no supuesto** — son dos barreras, no una:
1. **BTF es un portón, no una optimización.** El programa cargado informa
`prog_type=26 (BPF_PROG_TYPE_TRACING)`, `attach_btf_id=80137`, `attach_btf_obj_id=1`: el
iterador **se ata por id de tipo BTF del vmlinux**. Un kernel sin `DEBUG_INFO_BTF` no publica
`/sys/kernel/btf/vmlinux` y no hay a qué atarse. **No hay versión de esto que esquive H7.**
2. **Privilegio**: `CAP_BPF`+`CAP_PERFMON` o root. Sin él, `bpf_object__load` sale
`Operation not permitted`. Una Card no lo tiene ni debe tenerlo ⇒ **el consumidor es el
supervisor, no la Card** — que es donde vive sandokan, así que encaja.
**Y los dos caminos NO dicen exactamente lo mismo.** De 245 procesos, **24 `comm` difieren**, y
son **todos kworkers**: `/proc` **sintetiza** el nombre pegándole la workqueue que el hilo está
sirviendo (`kworker/0:0H-kblockd`), mientras `task->comm` trae el nombre pelado
(`kworker/0:0H`). Ningún proceso de usuario difiere. Para las Cards da igual; para cualquiera que
muestre nombres de kworker, no. **Un reemplazo que se vende como "la misma información más
rápido" tenía una diferencia de contenido, y sólo apareció cotejando registro contra registro** —
no medir es lo que la habría dejado pasar.
**Nota de método**: el bench aborta con exit 2 si el iterador devuelve **cero registros**. Un
barrido vacío es infinitamente rápido y no mide nada: es CLAUDE.md §3 aplicado a un benchmark.
### 9.6 · Lo que sigue abierto
1. **Un Intel con PTI**: todos los pisos de syscall de acá son de un AMD sin PTI. En el laptop
(TigerLake) las mismas medidas darían peor, y ahí T3/T6 duelen más.
## 10. Evidencia
@@ -618,6 +704,14 @@ Se compilan igual que los otros (`bench_sqpoll` pide `-luring`) y los tres prime
`hammer kernel contract --list` (la vista). Los ficheros de esta carpeta no cambian; el contrato sí
—cada vez que un consumidor nuevo usa el kernel por nombre.
**Tercera corrida (§9.5, iterador BPF, 2026-08-31)**: `bpf_iter_task.bpf.c` (el programa BPF) +
`bench_bpf_iter.c` (el de usuario) → `bpf-iter.txt`. Se compilan con
`clang -O2 -g -target bpf -D__TARGET_ARCH_x86 -c bpf_iter_task.bpf.c -o bpf_iter_task.bpf.o` y
`cc -O2 -o bench_bpf_iter bench_bpf_iter.c -lbpf`; se corre `./bench_bpf_iter [rondas] [orden]`
como root, con el `.bpf.o` al lado. **No usa `vmlinux.h`** —haría falta bpftool, que no está en la
máquina ni en el store— sino los tipos mínimos declarados a mano con `preserve_access_index`, que
es lo que deja a CO-RE reubicar los offsets contra el BTF del kernel en carga.
**El pago de H1** (2026-08-30): `contrato-h1-pagado.txt` — el barrido entero con los cuatro kernels
reconstruidos (5 de 5 vigentes en verde, 10 superados listados) y el `diff-back` del plan de gioser
contra su `.config` nuevo. Es la única salida de esta carpeta que sí cambia si se vuelve a correr: