[FIX] script todo: régler l'écran de l'émulateur au lancement, densité comprise

Écrire hw.lcd.* dans le config.ini de l'AVD ne servait à rien : l'émulateur
réécrit ce fichier depuis le profil du téléphone au premier démarrage, et l'AVD
repartait en 1080x2400 densité 420 — quatre fois les pixels voulus. Constaté sur
la VM, où le réglage était censé s'appliquer depuis des semaines.

La taille passe donc au lancement, et la DENSITÉ avec elle. C'est ce point qui
surprend, et il est mesuré sur une charge identique : 540x1140 en densité 420 est
PIRE que le plein écran — 81 ms de médiane contre 40, 57 % d'images en retard
contre 37, tout étant rendu énorme. En densité 240 : 38 ms, 32 %, et le 99e
centile tombe de 950 ms à 250.

« -no-snapshot-save » vient avec : ce menu propose de tuer l'émulateur par
pkill, et le lancement suivant mourait alors sur « A snapshot operation is
pending ». Vérifié après un pkill : plus aucun FATAL.

--- EN ---

Writing hw.lcd.* into the AVD's config.ini did nothing: the emulator rewrites
that file from the phone profile on first boot, and the AVD came back at
1080x2400 density 420 — four times the intended pixels. Found on the VM, where
the setting was supposed to have applied for weeks.

The size therefore moves to launch time, and the DENSITY with it. That is the
surprising part, measured on an identical workload: 540x1140 at density 420 is
WORSE than the full screen — 81 ms median against 40, 57 % janky frames against
37, everything rendered huge. At density 240: 38 ms, 32 %, and the 99th
percentile drops from 950 ms to 250.

"-no-snapshot-save" comes along: this menu offers to kill the emulator with
pkill, and the next start then died on "A snapshot operation is pending".
Checked after a pkill: no FATAL left.

Assisted-by: Claude Opus 5
This commit is contained in:
Mathieu Benoit 2026-08-21 21:49:44 -04:00
parent 2c181c60d6
commit a98d6ec9cd
3 changed files with 98 additions and 33 deletions

View file

