atuq: no era el perfil — son DOS SALIDAS, y el «3 de 13» medía la cámara

Dos cosas antes de la medición: el MOZ_LOG de las que pintan y las que no termina IDÉNTICO (las dos
commitean WaylandBufferSHM de 1280x696), y el cruce de las 15 corridas está confundido — las 13 de
«perfil del usuario» salieron todas con VGA=1.

`screendump` de QMP fotografía UN dispositivo. Con VGA=1 hay dos, y hasta ahora se miraba uno. Con
`id=` en cada uno y capturando los dos en cada toma, en la misma corrida y el mismo instante:

    +312s  ██ PRIMERA PINTURA — 704456 px EN vga0
           gpu0 = 962675 px de fondo + panel y dock (el escritorio, SIN navegador)
           vga0 = 703766 px magenta                 (la ventana, con la página)

⇒ cosmic-comp maneja las DOS salidas y la ventana cae en una u otra. «Pinta 3 de 13» era cuántas
veces cayó en la pantalla que yo fotografiaba: una tasa de mi cámara, no del producto. El §6.10.ter
tiene la misma explicación (aquel arranque también traía `drm: card0 card1`).

Queda en pie, ya sin confundido: el navegador arranca, mapea y commitea SIEMPRE; cuando la ventana
está en la pantalla que se mira, se ve entre +184 y +312 s; el perfil no era la causa y cosmic-comp
nesteado tampoco.

