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 604Rust 56Shell 36Markdown 281227 Sutichaykuna

⎇ main docs/evidencia/tasas-kernel-2026-08-29/bench_overlay.c C · 570 siq'ikuna · 24.7 KB Ñ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 1df77985f · Sergio ·

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}