@ -1659,6 +1659,26 @@ class TODO:
# Commande de l'émulateur dans la VM. Le chemin est ABSOLU : un
# « ssh hôte 'commande' » ne lit ni ~/.profile ni ~/.bashrc.
_QEMU_EMULATOR_BIN = "$HOME/android/emulator/emulator"
# Drapeaux passés à CHAQUE lancement, et non écrits dans le config.ini de
# l'AVD : l'émulateur réécrit ce fichier depuis le profil du téléphone au
# premier démarrage, et les hw.lcd.* y étaient effacés — l'AVD repartait en
# 1080x2400 densité 420, quatre fois les pixels voulus. Mesuré.
#
# La résolution et la DENSITÉ vont ensemble, et c'est contre-intuitif :
# 540x1140 en densité 420 est PIRE que le plein écran — 81 ms de médiane
# contre 40, et 57 % d'images en retard contre 37, tout étant rendu énorme.
# Avec la densité 240, la queue s'effondre : 99e centile à 250 ms contre
# 950, et 32 % d'images en retard.
#
# « -no-snapshot-save » : sans lui, un émulateur tué par pkill — ce que ce
# menu propose lui-même — laisse un instantané en cours, et le lancement
# SUIVANT meurt sur « A snapshot operation is pending and timeout has
# expired ». Vécu, et le message ne dit pas quoi faire.
_QEMU_EMULATOR_FLAGS = (
"-no-audio -no-boot-anim -no-snapshot-save -gpu swangle"
" -skin 540x1140 -prop qemu.sf.lcd_density=240"
)
_QEMU_AVD_NAME = "erplibre"
def _qemu_emulator_running(self, target, src="virsh"):
@ -1778,8 +1798,8 @@ class TODO:
# partir d'ici, où il n'y a pas d'écran à lui donner.
print(f"\n {t('Run this on YOUR workstation:')}")
print(
f"\n ssh -XC {target} '{emu} -avd {avd}"
" -no-audio -no-boot-anim'\n"
f"\n ssh -XC {target} '{emu} -avd {avd} "
f"{self._QEMU_EMULATOR_FLAGS}'\n"
)
print(
f" {t('X11 compression is on (-XC); the screen is 540x1140.')}"
@ -1792,8 +1812,9 @@ class TODO:
# l'émulateur refuse de démarrer. setsid le détache, pour qu'il survive
# à la fermeture de ce ssh.
start = (
f'setsid -f sg kvm -c "{emu} -avd {avd} -no-window -no-audio'
' -no-boot-anim > /tmp/erplibre-emulator.log 2>&1"'
f'setsid -f sg kvm -c "{emu} -avd {avd} -no-window '
f"{self._QEMU_EMULATOR_FLAGS}"
' > /tmp/erplibre-emulator.log 2>&1"'
)
res = subprocess.run(
["ssh"] + self._qemu_ssh_opts(src) + [target, start],
@ -1865,8 +1886,8 @@ class TODO:
)
print(
f"\n ssh {target} '{self._QEMU_EMULATOR_BIN} "
f"-avd {self._QEMU_AVD_NAME}"
" -no-window -no-audio -no-boot-anim'\n"
f"-avd {self._QEMU_AVD_NAME} -no-window "
f"{self._QEMU_EMULATOR_FLAGS}'\n"
)
print(f" {t('2. Open the tunnel from YOUR workstation:')}")
if src == "ssh_config":
@ -6692,16 +6713,16 @@ class TODO:
"dev=$(cat $HOME/.erplibre-avd-device); "
'echo no | avdmanager create avd -n erplibre -k "$img" '
'-d "$dev" --force && '
# Rendu logiciel, écrit dans la config : par ssh -X il n'y a pas de
# GLX direct, et « auto » donnerait un écran noir.
# L'écran, RÉDUIT, et c'est ce réglage qui décide du confort. Le
# profil Pixel donne 1080x2400, soit 2,6 Mpixels à pousser
# image par image à travers SSH, en rendu logiciel : « ça se
# lance mais c'est trop lent ». En 540x1140 il en reste
# 0,62 Mpixel — 4,2 fois moins. Android gère la densité et
# l'application ne s'en aperçoit pas ; qui veut la taille
# réelle l'écrase au lancement par « -skin 1080x2400 ».
'printf "hw.gpu.enabled=yes\\nhw.gpu.mode=swangle\\nhw.lcd.width=540\\nhw.lcd.height=1140\\nhw.lcd.density=240\\n" '
# Rendu logiciel, écrit dans la config : par ssh -X il n'y a pas
# de GLX direct, et « auto » donnerait un écran noir. Ces deux
# clés-là SURVIVENT, elles ne viennent pas du profil du téléphone.
#
# L'écran, en revanche, ne s'écrit PAS ici : l'émulateur réécrit
# config.ini depuis le profil Pixel au premier démarrage, et les
# hw.lcd.* y étaient effacés — l'AVD repartait en 1080x2400
# densité 420. C'est donc au LANCEMENT qu'il se règle, par
# _QEMU_EMULATOR_FLAGS, et la commande affichée plus bas les porte.
'printf "hw.gpu.enabled=yes\\nhw.gpu.mode=swangle\\n" '
">> $HOME/.android/avd/erplibre.avd/config.ini' && "
f'echo " ✅ {t("AVD ready:")} '
"$(cat $HOME/.erplibre-avd-device) / "
@ -6717,10 +6738,11 @@ class TODO:
# found ». Vécu, sur la ligne que ce message affichait lui-même.
f'echo " {t("open it from your workstation:")} '
# « -XC » et non « -X » : la compression X11 change tout sur un
# écran distant. « -no-boot-anim » retire une animation qui
# ne sert qu'à faire attendre.
# écran distant. Les autres drapeaux viennent de la même autorité
# que le lancement du menu : écran réduit, densité qui va avec, et
# pas d'instantané en attente si on tue l'émulateur.
'ssh -XC erplibre@$ip \\"$HOME/android/emulator/emulator '
'-avd erplibre -no-audio -no-boot-anim\\""; '
f'-avd erplibre {self._QEMU_EMULATOR_FLAGS}\\""; '
f'echo " {t("then install the APK:")} '
# « -t » : l'ABI injectée fait marquer l'APK « testOnly » par AGP,
# et adb le refuse sans ce drapeau — « INSTALL_FAILED_TEST_ONLY ».

View file

@ -526,17 +526,29 @@ class TestAndroidEmulator(unittest.TestCase):
"avd", self.todo._qemu_tools_for(("avd",), "amd64", "", "ubuntu")
)
def test_the_screen_is_small_enough_to_travel(self):
"""Le profil Pixel donne 1080x2400 : 2,6 Mpixels par image à pousser
dans SSH, et « ça se lance mais c'est trop lent ». Mesuré après
réduction : 540x1140 confirmé par « wm size », et la capture pleine
page tombe de 220 Ko à 49 Ko."""
for key in (
"hw.lcd.width=540",
"hw.lcd.height=1140",
"hw.lcd.density=240",
):
self.assertIn(key, self.cmd, key)
def test_the_screen_is_set_at_launch_not_in_the_config(self):
"""Écrire hw.lcd.* dans config.ini ne SERT À RIEN : l'émulateur réécrit
ce fichier depuis le profil du téléphone au premier démarrage, et l'AVD
repartait en 1080x2400 densité 420 — constaté sur la VM. La taille se
règle donc au lancement, et la commande affichée la porte."""
self.assertNotIn("hw.lcd.width", self.cmd)
self.assertIn("-skin 540x1140", self.cmd)
# Ces deux clés-là survivent : elles ne viennent pas du profil.
self.assertIn("hw.gpu.mode=swangle", self.cmd)
def test_the_density_travels_with_the_resolution(self):
"""Contre-intuitif, et mesuré : 540x1140 en densité 420 est PIRE que le
plein écran — 81 ms de médiane contre 40, et 57 % d'images en retard
contre 37, tout étant rendu énorme. Avec la densité 240 : 38 ms, 32 %,
et le 99e centile tombe de 950 ms à 250."""
self.assertIn("qemu.sf.lcd_density=240", self.cmd)
def test_a_killed_emulator_does_not_block_the_next_start(self):
"""Ce menu propose lui-même de tuer l'émulateur par pkill. Sans
« -no-snapshot-save », le lancement suivant meurt sur « A snapshot
operation is pending and timeout has expired » — vécu, et le message ne
dit pas quoi faire."""
self.assertIn("-no-snapshot-save", self.cmd)
def test_the_printed_command_compresses_the_display(self):
"""« -XC » plutôt que « -X » sur un écran distant."""

