sergio/takana
> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…
⎇ 84e3737e10e17927380ed3e232e50cebfdd39123
docs/evidencia/tasas-kernel-2026-08-29/bench_bpf_iter.c
History
Who touched what
raw
SDD 25 §9.5: el iterador BPF, medido — y H7 pasa a tener precio
1// SDD 25 §9.5.1 — Iterador BPF contra el barrido de /proc.
2//
3// Mide el MISMO trabajo por los dos caminos: sacar {pid, ppid, estado, utime,
4// stime, comm} de todos los procesos vivos, que es lo que sandokan necesita para
5// una foto de la máquina.
6//
7// A) /proc completo : readdir(/proc) + open+read+parse de /proc/<pid>/stat
8// B) iterador BPF : bpf_iter_create + read() de registros BINARIOS de tamaño fijo
9// C) piso : readdir(/proc) a secas, sin abrir nada
10//
11// Compilar: cc -O2 -o bench_bpf_iter bench_bpf_iter.c -lbpf
12// Correr: ./bench_bpf_iter [rondas] (pide root o CAP_BPF+CAP_PERFMON)
13// Necesita: bpf_iter_task.bpf.o al lado, y BTF del kernel en /sys/kernel/btf/vmlinux.
14#define _GNU_SOURCE
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
18#include <unistd.h>
19#include <fcntl.h>
20#include <dirent.h>
21#include <time.h>
22#include <errno.h>
23#include <bpf/libbpf.h>
24#include <bpf/bpf.h>
25
26#define MAX_RONDAS 33
27
28struct registro {
29 int pid;
30 int ppid;
31 unsigned int estado;
32 unsigned int _pad;
33 unsigned long long utime;
34 unsigned long long stime;
35 char comm[16];
36};
37
38static long long ahora_ns(void)
39{
40 struct timespec ts;
41 clock_gettime(CLOCK_MONOTONIC, &ts);
42 return (long long)ts.tv_sec * 1000000000LL + ts.tv_nsec;
43}
44
45static int cmp_ll(const void *a, const void *b)
46{
47 long long x = *(const long long *)a, y = *(const long long *)b;
48 return (x > y) - (x < y);
49}
50
51// ---------------------------------------------------------------- A) /proc
52static int barrido_proc(struct registro *out, int max)
53{
54 DIR *d = opendir("/proc");
55 struct dirent *e;
56 char ruta[64], buf[2048];
57 int n = 0;
58
59 if (!d)
60 return -1;
61 while ((e = readdir(d)) && n < max) {
62 if (e->d_name[0] < '0' || e->d_name[0] > '9')
63 continue;
64 snprintf(ruta, sizeof ruta, "/proc/%s/stat", e->d_name);
65 int fd = open(ruta, O_RDONLY);
66 if (fd < 0)
67 continue; // murió entre el readdir y el open
68 ssize_t k = read(fd, buf, sizeof buf - 1);
69 close(fd);
70 if (k <= 0)
71 continue;
72 buf[k] = 0;
73
74 // comm puede traer espacios y paréntesis ⇒ cortar por el ÚLTIMO ')'.
75 char *cierre = strrchr(buf, ')');
76 char *abre = strchr(buf, '(');
77 if (!cierre || !abre)
78 continue;
79 struct registro *r = &out[n];
80 r->pid = atoi(buf);
81 size_t largo = (size_t)(cierre - abre - 1);
82 if (largo > 15)
83 largo = 15;
84 memcpy(r->comm, abre + 1, largo);
85 r->comm[largo] = 0;
86
87 char estado;
88 int ppid, pgrp, sid, tty, tpgid;
89 unsigned int flags;
90 unsigned long minflt, cminflt, majflt, cmajflt, utime, stime;
91 if (sscanf(cierre + 2, "%c %d %d %d %d %d %u %lu %lu %lu %lu %lu %lu",
92 &estado, &ppid, &pgrp, &sid, &tty, &tpgid, &flags,
93 &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime) != 13)
94 continue;
95 r->ppid = ppid;
96 r->estado = (unsigned char)estado;
97 r->utime = utime;
98 r->stime = stime;
99 n++;
100 }
101 closedir(d);
102 return n;
103}
104
105// -------------------------------------------------------------- C) el piso
106static int solo_readdir(void)
107{
108 DIR *d = opendir("/proc");
109 struct dirent *e;
110 int n = 0;
111
112 if (!d)
113 return -1;
114 while ((e = readdir(d)))
115 if (e->d_name[0] >= '0' && e->d_name[0] <= '9')
116 n++;
117 closedir(d);
118 return n;
119}
120
121// ------------------------------------------------------------------ B) BPF
122static int barrido_bpf(int link_fd, struct registro *out, int max)
123{
124 char buf[65536];
125 int n = 0, resto = 0;
126 int fd = bpf_iter_create(link_fd);
127
128 if (fd < 0)
129 return -1;
130 for (;;) {
131 ssize_t k = read(fd, buf + resto, sizeof buf - resto);
132 if (k < 0) {
133 if (errno == EAGAIN)
134 continue;
135 close(fd);
136 return -1;
137 }
138 if (k == 0)
139 break;
140 int total = resto + (int)k, i = 0;
141 while (total - i >= (int)sizeof(struct registro) && n < max) {
142 memcpy(&out[n++], buf + i, sizeof(struct registro));
143 i += sizeof(struct registro);
144 }
145 resto = total - i;
146 if (resto > 0)
147 memmove(buf, buf + i, resto);
148 }
149 close(fd);
150 return n;
151}
152
153static void reporte(const char *nombre, long long *v, int r, int procesos)
154{
155 qsort(v, r, sizeof *v, cmp_ll);
156 double min = v[0] / 1000.0, med = v[r / 2] / 1000.0;
157 printf(" %-34s min %9.1f us mediana %9.1f us %6.2f us/proceso (min)\n",
158 nombre, min, med, procesos ? v[0] / 1000.0 / procesos : 0.0);
159}
160
161int main(int argc, char **argv)
162{
163 int rondas = argc > 1 ? atoi(argv[1]) : 9;
164 // 1 = medir B antes que A. Control de sesgo de orden: el primero de cada
165 // ronda paga el enfriamiento de las dentries de /proc, y hay que ver cuánto.
166 int invertido = argc > 2 ? atoi(argv[2]) : 0;
167 if (rondas < 3 || rondas > MAX_RONDAS)
168 rondas = 9;
169
170 static struct registro a[16384], b[16384];
171 long long t_proc[MAX_RONDAS], t_bpf[MAX_RONDAS], t_dir[MAX_RONDAS];
172 long long t0;
173
174 libbpf_set_print(NULL);
175
176 long long t_carga0 = ahora_ns();
177 struct bpf_object *obj = bpf_object__open_file("bpf_iter_task.bpf.o", NULL);
178 if (!obj) {
179 fprintf(stderr, "open_file: %s\n", strerror(errno));
180 return 1;
181 }
182 if (bpf_object__load(obj)) {
183 fprintf(stderr, "load (¿root? ¿BTF?): %s\n", strerror(errno));
184 return 1;
185 }
186 struct bpf_program *prog = bpf_object__find_program_by_name(obj, "barrido");
187 if (!prog) {
188 fprintf(stderr, "no está el programa 'barrido'\n");
189 return 1;
190 }
191 struct bpf_link *link = bpf_program__attach_iter(prog, NULL);
192 if (!link) {
193 fprintf(stderr, "attach_iter: %s\n", strerror(errno));
194 return 1;
195 }
196 int link_fd = bpf_link__fd(link);
197 long long carga = ahora_ns() - t_carga0;
198
199 int n_proc = 0, n_bpf = 0, n_dir = 0;
200 for (int i = 0; i < rondas; i++) {
201 t0 = ahora_ns(); n_dir = solo_readdir(); t_dir[i] = ahora_ns() - t0;
202 if (invertido) {
203 t0 = ahora_ns(); n_bpf = barrido_bpf(link_fd, b, 16384); t_bpf[i] = ahora_ns() - t0;
204 t0 = ahora_ns(); n_proc = barrido_proc(a, 16384); t_proc[i] = ahora_ns() - t0;
205 } else {
206 t0 = ahora_ns(); n_proc = barrido_proc(a, 16384); t_proc[i] = ahora_ns() - t0;
207 t0 = ahora_ns(); n_bpf = barrido_bpf(link_fd, b, 16384); t_bpf[i] = ahora_ns() - t0;
208 }
209 if (n_bpf < 0) {
210 fprintf(stderr, "bpf_iter_create/read falló: %s\n", strerror(errno));
211 return 1;
212 }
213 }
214
215 printf("procesos vistos: /proc %d · BPF %d · readdir %d (%d rondas, orden %s)\n",
216 n_proc, n_bpf, n_dir, rondas, invertido ? "B->A" : "A->B");
217 if (n_bpf == 0) {
218 fprintf(stderr, "\nEL ITERADOR DEVOLVIÓ CERO REGISTROS: eso no es 'rápido', "
219 "es que no midió nada. Abortando.\n");
220 return 2;
221 }
222 printf("carga+verificación+attach del programa BPF (una vez): %.1f ms\n\n", carga / 1e6);
223
224 reporte("A) /proc readdir+stat+parse", t_proc, rondas, n_proc);
225 reporte("B) iterador BPF (binario)", t_bpf, rondas, n_bpf);
226 reporte("C) piso: readdir(/proc) solo", t_dir, rondas, n_dir);
227
228 qsort(t_proc, rondas, sizeof *t_proc, cmp_ll);
229 qsort(t_bpf, rondas, sizeof *t_bpf, cmp_ll);
230 printf("\ncociente A/B (min, misma corrida): %.2fx\n",
231 (double)t_proc[0] / (double)t_bpf[0]);
232 // El programa BPF se carga y verifica UNA vez; a partir de ahí cada barrido
233 // ahorra (A-B). El punto de equilibrio es lo que decide si conviene.
234 long long ahorro = t_proc[0] - t_bpf[0];
235 if (ahorro > 0)
236 printf("equilibrio: la carga (%.1f ms) se amortiza en %lld barridos\n",
237 carga / 1e6, carga / ahorro + 1);
238
239 // Cotejo de contenido: dos caminos que dicen lo mismo, o el bench no sirve.
240 int coinciden = 0, solo_a = 0;
241 for (int i = 0; i < n_proc; i++) {
242 int hallado = 0;
243 for (int j = 0; j < n_bpf; j++)
244 if (a[i].pid == b[j].pid) {
245 hallado = 1;
246 if (strncmp(a[i].comm, b[j].comm, 15) == 0)
247 coinciden++;
248 break;
249 }
250 if (!hallado)
251 solo_a++;
252 }
253 printf("cotejo: %d pid+comm idénticos, %d sólo en /proc, %d sólo en BPF\n",
254 coinciden, solo_a, n_bpf - (n_proc - solo_a));
255 return 0;
256}