⚠ Tercera vez en el día con la misma forma: un ✗ de una captura de UNA pantalla no dice «la ventana
no está», dice «no está en ESA» — igual que `mapped 1` no probaba que se viera. El estado anota ahora
en qué pantalla apareció y cuántas se fotografiaron, y el vigía NO cuenta las ciegas: las nombra.
This commit is contained in:
Sergio
2026-09-14 18:57:00 +00:00
parent 26dd2233dc
commit 5b98d1b2bf
4 changed files with 162 additions and 34 deletions
+36 -13
View File
@@ -46,7 +46,13 @@ SMP = os.environ.get("SMP", "2")
# Un compositor con dos tarjetas puede componer en la que el `screendump` NO muestra. Por eso el
# default acá es UNA sola (-vga none) y la reproducción del caso original es una opción explícita:
# son dos mediciones distintas y hay que poder pedir cada una por su nombre.
VGA_ARGS = [] if os.environ.get("VGA") == "1" else ["-vga", "none"]
# Las dos pantallas van con ID EXPLÍCITO (`vga0`, `gpu0`) y no por el `-vga std` implícito, porque
# `screendump` de QMP fotografía UN dispositivo y hay que poder pedir cada uno por su nombre: con dos
# salidas, el compositor puede poner la ventana en la que la captura por defecto NO muestra, y eso se
# ve exactamente igual que «la ventana no aparece».
DOS_PANTALLAS = os.environ.get("VGA") == "1"
VGA_ARGS = (["-vga", "none", "-device", "VGA,id=vga0"] if DOS_PANTALLAS else ["-vga", "none"])
PANTALLAS = ["gpu0", "vga0"] if DOS_PANTALLAS else ["gpu0"]
class Serial:
@@ -115,6 +121,18 @@ class Serial:
return self.esperar(marca, segundos, nombre=f"de «{orden[:40]}»")
def capturar(ruta_qmp, out, nombre):
"""Fotografía CADA pantalla del guest y devuelve {pantalla: fichero}.
Con dos salidas hay que mirar las dos: la ventana puede estar viva y dibujando en la que no se
fotografía, y eso es indistinguible de «no pinta» si sólo se mira una.
"""
ficheros = {p: os.path.join(out, f"{nombre}-{p}.ppm") for p in PANTALLAS}
qmp(ruta_qmp, [{"execute": "screendump", "arguments": {"filename": f, "device": p}}
for p, f in ficheros.items()])
return ficheros
def pintado(ppm):
"""¿Cuántos píxeles de la pantalla son la página de prueba?
@@ -132,7 +150,7 @@ def pintado(ppm):
return sum(n for n, (r, g, b) in im.getcolors(1 << 24) if r > 90 and b > 90 and g < 40)
def anotar_medicion(segundos, a):
def anotar_medicion(segundos, a, pantalla=None):
"""Deja el tiempo de primera pintura en `docs/state/primera-pintura.json`.
Va al estado del repo y no a un log porque quien pregunta «¿esta imagen se puede usar?» es
@@ -145,11 +163,14 @@ def anotar_medicion(segundos, a):
"medido": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"imagen": IMG,
"segundos_primera_pintura": segundos, # None = no pintó dentro de la ventana
"pantalla": pantalla, # EN CUÁL de las salidas apareció (medido 2026-09-14:
# con dos, la ventana cae en cualquiera de las dos)
"pantallas_fotografiadas": len(PANTALLAS), # con 1 y dos salidas, un ✗ NO es concluyente
"presupuesto_s": a.budget,
"ventana_s": a.wait,
"cadencia_s": a.cadence, # la RESOLUCIÓN del número: ±cadencia
"lanzamiento": "perfil del usuario" if a.as_user else "perfil nuevo en tmpfs",
"video": "vga+virtio-gpu (dos DRM)" if VGA_ARGS == [] else "virtio-gpu solo (-vga none)",
"video": "vga+virtio-gpu (dos DRM)" if DOS_PANTALLAS else "virtio-gpu solo (-vga none)",
"mem_mb": int(MEM), "vcpus": int(SMP), "acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py",
}
@@ -214,7 +235,7 @@ def main():
qemu = [
"nice", "-n", "10", "qemu-system-x86_64",
"-machine", "q35", "-accel", "tcg", "-cpu", "max", "-m", MEM, "-smp", SMP,
*VGA_ARGS, "-device", "virtio-gpu-pci", "-display", "none", "-nic", "none",
*VGA_ARGS, "-device", "virtio-gpu-pci,id=gpu0", "-display", "none", "-nic", "none",
"-drive", f"if=pflash,format=raw,unit=0,file={OVMF_CODE},readonly=on",
"-drive", f"if=pflash,format=raw,unit=1,file={vars_fd}",
"-drive", f"file={IMG},format=raw,if=virtio",
@@ -244,8 +265,8 @@ def main():
print(ser.cmd("echo ENTORNO: WD=$WAYLAND_DISPLAY XDG=$XDG_RUNTIME_DIR DBUS=$DBUS_SESSION_BUS_ADDRESS").strip())
print(ser.cmd("ls -l $XDG_RUNTIME_DIR; ls /usr/lib/dri").strip())
qmp(qmp_sock, [{"execute": "screendump", "arguments": {"filename": os.path.join(out, "antes.ppm")}}])
print("== captura ANTES", flush=True)
capturar(qmp_sock, out, "antes")
print("== captura ANTES (%s)" % ", ".join(PANTALLAS), flush=True)
if a.only_cosmic:
print("== CONTROL: no se lanza el navegador")
@@ -278,21 +299,23 @@ def main():
primera = None # segundos hasta la PRIMERA captura con la página en pantalla
while time.time() - t0 < a.wait:
time.sleep(a.cadence); n += 1
shot = os.path.join(out, f"durante-{n}.ppm")
qmp(qmp_sock, [{"execute": "screendump", "arguments": {"filename": shot}}])
tomas = capturar(qmp_sock, out, f"durante-{n}")
t = int(time.time() - t0)
px = pintado(shot)
medidas = {p: pintado(f) for p, f in tomas.items()}
px = max((v for v in medidas.values() if v is not None), default=None)
donde = next((p for p, v in medidas.items() if v and v > 100_000), None)
if primera is None and px and px > 100_000:
primera = t
print(f" +{t}s ██ PRIMERA PINTURA — {px} px de la página", flush=True)
print(f" +{t}s ██ PRIMERA PINTURA — {px} px de la página EN {donde}", flush=True)
if a.until_paint:
break
else:
vivos = ser.cmd("for c in /proc/[0-9]*/comm; do cat $c 2>/dev/null; done | grep -c atuq")
v = vivos.splitlines()[1].strip() if len(vivos.splitlines()) > 1 else "?"
print(f" +{t}s px de la página: {px if px is not None else '?'} procesos atuq: {v}", flush=True)
detalle = " ".join(f"{p}={medidas[p]}" for p in PANTALLAS)
print(f" +{t}s px de la página: {detalle} procesos atuq: {v}", flush=True)
qmp(qmp_sock, [{"execute": "screendump", "arguments": {"filename": os.path.join(out, "despues.ppm")}}])
capturar(qmp_sock, out, "despues")
print("== captura DESPUES", flush=True)
# EL NÚMERO. No «pinta / no pinta»: CUÁNTO TARDA. Medido el 2026-09-14 sobre esta misma
@@ -314,7 +337,7 @@ def main():
# El número se PUBLICA donde lo pueda leer el vigía de la imagen, que es estático y no
# arranca nada: sin esto el dato vive en el scrollback del que corrió la VM y se pierde,
# que es exactamente lo que pasó con la corrida a mano del §6.10.ter.
anotar_medicion(primera, a)
anotar_medicion(primera, a, donde if primera is not None else None)
# La evidencia que no se puede leer de una captura: qué hizo el widget de Wayland.
print("---- atuq.log (cola) ----")