View file

@ -155,8 +155,16 @@ class _MenuCase(unittest.TestCase):
self.todo._qemu_self_address = staticmethod(lambda: ("10.0.0.2", True))
self.calls = []
def _play(self, answers, running=0, start_rc=0, port_taken=False,
probe="", running_after=1, log="rien"):
def _play(
self,
answers,
running=0,
start_rc=0,
port_taken=False,
probe="",
running_after=1,
log="rien",
):
"""Joue le menu avec des réponses données ; rend (sortie, commandes).
« running » est le compte AVANT le démarrage, « running_after » celui
@ -254,6 +262,28 @@ class TestEmulatorMenu(_MenuCase):
self.assertIn("setsid -f", cmd)
self.assertIn("/tmp/erplibre-emulator.log", cmd)
def test_the_start_carries_the_shared_flags(self):
"""Une seule autorité pour ces drapeaux : le lancement du menu, la
commande fenêtrée et celle de l'étape AVD doivent dire la même chose.
La densité va avec la résolution — 540x1140 en densité 420 est mesuré
PIRE que le plein écran."""
_, calls = self._play(["1", "1", "n"])
cmd = self._started(calls)[0][-1]
for flag in (
"-skin 540x1140",
"qemu.sf.lcd_density=240",
"-no-snapshot-save",
"-gpu swangle",
):
self.assertIn(flag, cmd, flag)
def test_the_windowed_command_carries_them_too(self):
"""Qui ouvre la fenêtre depuis son poste doit avoir le même écran que
qui passe par scrcpy — sinon la comparaison ne veut rien dire."""
out, _ = self._play(["1", "2"])
self.assertIn("-skin 540x1140", out)
self.assertIn("qemu.sf.lcd_density=240", out)
def test_the_start_command_is_valid_shell(self):
"""Une apostrophe ou un guillemet de trop, et la VM répond par une
erreur de syntaxe — déjà rencontré dans ce même fichier."""
@ -272,7 +302,8 @@ class TestEmulatorMenu(_MenuCase):
def test_a_vm_without_the_sdk_is_diagnosed_before_anything_else(self):
"""Une VM déployée sans cocher l'outil est le cas NORMAL. Le menu le
dit avant même de demander la fenêtre — mesuré sur une VM de migration,
où le démarrage détaché rendait 0 et le journal disait « not found »."""
où le démarrage détaché rendait 0 et le journal disait « not found ».
"""
out, calls = self._play(["1"], probe="NO_SDK\n")
self.assertIn("SDK", out)
self.assertNotIn("[1]", out.split("VM locale")[-1])