diff --git a/docs/26-atuq-envoltorio-gecko.md b/docs/26-atuq-envoltorio-gecko.md index 8f155c6e..4a5b7d05 100644 --- a/docs/26-atuq-envoltorio-gecko.md +++ b/docs/26-atuq-envoltorio-gecko.md @@ -2127,9 +2127,9 @@ sola cosa: `toolkit.startup.recent_crashes`, fijado en el `user.js` del perfil ( | **B** 17:44 | fijado en **16** | ✗ nada en 300 s · `set_title("Open atuq in Troubleshoot Mode?")` · `ATUQ-EXIT=0` | ⚠ **Y una honestidad que cambia la fuerza del argumento:** en la corrida A el `--crashes 0` fue un -**no-op** —el contador ya no estaba en `prefs.js`, porque Gecko lo BORRA después de mostrar el -diálogo—, así que A sola no prueba nada: prueba tanto «lo arregló el 0» como «el perfil ya estaba -limpio». **La que prueba la causa es B**, que lo pone de vuelta y rompe de vuelta. Una sola dirección +**no-op** —el contador ya no estaba en `prefs.js`, y **por qué no estaba sigue sin explicación +medida** (ver más abajo: mostrar el diálogo NO lo limpia)—, así que A sola no prueba nada: prueba +tanto «lo arregló el 0» como «el perfil ya estaba limpio». **La que prueba la causa es B**, que lo pone de vuelta y rompe de vuelta. Una sola dirección habría sido una correlación con suerte. ##### ⚠⚠ La serie de `primera-pintura.json` está CONTAMINADA, y por el propio andamiaje @@ -2160,20 +2160,51 @@ distinguía: incrementa» de «sólo después de uno interrumpido», que es la diferencia entre culpar al kill y culpar al arranque que no llegó a terminar. -⚠ **Lo que sigue sin medir es el BORDE**, y se dice para que no se lea como medido: el diálogo aparece -cuando el contador vale más que `max_resumed_crashes`, pero no está comprobado si la comparación se -hace **antes o después** del incremento de ese mismo arranque — o sea si el diálogo sale en el -arranque que EMPIEZA valiendo 3 (compara después de sumar) o en el que empieza valiendo 4 (compara lo -guardado). Las dos dan «cuatro arranques interrumpidos seguidos» como respuesta práctica, y -distinguirlas importa sólo para poder decir el número sin inventarlo. Se mide cruzando el umbral en -vivo, que es lo que está en curso. +##### El borde, medido en los dos lados: **3 guardado no alcanza, 4 sí** + +Partiendo del perfil en 2, tres fases más en un arranque (las dos primeras matadas antes de pintar): + +| fase | antes | después | qué ventana abrió | `ATUQ-EXIT` | +|---|---|---|---|---| +| k1 | 2 | 3 | **el navegador** — `set_title("atuq")`, `set_min_size(638, 120)` | 137 (la matamos) | +| k2 | 3 | **4** | **el diálogo** — `set_title("Open atuq in Troubleshoot Mode?")`, `min_size(117, 37)` | 0 | +| k3 | 4 | **5** | el diálogo otra vez | 0 | + +⇒ **la comparación es DESPUÉS de sumar**: el arranque que encuentra 3 guardado lo sube a 4, compara +4 > 3 y abre el diálogo; el que encuentra 2 pinta. Contando desde la última corrida que terminó bien, +eso son **cuatro arranques interrumpidos seguidos, y el quinto lanzamiento ya no abre el navegador**. + +Y la otra mitad del cálculo lo confirma sin ambigüedad: `toolkit.startup.last_success` vale +**1789494795 en las tres fases** —congelado en el arranque de la última corrida sana—. El contador +sube mientras `last_success` no se mueve; con las dos cifras juntas, un contador que no cambió deja +de ser ambiguo entre «no subió» y «subió y se limpió». + +⚠⚠ **Y eso REFUTA algo que este documento afirmaba dos secciones más arriba: mostrar el diálogo NO +limpia el contador.** k2 lo dejó en 4 y k3 en 5 — dos corridas consecutivas con diálogo y ninguna +limpieza. Entonces la desaparición del 16 entre las 17:12 y las 17:20 **no tiene explicación +medida**, y la atribución «Gecko lo borró al mostrarlo» se retira. Dos candidatas, las dos sin +comprobar: que el `prefs.js` se perdiera en el kill (cuando se lo leyó con `debugfs` figuraba de 0 +bytes, y ese volcado no es fiable — es la lección del párrafo de `debugfs`), o que un arranque que +TERMINA sí lo limpie, que es lo que hace upstream pero que acá no se puede atribuir: **todas las +corridas que pintaron llevaban `--crashes 0` pinneado, o sea el valor por defecto, y un pref igual al +default no se persiste**. El «ausente» de esas corridas puede ser eso. + +La medición que lo cierra, y que sale gratis de la serie sana: dejar el contador en 1 o 2 **sin +pinnearlo**, correr hasta que pinte y leerlo después. Si pasa a ausente, limpia el arranque que +termina; si sigue en 2, no limpia nada y lo del 16 fue el fichero. + +**Un discriminador que salió gratis y sirve para siempre:** el `ATUQ-EXIT` de una fase que matamos es +**137** (128+9) y el de una corrida que se fue sola por el diálogo es **0**. El código de salida +distingue «la mataron» de «se fue sola» sin mirar nada más — que es justo lo que el §6.10.terdecies +no podía distinguir de una captura. Qué se hizo con eso, en vez de borrar el fichero: - **el contador viaja con cada medición**: `recent_crashes_antes`, `recent_crashes_despues` y `recent_crashes_fijado` son campos de cada corrida de `docs/state/primera-pintura.json`. Antes y - después porque Gecko lo limpia al mostrar el diálogo: sin el «después», la corrida siguiente parece - arreglarse sola y la anterior parece intermitente; + después porque el número cambia DENTRO de la corrida —lo escribe el arranque, no el kill— y porque + sin el «después» no se puede encadenar una corrida con la siguiente, que es lo que convirtió esto + en una serie legible; - **el vigía las APARTA y lo dice**: `scripts/vigia-imagen.py` saca de la tasa las corridas con el contador > 3 y, para las 21 anteriores al campo, informa que **no se pueden clasificar** en vez de contarlas como buenas. Un denominador que se marca, no que se borra. @@ -2237,10 +2268,11 @@ con el árbol de fuentes, pero con la mitigación ya puesta. Dos consecuencias: `set_max_size(16332, 16332)`; el diálogo pide `set_min_size(117, 37)` y `set_max_size(348, 16332)`. Con mirar el `min_size` se sabe cuál abrió, sin leer el título. - Dos cosas más de esa corrida: `recent_crashes_despues` salió **ausente** —la corrida que pinta - LIMPIA el contador, que es el mecanismo visto desde el otro lado y explica el «se arregla sola» - sin ninguna hipótesis— y el `xulstore` quedó igual: **una corrida sana no ensucia lo que el perfil - recuerda**. + Dos cosas más de esa corrida: el `xulstore` quedó igual ⇒ **una corrida sana no ensucia lo que el + perfil recuerda**; y `recent_crashes_despues` salió **ausente**, que en su momento se leyó como + «la corrida que pinta limpia el contador» y **hay que no leerlo así** — esa corrida llevaba + `--crashes 0` pinneado, o sea el valor por DEFECTO, y un pref igual al default no se persiste. El + «ausente» puede ser eso y no una limpieza. ### 6.11 Y ahora la pregunta que faltaba: ¿REPRODUCE? (2026-09-07) diff --git a/docs/state/primera-pintura.json b/docs/state/primera-pintura.json index 7f53c290..bd299302 100644 --- a/docs/state/primera-pintura.json +++ b/docs/state/primera-pintura.json @@ -1,112 +1,5 @@ { "corridas": [ - { - "medido": "2026-09-14T16:17:13Z", - "imagen": "/mnt/cosecha/takana-cosmic-qemu.img", - "segundos_primera_pintura": null, - "presupuesto_s": 420, - "ventana_s": 480, - "cadencia_s": 30, - "lanzamiento": "perfil del usuario", - "video": "vga+virtio-gpu (dos DRM)", - "mem_mb": 2560, - "vcpus": 2, - "acel": "tcg", - "guion": "scripts/cosmic/atuq-en-imagen.py", - "pantallas_fotografiadas": 1 - }, - { - "medido": "2026-09-14T16:26:43Z", - "imagen": "/mnt/cosecha/takana-cosmic-qemu.img", - "segundos_primera_pintura": 184, - "presupuesto_s": 420, - "ventana_s": 480, - "cadencia_s": 30, - "lanzamiento": "perfil del usuario", - "video": "vga+virtio-gpu (dos DRM)", - "mem_mb": 2560, - "vcpus": 2, - "acel": "tcg", - "guion": "scripts/cosmic/atuq-en-imagen.py", - "pantallas_fotografiadas": 1, - "pantalla": "desconocida (una sola captura)" - }, - { - "medido": "2026-09-14T16:39:48Z", - "imagen": "/mnt/cosecha/takana-cosmic-qemu.img", - "segundos_primera_pintura": null, - "presupuesto_s": 420, - "ventana_s": 480, - "cadencia_s": 30, - "lanzamiento": "perfil del usuario", - "video": "vga+virtio-gpu (dos DRM)", - "mem_mb": 2560, - "vcpus": 2, - "acel": "tcg", - "guion": "scripts/cosmic/atuq-en-imagen.py", - "pantallas_fotografiadas": 1 - }, - { - "medido": "2026-09-14T16:48:45Z", - "imagen": "/mnt/cosecha/takana-cosmic-qemu.img", - "segundos_primera_pintura": 199, - "presupuesto_s": 420, - "ventana_s": 480, - "cadencia_s": 30, - "lanzamiento": "perfil del usuario", - "video": "vga+virtio-gpu (dos DRM)", - "mem_mb": 2560, - "vcpus": 2, - "acel": "tcg", - "guion": "scripts/cosmic/atuq-en-imagen.py", - "pantallas_fotografiadas": 1, - "pantalla": "desconocida (una sola captura)" - }, - { - "medido": "2026-09-14T17:03:19Z", - "imagen": "/mnt/cosecha/takana-cosmic-qemu.img", - "segundos_primera_pintura": null, - "presupuesto_s": 420, - "ventana_s": 480, - "cadencia_s": 30, - "lanzamiento": "perfil del usuario", - "video": "vga+virtio-gpu (dos DRM)", - "mem_mb": 2560, - "vcpus": 2, - "acel": "tcg", - "guion": "scripts/cosmic/atuq-en-imagen.py", - "pantallas_fotografiadas": 1 - }, - { - "medido": "2026-09-14T17:18:52Z", - "imagen": "/mnt/cosecha/takana-cosmic-qemu.img", - "segundos_primera_pintura": null, - "presupuesto_s": 420, - "ventana_s": 480, - "cadencia_s": 30, - "lanzamiento": "perfil del usuario", - "video": "vga+virtio-gpu (dos DRM)", - "mem_mb": 2560, - "vcpus": 2, - "acel": "tcg", - "guion": "scripts/cosmic/atuq-en-imagen.py", - "pantallas_fotografiadas": 1 - }, - { - "medido": "2026-09-14T17:34:16Z", - "imagen": "/mnt/cosecha/takana-cosmic-qemu.img", - "segundos_primera_pintura": null, - "presupuesto_s": 420, - "ventana_s": 480, - "cadencia_s": 30, - "lanzamiento": "perfil del usuario", - "video": "vga+virtio-gpu (dos DRM)", - "mem_mb": 2560, - "vcpus": 2, - "acel": "tcg", - "guion": "scripts/cosmic/atuq-en-imagen.py", - "pantallas_fotografiadas": 1 - }, { "medido": "2026-09-14T17:51:50Z", "imagen": "/mnt/cosecha/takana-cosmic-qemu.img", @@ -475,6 +368,139 @@ "recent_crashes_fijado": 16, "recent_crashes_antes": null, "recent_crashes_despues": 16 + }, + { + "medido": "2026-09-15T17:56:31Z", + "imagen": "/mnt/cosecha/takana-cosmic-qemu.img", + "segundos_primera_pintura": 195, + "pantalla": "gpu0", + "pantallas_fotografiadas": 1, + "presupuesto_s": 420, + "ventana_s": 300, + "cadencia_s": 60, + "lanzamiento": "perfil del usuario", + "video": "virtio-gpu solo (-vga none)", + "mem_mb": 2560, + "vcpus": 2, + "acel": "tcg", + "guion": "scripts/cosmic/atuq-en-imagen.py", + "recent_crashes_fijado": 0, + "recent_crashes_antes": 16, + "recent_crashes_despues": null + }, + { + "medido": "2026-09-15T18:04:44Z", + "imagen": "/mnt/cosecha/takana-cosmic-qemu.img", + "segundos_primera_pintura": null, + "pantalla": null, + "pantallas_fotografiadas": 1, + "presupuesto_s": 420, + "ventana_s": 60, + "cadencia_s": 30, + "lanzamiento": "perfil del usuario", + "video": "virtio-gpu solo (-vga none)", + "mem_mb": 2560, + "vcpus": 2, + "acel": "tcg", + "guion": "scripts/cosmic/atuq-en-imagen.py", + "recent_crashes_fijado": null, + "recent_crashes_antes": null, + "recent_crashes_despues": null + }, + { + "medido": "2026-09-15T18:06:05Z", + "imagen": "/mnt/cosecha/takana-cosmic-qemu.img", + "segundos_primera_pintura": null, + "pantalla": null, + "pantallas_fotografiadas": 1, + "presupuesto_s": 420, + "ventana_s": 60, + "cadencia_s": 30, + "lanzamiento": "perfil del usuario", + "video": "virtio-gpu solo (-vga none)", + "mem_mb": 2560, + "vcpus": 2, + "acel": "tcg", + "guion": "scripts/cosmic/atuq-en-imagen.py", + "recent_crashes_fijado": null, + "recent_crashes_antes": null, + "recent_crashes_despues": 1 + }, + { + "medido": "2026-09-15T18:07:24Z", + "imagen": "/mnt/cosecha/takana-cosmic-qemu.img", + "segundos_primera_pintura": null, + "pantalla": null, + "pantallas_fotografiadas": 1, + "presupuesto_s": 420, + "ventana_s": 60, + "cadencia_s": 30, + "lanzamiento": "perfil del usuario", + "video": "virtio-gpu solo (-vga none)", + "mem_mb": 2560, + "vcpus": 2, + "acel": "tcg", + "guion": "scripts/cosmic/atuq-en-imagen.py", + "recent_crashes_fijado": null, + "recent_crashes_antes": 1, + "recent_crashes_despues": 2 + }, + { + "medido": "2026-09-15T18:13:42Z", + "imagen": "/mnt/cosecha/takana-cosmic-qemu.img", + "segundos_primera_pintura": null, + "pantalla": null, + "pantallas_fotografiadas": 1, + "presupuesto_s": 420, + "ventana_s": 60, + "cadencia_s": 30, + "lanzamiento": "perfil del usuario", + "video": "virtio-gpu solo (-vga none)", + "mem_mb": 2560, + "vcpus": 2, + "acel": "tcg", + "guion": "scripts/cosmic/atuq-en-imagen.py", + "recent_crashes_fijado": null, + "recent_crashes_antes": 2, + "recent_crashes_despues": 3 + }, + { + "medido": "2026-09-15T18:15:02Z", + "imagen": "/mnt/cosecha/takana-cosmic-qemu.img", + "segundos_primera_pintura": null, + "pantalla": null, + "pantallas_fotografiadas": 1, + "presupuesto_s": 420, + "ventana_s": 60, + "cadencia_s": 30, + "lanzamiento": "perfil del usuario", + "video": "virtio-gpu solo (-vga none)", + "mem_mb": 2560, + "vcpus": 2, + "acel": "tcg", + "guion": "scripts/cosmic/atuq-en-imagen.py", + "recent_crashes_fijado": null, + "recent_crashes_antes": 3, + "recent_crashes_despues": 4 + }, + { + "medido": "2026-09-15T18:16:18Z", + "imagen": "/mnt/cosecha/takana-cosmic-qemu.img", + "segundos_primera_pintura": null, + "pantalla": null, + "pantallas_fotografiadas": 1, + "presupuesto_s": 420, + "ventana_s": 60, + "cadencia_s": 30, + "lanzamiento": "perfil del usuario", + "video": "virtio-gpu solo (-vga none)", + "mem_mb": 2560, + "vcpus": 2, + "acel": "tcg", + "guion": "scripts/cosmic/atuq-en-imagen.py", + "recent_crashes_fijado": null, + "recent_crashes_antes": 4, + "recent_crashes_despues": 5 } ] } diff --git a/scripts/cosmic/atuq-en-imagen.py b/scripts/cosmic/atuq-en-imagen.py index 07c46191..b176d9cd 100755 --- a/scripts/cosmic/atuq-en-imagen.py +++ b/scripts/cosmic/atuq-en-imagen.py @@ -255,13 +255,21 @@ def main(): "que es la única forma de subirle el nivel de log sin recompilar") ap.add_argument("--crashes", type=int, metavar="N", help="fija `toolkit.startup.recent_crashes` del perfil del usuario antes de " - "lanzar: 0 lo limpia, >3 reproduce el diálogo de Modo de resolución de " - "problemas. Sólo tiene efecto con --as-user") + "lanzar: 0 lo limpia, 4 o más reproduce el diálogo de Modo de resolución " + "de problemas (medido: con 3 guardado todavía pinta, porque el arranque " + "suma ANTES de comparar). Sólo tiene efecto con --as-user. ⚠ 0 es el valor " + "por DEFECTO y un pref igual al default no se persiste: si lo que querés " + "medir es quién limpia el contador, NO lo pinnees") ap.add_argument("--xulstore", metavar="ANCHOxALTO", help="agrega una SEGUNDA fase en el mismo arranque con `xulstore.json` sembrado " "(p. ej. 1152x720): la ventana arranca con un tamaño YA PERSISTIDO. Es el " "control del 117x70 — si con semilla sale grande, lo que falla es que nadie " "le da tamaño en el primer arranque") + ap.add_argument("--repeat", type=int, metavar="N", + help="corre N fases IDÉNTICAS en el mismo arranque. Con `--wait` corto (60 s, o " + "sea antes de que pinte) mide el contador de caídas: cada fase lee " + "`recent_crashes` ANTES de lanzar, así que la serie 0,1,2… dice cuánto suma " + "un arranque interrumpido — y si no sube, el envenenamiento vino de otro lado") ap.add_argument("--out", default=os.path.join(RAIZ, "work/atuq-imagen")) a = ap.parse_args() @@ -400,8 +408,12 @@ def main(): # que CADA corrida fallida puede sumar uno: el instrumento degrada el sujeto y # la tasa medida deja de ser del producto. Sin este número impreso, una corrida # no se puede interpretar. - volcado = ser.cmd("grep -E 'toolkit.startup.(recent_crashes|max_resumed)|" - "browser.sessionstore.max_resumed' %s/prefs.js" % d, 60) + # ⚠ `last_success` VA EN EL MISMO VOLCADO. Es la otra mitad del cálculo: sin + # él, un `recent_crashes` que no se movió es ambiguo entre «no subió» y «subió y + # lo limpió el gancho de cierre», que son conclusiones opuestas sobre la misma + # cifra. + volcado = ser.cmd("grep -E 'toolkit.startup.(recent_crashes|max_resumed|" + "last_success)|browser.sessionstore.max_resumed' %s/prefs.js" % d, 60) print(volcado.strip()) estado["recent_crashes_antes"] = leer_contador_de_caidas(volcado) else: @@ -455,8 +467,12 @@ def main(): línea de orden del shell del serial y ahí se mata la sesión entera (medido en el hub, sale 144 y lo que sigue no corre). """ + # ⚠ `-9` A PROPÓSITO. Un `kill` a secas le da a Gecko su apagado ordenado, y un + # apagado ordenado es EXACTAMENTE la variable bajo prueba: lo que hace este arnés al + # terminar es matar la VM entera, que no avisa. Con `-KILL` la fase siguiente + # encuentra el mismo estado que encontraría tras una corrida matada de verdad. ser.cmd("for p in /proc/[0-9]*; do grep -q atuq $p/comm 2>/dev/null && " - "kill ${p#/proc/}; done; true", 60) + "kill -9 ${p#/proc/}; done; true", 60) time.sleep(8) salida = ser.cmd("for c in /proc/[0-9]*/comm; do cat $c 2>/dev/null; done | grep -c atuq") # El serial hace ECO de la orden, así que la respuesta es la ÚLTIMA línea que sea un @@ -477,10 +493,14 @@ def main(): fases = [("base", None)] if a.xulstore: fases.append(("semilla-%s" % a.xulstore, a.xulstore)) + if a.repeat: + # N fases iguales: lo que varía entre ellas no es la configuración sino el ESTADO que + # deja la anterior en el perfil, que es justo lo que se quiere medir. + fases = [("k%d" % i, None) for i in range(1, a.repeat + 1)] for fase, semilla in fases: print("\n===== FASE «%s» =====" % fase, flush=True) - if semilla: + if fase != fases[0][0]: matar_al_navegador() perfil_d = perfil_de_la_fase(semilla) log_atuq = "/var/log/cosmic/atuq-%s.log" % fase @@ -545,10 +565,11 @@ def main(): rc[0] = 1 if perfil_d and a.as_user: - # ⚠ EL ESTADO FINAL ES PARTE DE LA PRUEBA. Gecko BORRA el contador después de - # mostrar el diálogo, así que un perfil envenenado se ve limpio en cuanto se - # lo mira DESPUÉS: sin este segundo volcado, la corrida siguiente parece - # «arreglada sola» y la anterior parece intermitente. Se lee con el navegador + # ⚠ EL ESTADO FINAL ES PARTE DE LA PRUEBA: el número cambia DENTRO de la + # corrida —lo escribe el ARRANQUE al encontrar el anterior sin terminar, no el + # kill— así que sin el «después» no se puede encadenar una corrida con la + # siguiente, que es lo que convierte esto en una serie legible (medido: + # 2 → 3 → 4 en tres fases de un mismo arranque). Se lee con el navegador # todavía vivo, o sea que refleja el último volcado de prefs de Gecko. volcado = ser.cmd("grep -E 'toolkit.startup.recent_crashes' %s/prefs.js || " "echo '(el pref no está: arranque sin caídas pendientes)'"