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…
⎇ main
docs/evidencia/tasas-kernel-2026-08-29/bench_overlay.c
Ñawpa willay
Pi imata llamkarqan
ch'uya
tasas: T19 — el sandbox apila una capa overlay por dep, y lo caro no es lo que parece
1// bench_overlay.c — SDD 25 / T19: qué cobra overlayfs POR CAPA en un lookup.
2//
3// hammer apila una capa overlay por dependencia bajo el rootfs (crates/hammer-build/src/sandbox.rs,
4// `bwrap_args`) y sólo las funde en una sola capa de hardlinks cuando la suma de rutas pasa de
5// 3000 B. Con el store en /mnt/vvv/hammer/store eso son ~26 deps ⇒ el 95% del corpus construye con
6// N capas apiladas. Este banco mide qué cuesta esa N.
7//
8// Compilar: cc -O2 -o bench_overlay bench_overlay.c
9// Correr : sudo ./bench_overlay [rondas] [dir-base] (root: hace mount(2) de overlay)
10//
11// Método (mismo que el resto de la carpeta): 7 rondas por defecto, se reporta MÍNIMO y mediana.
12// Las capas se construyen UNA vez; el overlay se monta y desmonta en CADA ronda, así que las
13// dentries del overlay están frías en cada medida y las del filesystem de abajo, calientes: lo que
14// se aísla es el paseo del overlay por sus capas, no el coste de ext4. Los FALLOS usan nombres
15// nuevos en cada ronda (`nope_r<ronda>_<i>`) para que tampoco haya dentry negativa cacheada abajo.
16//
17// MODO D (tercer argumento): `sudo ./bench_overlay <rondas> <dir-base> <fichero-con-capas-reales>`
18// — en vez del árbol sintético, apila DEPS REALES del store de hammer (una ruta por línea, la
19// primera es la de más arriba, como el rootfs en `bwrap_args`). Es la comprobación de que la ley
20// que sale de A/B no depende de que las capas sean de juguete: los artefactos reales tienen
21// árboles profundos y miles de ficheros.
22//
23// Sale con 2 si la jaula de la medición no está puesta: que el overlay esté montado y que un
24// fichero de la capa MÁS PROFUNDA se vea a través del punto de montaje. Medir un overlay que no
25// montó da números preciosos y falsos (CLAUDE.md §3).
26
27#include <sys/mount.h>
28#include <sys/stat.h>
29#include <sys/types.h>
30#include <sys/wait.h>
31#include <errno.h>
32#include <fcntl.h>
33#include <stdarg.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <time.h>
38#include <unistd.h>
39
40#define K_FILES 256 // ficheros por capa
41static const int NS[] = {1, 2, 4, 8, 16, 24, 32, 48, 64};
42#define N_NS ((int)(sizeof(NS) / sizeof(NS[0])))
43#define MAXN 64
44
45static char BASE[512];
46static double g_mount_ns, g_umount_ns; // lo que cuesta ARMAR la jaula de capas, por fase de build
47
48static double now_ns(void) {
49 struct timespec t;
50 clock_gettime(CLOCK_MONOTONIC, &t);
51 return (double)t.tv_sec * 1e9 + (double)t.tv_nsec;
52}
53
54static int cmp_d(const void *a, const void *b) {
55 double x = *(const double *)a, y = *(const double *)b;
56 return x < y ? -1 : x > y ? 1 : 0;
57}
58
59static void sh(const char *fmt, ...) {
60 char cmd[4096];
61 va_list ap;
62 va_start(ap, fmt);
63 vsnprintf(cmd, sizeof cmd, fmt, ap);
64 va_end(ap);
65 int r = system(cmd);
66 (void)r;
67}
68
69static void mkpath(const char *p) {
70 char buf[1024];
71 snprintf(buf, sizeof buf, "%s", p);
72 for (char *q = buf + 1; *q; q++) {
73 if (*q == '/') { *q = 0; mkdir(buf, 0755); *q = '/'; }
74 }
75 mkdir(buf, 0755);
76}
77
78// ── construcción del árbol ────────────────────────────────────────────────────────────────────
79// Cada capa i trae `usr/include/` (compartido: fuerza la fusión de directorios, que es lo que hace
80// un overlay real) y K ficheros PROPIOS `usr/include/l<i>_<j>.h`. Un fichero de la capa i sólo
81// existe ahí ⇒ encontrarlo cuesta exactamente i+1 lookups de capa.
82static void construir_capas(int nmax) {
83 char p[1024];
84 for (int i = 0; i < nmax; i++) {
85 snprintf(p, sizeof p, "%s/lower/%d/usr/include", BASE, i);
86 mkpath(p);
87 for (int j = 0; j < K_FILES; j++) {
88 char f[1200];
89 snprintf(f, sizeof f, "%s/l%d_%d.h", p, i, j);
90 int fd = open(f, O_CREAT | O_WRONLY | O_TRUNC, 0644);
91 if (fd >= 0) { (void)!write(fd, "/* x */\n", 8); close(fd); }
92 }
93 }
94 snprintf(p, sizeof p, "%s/upper", BASE); mkpath(p);
95 snprintf(p, sizeof p, "%s/work", BASE); mkpath(p);
96 snprintf(p, sizeof p, "%s/mnt", BASE); mkpath(p);
97}
98
99// lowerdir=l0:l1:...:l(n-1) — el PRIMERO es el de arriba (mayor prioridad), igual que bwrap apila
100// el rootfs primero y las deps debajo.
101static int montar(int n, const char *lower_override) {
102 char opts[8192];
103 int off = snprintf(opts, sizeof opts, "lowerdir=");
104 if (lower_override) {
105 off += snprintf(opts + off, sizeof opts - off, "%s", lower_override);
106 } else {
107 for (int i = 0; i < n; i++)
108 off += snprintf(opts + off, sizeof opts - off, "%s%s/lower/%d", i ? ":" : "", BASE, i);
109 }
110 snprintf(opts + off, sizeof opts - off, ",upperdir=%s/upper,workdir=%s/work", BASE, BASE);
111 char mnt[600];
112 snprintf(mnt, sizeof mnt, "%s/mnt", BASE);
113 double t0 = now_ns();
114 int r = mount("overlay", mnt, "overlay", 0, opts);
115 g_mount_ns = now_ns() - t0;
116 if (r != 0) {
117 fprintf(stderr, "mount overlay (n=%d): %m\n opts=%s\n", n, opts);
118 return -1;
119 }
120 return 0;
121}
122
123static void desmontar(void) {
124 char mnt[600];
125 snprintf(mnt, sizeof mnt, "%s/mnt", BASE);
126 double t0 = now_ns();
127 if (umount(mnt) != 0) fprintf(stderr, "umount: %m\n");
128 g_umount_ns = now_ns() - t0;
129 // El upper es descartable (como el --tmp-overlay de bwrap): vaciarlo entre montajes.
130 sh("rm -rf '%s/upper' '%s/work' && mkdir -p '%s/upper' '%s/work'", BASE, BASE, BASE, BASE);
131}
132
133// ── medidas ──────────────────────────────────────────────────────────────────────────────────
134// stat de K rutas DISTINTAS, cada una por primera vez en este montaje ⇒ cada una paga el paseo.
135static double stat_frio(int capa, int esperado_ok) {
136 char f[1200];
137 struct stat st;
138 int ok = 0;
139 double t0 = now_ns();
140 for (int j = 0; j < K_FILES; j++) {
141 snprintf(f, sizeof f, "%s/mnt/usr/include/l%d_%d.h", BASE, capa, j);
142 if (stat(f, &st) == 0) ok++;
143 }
144 double dt = now_ns() - t0;
145 if (esperado_ok && ok != K_FILES) {
146 fprintf(stderr, "[canario] capa %d: %d de %d ficheros VISIBLES — la medida no vale\n",
147 capa, ok, K_FILES);
148 exit(2);
149 }
150 return dt / K_FILES;
151}
152
153static double stat_fallo(int ronda) {
154 char f[1200];
155 struct stat st;
156 int ok = 0;
157 double t0 = now_ns();
158 for (int j = 0; j < K_FILES; j++) {
159 snprintf(f, sizeof f, "%s/mnt/usr/include/nope_r%d_%d.h", BASE, ronda, j);
160 if (stat(f, &st) == 0) ok++;
161 }
162 double dt = now_ns() - t0;
163 if (ok != 0) { fprintf(stderr, "[canario] un fichero inexistente EXISTE — la medida no vale\n"); exit(2); }
164 return dt / K_FILES;
165}
166
167static double open_frio(int capa) {
168 char f[1200];
169 int ok = 0;
170 double t0 = now_ns();
171 for (int j = 0; j < K_FILES; j++) {
172 snprintf(f, sizeof f, "%s/mnt/usr/include/l%d_%d.h", BASE, capa, j);
173 int fd = open(f, O_RDONLY);
174 if (fd >= 0) { ok++; close(fd); }
175 }
176 double dt = now_ns() - t0;
177 if (ok != K_FILES) { fprintf(stderr, "[canario] open: %d de %d\n", ok, K_FILES); exit(2); }
178 return dt / K_FILES;
179}
180
181// La MISMA ruta muchas veces: la dentry del overlay ya está, no se pasea nada.
182static double stat_caliente(int capa) {
183 char f[1200];
184 struct stat st;
185 snprintf(f, sizeof f, "%s/mnt/usr/include/l%d_0.h", BASE, capa);
186 if (stat(f, &st) != 0) { fprintf(stderr, "[canario] caliente: %m\n"); exit(2); }
187 const int R = 20000;
188 double t0 = now_ns();
189 for (int j = 0; j < R; j++) stat(f, &st);
190 return (now_ns() - t0) / R;
191}
192
193// ── la alternativa real: fundir las N capas en UNA por hardlinks (lo que hace merge_deps_layer) ──
194static double construir_merge(int n) {
195 char m[600];
196 snprintf(m, sizeof m, "%s/merged", BASE);
197 sh("rm -rf '%s'", m);
198 mkpath(m);
199 double t0 = now_ns();
200 for (int i = 0; i < n; i++)
201 sh("cp -alf '%s/lower/%d/.' '%s/' 2>/dev/null", BASE, i, m);
202 return now_ns() - t0; // ns totales
203}
204
205
206// ── MODO D · capas REALES del store ──────────────────────────────────────────────────────────
207#define MAXR 64
208static char CAPA[MAXR][512];
209static char REL[K_FILES][512]; // rutas relativas de ficheros que EXISTEN en la capa más profunda
210
211static int leer_capas(const char *lista) {
212 FILE *fp = fopen(lista, "r");
213 if (!fp) { fprintf(stderr, "no puedo abrir %s: %m\n", lista); exit(2); }
214 int n = 0;
215 char ln[512];
216 while (n < MAXR && fgets(ln, sizeof ln, fp)) {
217 size_t l = strlen(ln);
218 while (l && (ln[l - 1] == '\n' || ln[l - 1] == ' ')) ln[--l] = 0;
219 if (!l || ln[0] == '#') continue;
220 snprintf(CAPA[n++], 512, "%s", ln);
221 }
222 fclose(fp);
223 return n;
224}
225
226// Ficheros reales de la capa `dir`, en rutas relativas, para statearlos a través del montaje.
227static int recolectar(const char *dir, int *nrel) {
228 char cmd[1200];
229 snprintf(cmd, sizeof cmd,
230 "cd '%s' 2>/dev/null && find . -type f -printf '%%P\\n' 2>/dev/null | head -%d", dir, K_FILES);
231 FILE *fp = popen(cmd, "r");
232 if (!fp) return -1;
233 int k = 0;
234 char ln[512];
235 while (k < K_FILES && fgets(ln, sizeof ln, fp)) {
236 size_t l = strlen(ln);
237 while (l && ln[l - 1] == '\n') ln[--l] = 0;
238 if (l) snprintf(REL[k++], 512, "%s", ln);
239 }
240 pclose(fp);
241 *nrel = k;
242 return k;
243}
244
245static double stat_reales(int esperado_ok, int nrel) {
246 char f[1200]; struct stat st; int ok = 0;
247 double t0 = now_ns();
248 for (int j = 0; j < nrel; j++) {
249 snprintf(f, sizeof f, "%s/mnt/%s", BASE, REL[j]);
250 if (stat(f, &st) == 0) ok++;
251 }
252 double dt = now_ns() - t0;
253 if (esperado_ok && ok != nrel) {
254 fprintf(stderr, "[canario] sólo %d de %d ficheros REALES visibles — la medida no vale\n", ok, nrel);
255 exit(2);
256 }
257 return dt / nrel;
258}
259
260static int montar_lista(int n) {
261 char opts[8192];
262 int off = snprintf(opts, sizeof opts, "lowerdir=");
263 for (int i = 0; i < n; i++)
264 off += snprintf(opts + off, sizeof opts - off, "%s%s", i ? ":" : "", CAPA[i]);
265 if (off > 3800) fprintf(stderr, "# AVISO: lowerdir de %d B, cerca del límite de página\n", off);
266 snprintf(opts + off, sizeof opts - off, ",upperdir=%s/upper,workdir=%s/work", BASE, BASE);
267 char mnt[600]; snprintf(mnt, sizeof mnt, "%s/mnt", BASE);
268 double t0 = now_ns();
269 int rc = mount("overlay", mnt, "overlay", 0, opts);
270 g_mount_ns = now_ns() - t0;
271 if (rc != 0) {
272 fprintf(stderr, "mount overlay reales (n=%d): %m\n", n); return -1;
273 }
274 return 0;
275}
276
277static int modo_real(int rondas, const char *lista) {
278 int ncapas = leer_capas(lista);
279 printf("# MODO D — %d capas REALES del store, la primera es la de arriba\n", ncapas);
280 char p[1024];
281 snprintf(p, sizeof p, "%s/upper", BASE); mkpath(p);
282 snprintf(p, sizeof p, "%s/work", BASE); mkpath(p);
283 snprintf(p, sizeof p, "%s/mnt", BASE); mkpath(p);
284
285 int ns[] = {1, 2, 4, 8, 16, 24, 32, 48, 64};
286 printf("\n## D1 · apiladas: ns/ruta, lookup frío\n");
287 printf("%4s %8s %16s %16s %16s %10s\n", "N", "ficheros", "hit capa N-1", "FALLO",
288 "open capa N-1", "mount(us)");
289 for (int a = 0; a < 9; a++) {
290 int n = ns[a];
291 if (n > ncapas) break;
292 int nrel = 0;
293 if (recolectar(CAPA[n - 1], &nrel) <= 0 || nrel == 0) { printf("%4d (capa vacía)\n", n); continue; }
294 double t_hit[64], t_miss[64], t_open[64], t_mnt[64];
295 for (int r = 0; r < rondas; r++) {
296 if (montar_lista(n) != 0) {
297 // El lowerdir de mount(2) cabe en UNA página: con rutas reales del store
298 // (~97 B cada una) el apilado se acaba antes de N=64. Que se acabe ES el dato.
299 printf("%4d %8d — el lowerdir no cabe en una página (ver sandbox.rs)\n", n, nrel);
300 fflush(stdout);
301 goto fin_d1;
302 }
303 t_mnt[r] = g_mount_ns / 1000.0;
304 t_hit[r] = stat_reales(1, nrel);
305 t_miss[r] = stat_fallo(r + 7000 + a * 1000);
306 { // open de los mismos reales
307 char f[1200]; int ok = 0; double t0 = now_ns();
308 for (int j = 0; j < nrel; j++) {
309 snprintf(f, sizeof f, "%s/mnt/%s", BASE, REL[j]);
310 int fd = open(f, O_RDONLY);
311 if (fd >= 0) { ok++; close(fd); }
312 }
313 t_open[r] = (now_ns() - t0) / nrel;
314 if (ok != nrel) { fprintf(stderr, "[canario] open reales %d/%d\n", ok, nrel); return 2; }
315 }
316 desmontar();
317 }
318 qsort(t_hit, rondas, sizeof(double), cmp_d);
319 qsort(t_miss, rondas, sizeof(double), cmp_d);
320 qsort(t_open, rondas, sizeof(double), cmp_d);
321 qsort(t_mnt, rondas, sizeof(double), cmp_d);
322 printf("%4d %8d %8.1f/%6.1f %8.1f/%6.1f %8.1f/%6.1f %10.1f\n", n, nrel,
323 t_hit[0], t_hit[rondas / 2], t_miss[0], t_miss[rondas / 2],
324 t_open[0], t_open[rondas / 2], t_mnt[0]);
325 fflush(stdout);
326 continue;
327fin_d1:
328 break;
329 }
330
331 printf("\n## D2 · las mismas, FUNDIDAS en una capa de hardlinks\n");
332 printf("%4s %8s %16s %16s %16s %10s %10s\n", "N", "ficheros", "hit", "FALLO", "open", "fundir(ms)", "ficheros");
333 for (int a = 0; a < 9; a++) {
334 int n = ns[a];
335 if (n > ncapas) break;
336 int nrel = 0;
337 if (recolectar(CAPA[n - 1], &nrel) <= 0 || nrel == 0) continue;
338 // El merge por hardlinks TIENE que caer en el filesystem de las capas: el store de esta
339 // máquina es un bind de /dev/sdb y BASE está en /dev/sdc ⇒ `cp -al` cross-device falla.
340 // Es el mismo cálculo que hace `merge_deps_layer` en sandbox.rs, y por la misma razón.
341 char m[600];
342 { char dir[512]; snprintf(dir, sizeof dir, "%s", CAPA[0]);
343 char *sl = strrchr(dir, '/'); if (sl) *sl = 0;
344 snprintf(m, sizeof m, "%s/.bench-merge", dir); }
345 sh("rm -rf '%s'", m); mkpath(m);
346 double t0 = now_ns();
347 for (int i = 0; i < n; i++) sh("cp -alf '%s/.' '%s/' 2>/dev/null", CAPA[i], m);
348 double ms = (now_ns() - t0) / 1e6;
349 long nf = 0;
350 { char c[900]; snprintf(c, sizeof c, "find '%s' -type f 2>/dev/null | wc -l", m);
351 FILE *fp = popen(c, "r"); if (fp) { if (fscanf(fp, "%ld", &nf) != 1) nf = -1; pclose(fp); } }
352 snprintf(CAPA[MAXR - 1], 512, "%s", m);
353 char guardado[512]; snprintf(guardado, sizeof guardado, "%s", CAPA[0]);
354 snprintf(CAPA[0], 512, "%s", m);
355 double t_hit[64], t_miss[64], t_open[64];
356 for (int r = 0; r < rondas; r++) {
357 if (montar_lista(1) != 0) return 2;
358 t_hit[r] = stat_reales(1, nrel);
359 t_miss[r] = stat_fallo(r + 9000 + a * 1000);
360 { char f[1200]; int ok = 0; double tt = now_ns();
361 for (int j = 0; j < nrel; j++) {
362 snprintf(f, sizeof f, "%s/mnt/%s", BASE, REL[j]);
363 int fd = open(f, O_RDONLY); if (fd >= 0) { ok++; close(fd); } }
364 t_open[r] = (now_ns() - tt) / nrel; }
365 desmontar();
366 }
367 snprintf(CAPA[0], 512, "%s", guardado);
368 qsort(t_hit, rondas, sizeof(double), cmp_d);
369 qsort(t_miss, rondas, sizeof(double), cmp_d);
370 qsort(t_open, rondas, sizeof(double), cmp_d);
371 printf("%4d %8d %8.1f/%6.1f %8.1f/%6.1f %8.1f/%6.1f %10.1f %10ld\n", n, nrel,
372 t_hit[0], t_hit[rondas / 2], t_miss[0], t_miss[rondas / 2],
373 t_open[0], t_open[rondas / 2], ms, nf);
374 fflush(stdout);
375 }
376 printf("\n# el merge queda en %s/merged-real — borrar a mano\n", BASE);
377 return 0;
378}
379
380
381// ── MODO E · la API de montaje NUEVA (fsopen/fsconfig) ────────────────────────────────────────
382// El muro de D1 —el lowerdir no cabe en una página— es del `mount(2)` clásico, que pasa TODAS las
383// opciones en un único buffer de una página. La API nueva pasa cada opción por separado y overlayfs
384// acepta `lowerdir+` repetido (una capa por llamada). Si eso monta 64 capas reales, el merge de
385// hardlinks de `merge_deps_layer` deja de ser obligatorio — y con él la retención de `.dmerge`.
386#include <sys/syscall.h>
387#ifndef FSCONFIG_SET_STRING
388#define FSCONFIG_SET_STRING 1
389#define FSCONFIG_CMD_CREATE 6
390#endif
391#ifndef MOVE_MOUNT_F_EMPTY_PATH
392#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004
393#endif
394static int fsopen_(const char *fs, unsigned f) { return (int)syscall(__NR_fsopen, fs, f); }
395static int fsconfig_(int fd, unsigned cmd, const char *k, const void *v, int aux) {
396 return (int)syscall(__NR_fsconfig, fd, cmd, k, v, aux);
397}
398static int fsmount_(int fd, unsigned f, unsigned ms) { return (int)syscall(__NR_fsmount, fd, f, ms); }
399static int move_mount_(int ffd, const char *fp, int tfd, const char *tp, unsigned f) {
400 return (int)syscall(__NR_move_mount, ffd, fp, tfd, tp, f);
401}
402
403static int modo_api_nueva(int rondas, const char *lista) {
404 int ncapas = leer_capas(lista);
405 char p[1024];
406 snprintf(p, sizeof p, "%s/upper", BASE); mkpath(p);
407 snprintf(p, sizeof p, "%s/work", BASE); mkpath(p);
408 snprintf(p, sizeof p, "%s/mnt", BASE); mkpath(p);
409 char mnt[600]; snprintf(mnt, sizeof mnt, "%s/mnt", BASE);
410 char up[600], wk[600];
411 snprintf(up, sizeof up, "%s/upper", BASE);
412 snprintf(wk, sizeof wk, "%s/work", BASE);
413
414 printf("# MODO E — fsopen/fsconfig con `lowerdir+`, %d capas reales disponibles\n", ncapas);
415 printf("%4s %8s %16s %16s %12s\n", "N", "ficheros", "hit capa N-1", "FALLO", "mount(us)");
416 int ns[] = {8, 16, 24, 32, 48, 64};
417 for (int a = 0; a < 6; a++) {
418 int n = ns[a];
419 if (n > ncapas) break;
420 int nrel = 0;
421 if (recolectar(CAPA[n - 1], &nrel) <= 0) continue;
422 double t_hit[64], t_miss[64], t_mnt[64];
423 for (int r = 0; r < rondas; r++) {
424 double t0 = now_ns();
425 int fd = fsopen_("overlay", 0);
426 if (fd < 0) { fprintf(stderr, "fsopen: %m\n"); return 2; }
427 int mal = 0;
428 // Orden: la PRIMERA `lowerdir+` es la de más arriba, igual que en la lista separada por ':'.
429 for (int i = 0; i < n; i++)
430 if (fsconfig_(fd, FSCONFIG_SET_STRING, "lowerdir+", CAPA[i], 0) != 0) {
431 fprintf(stderr, "fsconfig lowerdir+ (capa %d): %m\n", i); mal = 1; break;
432 }
433 if (mal) { close(fd); return 2; }
434 if (fsconfig_(fd, FSCONFIG_SET_STRING, "upperdir", up, 0) != 0) { fprintf(stderr, "upperdir: %m\n"); return 2; }
435 if (fsconfig_(fd, FSCONFIG_SET_STRING, "workdir", wk, 0) != 0) { fprintf(stderr, "workdir: %m\n"); return 2; }
436 if (fsconfig_(fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) != 0) { fprintf(stderr, "create (n=%d): %m\n", n); return 2; }
437 int mfd = fsmount_(fd, 0, 0);
438 if (mfd < 0) { fprintf(stderr, "fsmount: %m\n"); return 2; }
439 if (move_mount_(mfd, "", AT_FDCWD, mnt, MOVE_MOUNT_F_EMPTY_PATH) != 0) { fprintf(stderr, "move_mount: %m\n"); return 2; }
440 t_mnt[r] = (now_ns() - t0) / 1000.0;
441 close(mfd); close(fd);
442 t_hit[r] = stat_reales(1, nrel);
443 t_miss[r] = stat_fallo(r + 20000 + a * 1000);
444 desmontar();
445 }
446 qsort(t_hit, rondas, sizeof(double), cmp_d);
447 qsort(t_miss, rondas, sizeof(double), cmp_d);
448 qsort(t_mnt, rondas, sizeof(double), cmp_d);
449 printf("%4d %8d %8.1f/%6.1f %8.1f/%6.1f %12.1f\n", n, nrel,
450 t_hit[0], t_hit[rondas / 2], t_miss[0], t_miss[rondas / 2], t_mnt[0]);
451 fflush(stdout);
452 }
453 return 0;
454}
455
456int main(int argc, char **argv) {
457 int rondas = argc > 1 ? atoi(argv[1]) : 7;
458 snprintf(BASE, sizeof BASE, "%s", argc > 2 ? argv[2] : "/mnt/vvv/.bench-overlay");
459
460 if (geteuid() != 0) { fprintf(stderr, "hace falta root (mount de overlay)\n"); return 2; }
461
462 if (argc > 4) { // 4º argumento cualquiera ⇒ modo E (API de montaje nueva)
463 snprintf(BASE, sizeof BASE, "%s", argv[2]);
464 printf("# bench_overlay — SDD 25 T19 (modo E: fsconfig lowerdir+)\n");
465 sh("rm -rf '%s/mnt' '%s/upper' '%s/work'", BASE, BASE, BASE);
466 mkpath(BASE);
467 return modo_api_nueva(rondas, argv[3]);
468 }
469 if (argc > 3) {
470 snprintf(BASE, sizeof BASE, "%s", argv[2]);
471 printf("# bench_overlay — SDD 25 T19 (modo D: capas reales)\n");
472 printf("# base=%s rondas=%d\n", BASE, rondas);
473 sh("rm -rf '%s/mnt' '%s/upper' '%s/work'", BASE, BASE, BASE);
474 mkpath(BASE);
475 return modo_real(rondas, argv[3]);
476 }
477
478 printf("# bench_overlay — SDD 25 T19\n");
479 printf("# base=%s rondas=%d ficheros/capa=%d\n", BASE, rondas, K_FILES);
480 fflush(stdout);
481
482 sh("rm -rf '%s'", BASE);
483 mkpath(BASE);
484 construir_capas(MAXN);
485
486 printf("\n## A · lookup FRÍO por primera vez, en ns/ruta (capas apiladas)\n");
487 printf("%4s %14s %14s %14s %14s %12s %10s %9s\n", "N", "hit capa 0", "hit capa N-1", "FALLO",
488 "open capa N-1", "hit caliente", "mount(us)", "umount");
489 for (int a = 0; a < N_NS; a++) {
490 int n = NS[a];
491 double t_top[64], t_bot[64], t_miss[64], t_open[64], t_warm[64], t_mnt[64], t_umnt[64];
492 for (int r = 0; r < rondas; r++) {
493 if (montar(n, NULL) != 0) return 2;
494 t_mnt[r] = g_mount_ns / 1000.0;
495 t_top[r] = stat_frio(0, 1);
496 t_bot[r] = stat_frio(n - 1, 1);
497 t_miss[r] = stat_fallo(r + a * 1000);
498 t_open[r] = open_frio(n - 1);
499 t_warm[r] = stat_caliente(n - 1);
500 desmontar();
501 t_umnt[r] = g_umount_ns / 1000.0;
502 }
503 qsort(t_mnt, rondas, sizeof(double), cmp_d);
504 qsort(t_umnt, rondas, sizeof(double), cmp_d);
505 qsort(t_top, rondas, sizeof(double), cmp_d);
506 qsort(t_bot, rondas, sizeof(double), cmp_d);
507 qsort(t_miss, rondas, sizeof(double), cmp_d);
508 qsort(t_open, rondas, sizeof(double), cmp_d);
509 qsort(t_warm, rondas, sizeof(double), cmp_d);
510 printf("%4d %7.1f/%6.1f %7.1f/%6.1f %7.1f/%6.1f %7.1f/%6.1f %6.1f/%5.1f %10.1f %9.1f\n",
511 n, t_top[0], t_top[rondas / 2], t_bot[0], t_bot[rondas / 2],
512 t_miss[0], t_miss[rondas / 2], t_open[0], t_open[rondas / 2],
513 t_warm[0], t_warm[rondas / 2], t_mnt[0], t_umnt[0]);
514 fflush(stdout);
515 }
516 printf("# (mínimo/mediana de %d rondas)\n", rondas);
517
518 printf("\n## B · las MISMAS N capas fundidas en UNA sola (hardlinks, como merge_deps_layer)\n");
519 printf("%4s %14s %14s %14s %12s %14s\n", "N", "hit", "FALLO", "open", "caliente", "fundirlas(ms)");
520 for (int a = 0; a < N_NS; a++) {
521 int n = NS[a];
522 double ms_merge = construir_merge(n) / 1e6;
523 char lower[600];
524 snprintf(lower, sizeof lower, "%s/merged", BASE);
525 double t_hit[64], t_miss[64], t_open[64], t_warm[64];
526 for (int r = 0; r < rondas; r++) {
527 if (montar(1, lower) != 0) return 2;
528 t_hit[r] = stat_frio(n - 1, 1); // el fichero que en A estaba en el FONDO
529 t_miss[r] = stat_fallo(r + 500 + a * 1000);
530 t_open[r] = open_frio(n - 1);
531 t_warm[r] = stat_caliente(n - 1);
532 desmontar();
533 }
534 qsort(t_hit, rondas, sizeof(double), cmp_d);
535 qsort(t_miss, rondas, sizeof(double), cmp_d);
536 qsort(t_open, rondas, sizeof(double), cmp_d);
537 qsort(t_warm, rondas, sizeof(double), cmp_d);
538 printf("%4d %7.1f/%6.1f %7.1f/%6.1f %7.1f/%6.1f %6.1f/%5.1f %14.1f\n",
539 n, t_hit[0], t_hit[rondas / 2], t_miss[0], t_miss[rondas / 2],
540 t_open[0], t_open[rondas / 2], t_warm[0], t_warm[rondas / 2], ms_merge);
541 fflush(stdout);
542 }
543
544 printf("\n## C · piso: el mismo árbol SIN overlay (ext4 directo)\n");
545 {
546 double t_hit[64], t_miss[64];
547 char f[1200]; struct stat st;
548 for (int r = 0; r < rondas; r++) {
549 double t0 = now_ns();
550 for (int j = 0; j < K_FILES; j++) {
551 snprintf(f, sizeof f, "%s/lower/%d/usr/include/l%d_%d.h", BASE, MAXN - 1, MAXN - 1, j);
552 stat(f, &st);
553 }
554 t_hit[r] = (now_ns() - t0) / K_FILES;
555 t0 = now_ns();
556 for (int j = 0; j < K_FILES; j++) {
557 snprintf(f, sizeof f, "%s/lower/%d/usr/include/nope_c%d_%d.h", BASE, MAXN - 1, r, j);
558 stat(f, &st);
559 }
560 t_miss[r] = (now_ns() - t0) / K_FILES;
561 }
562 qsort(t_hit, rondas, sizeof(double), cmp_d);
563 qsort(t_miss, rondas, sizeof(double), cmp_d);
564 printf("stat ext4 hit %.1f/%.1f FALLO %.1f/%.1f ns\n",
565 t_hit[0], t_hit[rondas / 2], t_miss[0], t_miss[rondas / 2]);
566 }
567
568 printf("\n# el árbol queda en %s — borrar a mano\n", BASE);
569 return 0;
570}