perfilar.sh: la corrida de entrenamiento PGO, con las tres defensas puestas

Estaba en un scratchpad y se perdía; ahora vive en el repo con el porqué de cada
guarda, porque las tres se pagaron durante la primera corrida:

1. --unshare-pid en el bwrap. Sin él los hijos SOBREVIVEN al sandbox: un
   http.server quedó vivo 19 HORAS, retuvo el puerto y envenenó las corridas
   siguientes con «Address in use».

2. Puerto aleatorio por corrida. Elimina la colisión de raíz en vez de
   detectarla: si otro proceso tiene un puerto, este arranque usa otro.

3. Marcador único por corrida, servido desde una copia ESCRIBIBLE del corpus.
   La versión anterior horneaba el marcador como constante del script, y eso
   rompía justo lo que el chequeo existe para probar: el servidor huérfano
   servía el MISMO fichero de marca y el chequeo lo daba por bueno. Verificaba
   «alguien sirve este contenido», no «este servidor es el mío». Con el marcador
   por corrida, un servidor ajeno devuelve otra cosa y se aborta.

Y queda escrito por qué los sandboxes de Firefox van apagados en el
entrenamiento: con ellos los hijos mueren con signal 11, el proceso que renderiza
no nace, el servidor registra 0 GET y el único .profraw es el del padre
arrancando — un perfil de nada, con todo en verde. Es lo que hace el
profileserver.py de Mozilla. Sólo aplica al entrenamiento.

La espera al servidor va con reintento y no con un sleep fijo: 2 s concluían
«no responde» sobre uno que sí iba a responder.
This commit is contained in:
Sergio
2026-09-07 20:45:56 +00:00
parent a5bcf4fd3e
commit e74d449655
+66
View File
@@ -0,0 +1,66 @@
#!/bin/sh
# perfilar.sh — corrida de entrenamiento PGO: recorre el corpus con el firefox INSTRUMENTADO y deja
# los `.profraw` en /salida. Pensado para correr dentro de un bwrap con:
# --ro-bind <rootfs> / --ro-bind <corpus> /pgo --bind <salida> /salida --unshare-pid
#
# ══ TRES DEFENSAS QUE NO SON PARANOIA: LAS TRES SE PAGARON ═════════════════════════════════════
#
# 1. `--unshare-pid` EN EL BWRAP (fuera de este script). Sin él los hijos SOBREVIVEN al sandbox: un
# `http.server` quedó vivo 19 HORAS, retuvo el puerto y envenenó las corridas siguientes.
#
# 2. PUERTO ALEATORIO POR CORRIDA. Elimina la colisión de raíz en vez de detectarla: si otro proceso
# tiene un puerto, este arranque usa otro y no se entera.
#
# 3. MARCADOR ÚNICO POR CORRIDA, servido desde una copia ESCRIBIBLE del corpus. La primera versión
# horneaba el marcador como constante del script, y eso rompía justo lo que el chequeo existe
# para probar: un servidor ajeno servía el MISMO fichero de marca y el chequeo lo daba por bueno.
# Verificaba «alguien sirve este contenido», no «este servidor es el mío».
#
# ⚠ Y LOS SANDBOXES DE FIREFOX VAN APAGADOS. Con ellos los procesos hijo mueren con SIGNAL 11, el
# que renderiza no llega a nacer, el servidor registra 0 GET y el único `.profraw` es el del padre
# arrancando: un perfil de nada, con todo en verde. Es lo mismo que hace el `profileserver.py` de
# Mozilla. Sólo aplica al entrenamiento; el firefox que se distribuye lleva sus sandboxes intactos.
set -u
export HOME=/tmp/home; mkdir -p "$HOME"
export LANG=C.UTF-8 MOZ_HEADLESS=1 LIBGL_ALWAYS_SOFTWARE=1
export MOZ_DISABLE_CONTENT_SANDBOX=1 MOZ_DISABLE_GMP_SANDBOX=1 MOZ_DISABLE_RDD_SANDBOX=1 MOZ_DISABLE_SOCKET_PROCESS_SANDBOX=1
export LLVM_PROFILE_FILE=/salida/ff-%p-%m.profraw
MARCA="pgo-$$-$(cat /proc/sys/kernel/random/uuid 2>/dev/null || echo ${RANDOM:-0}${RANDOM:-0})"
PUERTO=$(( 20000 + $$ % 20000 ))
# Copia ESCRIBIBLE del corpus (tmpfs): hace falta para poner el marcador dentro de lo que se sirve.
SERVIR=/tmp/servir; mkdir -p "$SERVIR"; cp -a /pgo/. "$SERVIR"/ 2>/dev/null
printf '%s\n' "$MARCA" > "$SERVIR/marca.txt"
cd "$SERVIR" && /usr/bin/python3 -m http.server "$PUERTO" --bind 127.0.0.1 > /salida/httpd.log 2>&1 &
# Espera CON REINTENTO (un `sleep` fijo concluía «no responde» sobre un servidor que sí iba a
# responder) y exigiendo el marcador de ESTA corrida.
i=0
while [ $i -lt 25 ]; do
got=$(/usr/bin/python3 -c "import urllib.request
try: print(urllib.request.urlopen('http://127.0.0.1:$PUERTO/marca.txt',timeout=3).read().decode().strip())
except Exception: pass" 2>/dev/null)
[ "$got" = "$MARCA" ] && break
i=$((i+1)); sleep 1
done
[ $i -lt 25 ] || {
echo "!! en $PUERTO no contesta MI servidor (marca leída: '${got:-nada}') — abortando antes de" >&2
echo " generar perfiles de páginas que nunca se cargaron." >&2
tail -3 /salida/httpd.log >&2
exit 1
}
echo "== servidor propio OK en $PUERTO"
n=0
for f in $(cd "$SERVIR" && find . -name '*.html' | sed 's|^\./||' | sort); do
n=$((n+1))
LD_LIBRARY_PATH=/usr/lib/firefox timeout 200 /usr/lib/firefox/firefox --headless \
--screenshot /salida/shot.png "http://127.0.0.1:$PUERTO/$f" >/dev/null 2>&1
printf '.'
done
echo ""
echo "== páginas recorridas: $n"
echo "== GETs servidos: $(grep -c GET /salida/httpd.log 2>/dev/null)"
echo "== .profraw: $(ls /salida/*.profraw 2>/dev/null | wc -l)"