gioser

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 scripts/diagnostico/cazar-cuelgue-bajo-presion.sh Shell · 102 lines · 6.5 KB History raw

takana: la ruta del repo es /mnt/vvv/takana 17f8f4fb8 · Sergio ·

1#!/bin/bash
2# cazar-cuelgue-bajo-presion.sh — segunda caza del cuelgue de firefox headless, ahora BAJO PRESIÓN
3# DE MEMORIA, que es la única condición que las cuatro cazas anteriores (202 corridas, 0 cuelgues)
4# no tenían y que los dos bancos con cuelgues sí.
5#
6# ══ LA HIPÓTESIS, Y POR QUÉ ES FALSABLE ════════════════════════════════════════════════════════
7# El cuelgue es TODO O NADA: en 202 corridas ninguna pasó de 45 s, o terminan en ~20 s o se bloquean
8# hasta el timeout. Eso no parece lentitud por paginación —que daría una cola de tiempos largos—
9# sino un BLOQUEO. La forma más simple de que firefox se bloquee sólo bajo presión es que
10# **el proceso padre espere a un hijo que el OOM killer se llevó**: `--screenshot` espera a que el
11# proceso de contenido dibuje, y si el kernel lo mata, la respuesta no llega nunca.
12#
13# Eso es falsable con un contador que el kernel ya lleva: `oom_kill` en /proc/vmstat. Se muestrea
14# antes y después de CADA corrida. Si los cuelgues coinciden con incrementos, es prueba mecánica,
15# no correlación; si hay cuelgues SIN incremento, la hipótesis se cae y queda acotada por otro lado.
16#
17# ══ QUÉ SE MIDE, Y POR QUÉ ESTAS COSAS ═════════════════════════════════════════════════════════
18#   · MemAvailable y PSI (/proc/pressure/memory) al lanzar ⇒ la presión queda como NÚMERO. Sin esto
19#     «corrí bajo presión» es una afirmación sobre mi intención, no sobre el experimento.
20#   · delta de oom_kill ⇒ el testigo mecánico.
21#   · código de salida ⇒ distingue COLGADA (bloqueada hasta el umbral) de MUERTA (el kernel se la
22#     llevó a los 3 s). Son fenómenos distintos y confundirlos arruinaría el recuento.
23#   · nivel 0 SIN hog ⇒ el control. Sin él, un cuelgue no se puede atribuir a la presión.
24#
25# El generador de presión (presion-memoria.py) se marca oom_score_adj=1000: si el kernel tiene que
26# matar a alguien es a él, nunca a un build de la granja. Y toda la caza corre con el flock de la
27# granja tomado, para que no arranque una compilación de 4 h mientras se estrangula la RAM.
28#
29# Uso:  N=20 flock -o work/.farm-build.lock scripts/diagnostico/cazar-cuelgue-bajo-presion.sh
30#       El `-o` NO es opcional: sin él, un firefox que se cuelgue hereda el fd del lock y lo
31#       retiene DESPUÉS de que el arnés termine (regla 1 de CLAUDE.md). Pasó dos veces.
32set -u
33RAIZ=/mnt/vvv/takana; R=$RAIZ/store/.rootfs/sway-v2
34AQUI=$(cd "$(dirname "$0")" && pwd)
35N=${N:-20}; UMBRAL=${UMBRAL:-60}
36SAL=$(mktemp -d); trap 'rm -rf "$SAL"' EXIT
37FF=($(ls -d $RAIZ/store/8116bdec*-firefox $RAIZ/store/3d199174*-firefox))
38PAGS=(fuera-del-corpus.html del-corpus.html)   # LAS DEL BANCO: donde ocurrieron las 2 colgadas
39
40disp(){ awk '/MemAvailable/{print int($2/1024)}' /proc/meminfo; }
41psi(){  awk -F'avg10=' '/^some/{print substr($2,1,5)}' /proc/pressure/memory; }
42ooms(){ awk '/^oom_kill /{print $2}' /proc/vmstat; }
43
44HOG=""
45para_hog(){ [ -n "$HOG" ] && kill "$HOG" 2>/dev/null; HOG=""; sleep 3; }
46trap 'para_hog; rm -rf "$SAL"' EXIT
47
48for NIVEL in 0 2000 1200 700; do
49  if [ "$NIVEL" = 0 ]; then
50    echo "── nivel CONTROL: sin presión"
51  else
52    echo "── nivel: apuntando a $NIVEL MiB disponibles"
53    python3 "$AQUI/presion-memoria.py" "$NIVEL" 1800 >"$SAL/hog.log" 2>&1 & HOG=$!
54    for _ in $(seq 1 40); do sleep 2; [ "$(disp)" -le $((NIVEL+300)) ] && break; done
55  fi
56  echo "   arranca con disponible=$(disp) MiB psi=$(psi)"
57  colg=0; muer=0; ok=0
58  for i in $(seq 1 $N); do
59    d0=$(disp); p0=$(psi); o0=$(ooms)
60    # ⚠ `setsid` + matar el GRUPO, no el bwrap (medido 2026-09-08, DOS veces el mismo día).
61    # `kill $BW` mata el bwrap EXTERIOR, que no es el init del namespace de PID: el firefox de
62    # dentro sobrevive. Y si el arnés corría bajo `flock`, ese superviviente HEREDA el fd del lock
63    # y deja a la granja sin poder compilar hasta que alguien lo note — pasó hora y media, en
64    # silencio. Medido: matando sólo el bwrap queda 1 firefox vivo; matando el grupo, 0.
65    setsid bwrap --ro-bind "$R" / --ro-bind /usr/lib/musl/lib/libc.so /lib/ld-musl-x86_64.so.1 \
66      --ro-bind "${FF[$(( i % 2 ))]}/usr/lib/firefox" /usr/lib/firefox \
67      --dev /dev --proc /proc --tmpfs /run --tmpfs /tmp --unshare-pid \
68      --ro-bind "$AQUI" /bench --bind "$SAL" /salida --unshare-user --uid 0 --gid 0 \
69      --setenv PATH /usr/bin:/bin \
70      /bin/sh -c 'export HOME=/tmp/h MOZ_HEADLESS=1 MOZ_DISABLE_CONTENT_SANDBOX=1 LIBGL_ALWAYS_SOFTWARE=1 LANG=C.UTF-8; mkdir -p $HOME
71        LD_LIBRARY_PATH=/usr/lib/firefox /usr/lib/firefox/firefox --headless --screenshot /salida/x.png "file:///bench/'"${PAGS[$(( i % 2 ))]}"'"' \
72      >"$SAL/nav-$i.log" 2>&1 &
73    BW=$!; t=0
74    while kill -0 $BW 2>/dev/null && [ $t -lt $UMBRAL ]; do sleep 1; t=$((t+1)); done
75    if kill -0 $BW 2>/dev/null; then
76      colg=$((colg+1)); D=$RAIZ/work/cuelgue-$NIVEL-$i; mkdir -p "$D"
77      { echo "nivel=$NIVEL corrida=$i COLGADA a ${t}s"
78        echo "al lanzar:  disponible=$d0 MiB  psi_some_avg10=$p0  oom_kill=$o0"
79        echo "ahora:      disponible=$(disp) MiB  psi_some_avg10=$(psi)  oom_kill=$(ooms)"
80        echo "--- ¿mató el kernel a alguien DURANTE esta corrida? delta=$(( $(ooms) - o0 ))"; } > "$D/resumen.txt"
81      ps -eo pid,ppid,stat,rss,wchan:24,comm | grep -iE 'bwrap|firefox|Web Con|Isolated|PID' > "$D/ps.txt"
82      for p in $(pgrep -f 'usr/lib/firefox/firefox' | head -10); do
83        { echo "--- pid $p comm=$(cat /proc/$p/comm 2>/dev/null)"
84          echo "    state=$(awk '/^State:/{print $2,$3}' /proc/$p/status 2>/dev/null)"
85          echo "    wchan=$(cat /proc/$p/wchan 2>/dev/null)"
86          echo "    syscall=$(cut -c1-70 /proc/$p/syscall 2>/dev/null)"
87          echo "    rss=$(awk '/^VmRSS:/{print $2,$3}' /proc/$p/status 2>/dev/null)"; } >>"$D/procesos.txt" 2>/dev/null
88      done
89      cp "$SAL/nav-$i.log" "$D/navegador.log" 2>/dev/null
90      kill -TERM -$BW 2>/dev/null; sleep 3; kill -KILL -$BW 2>/dev/null   # el GRUPO; printf 'X'
91    else
92      wait $BW; rc=$?
93      od=$(( $(ooms) - o0 ))
94      if [ $rc -ne 0 ]; then muer=$((muer+1)); printf 'M'
95        echo "nivel=$NIVEL i=$i MUERTA rc=$rc t=${t}s disp=$d0 psi=$p0 oom_delta=$od" >> "$RAIZ/work/caza-presion.txt"
96      else ok=$((ok+1)); printf '.'; fi
97      [ $od -gt 0 ] && printf '(oom+%d)' $od
98    fi
99  done
100  echo ""; echo "   nivel $NIVEL: ok=$ok COLGADAS=$colg muertas=$muer  (disponible final $(disp) MiB, psi $(psi))"
101  para_hog
102done