tawasuyu

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…

TOML 1532Shell 196Rust 108Markdown 993686 Definiciones

⎇ 84e3737e10e17927380ed3e232e50cebfdd39123 docs/evidencia/tasas-kernel-2026-08-29/bench_bpf_iter.c C · 256 líneas · 7.5 KB Historia Quién tocó qué crudo

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

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}