Paillasse/portail/tests/Reels/decomposer-ouverture.py
Adam Belkacemi 0a5df89201 documente la capacité, la restauration et le redémarrage (étape 13)
Capacité mesurée sur 20 VM (docs/capacite.md), restauration complète du
portail réussie, retour automatique de tous les services après redémarrage.
Retire l'unité systemd orpheline de ProxySQL, rend fidèle au navigateur la
mesure du temps d'ouverture d'un lab (2,8 s), et regroupe la dette révélée.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:12:11 +02:00

60 lines
2.3 KiB
Python

#!/usr/bin/env python3
"""Décompose le temps d'ouverture d'un lab déjà admis (étape 13) : où passent
les secondes entre « la page voit l'état actif » et « l'invite login: » ?
Usage (sur le serveur, séance préparée par preparer-parcours.php et élève
déjà admis par parcours-lab.py) :
sudo -u www-data python3 tests/Reels/decomposer-ouverture.py
"""
import json
import os
import re
import time
from concurrent.futures import ThreadPoolExecutor
import requests
import websocket
BASE = os.environ.get("PARCOURS_BASE", "https://syllabuslab.fr")
BASE_WS = re.sub(r"^http", "ws", BASE)
ids = json.load(open("/tmp/parcours-identifiants.json"))
s = requests.Session()
tok = re.search(r'name="_token" value="([^"]+)"', s.get(BASE + "/connexion").text).group(1)
s.post(BASE + "/connexion/eleve", data={"_token": tok, "identifiant": ids["eleve_identifiant"], "code": ids["eleve_code"]})
def chrono(f):
t = time.time()
r = f()
return r, time.time() - t
def jeton(url, csrf):
return s.post(url, headers={"X-CSRF-TOKEN": csrf}).json()["jeton"]
def invite(jeton_console):
ws = websocket.create_connection(f"{BASE_WS}/console/{jeton_console}", timeout=10)
ws.send("\r")
sortie, fin = b"", time.time() + 10
# « login: » sur une VM neuve, « $ » si quelqu'un y est déjà connecté.
while not re.search(rb"login:|\$ ", sortie) and time.time() < fin:
m = ws.recv()
sortie += m if isinstance(m, bytes) else m.encode()
ws.close()
for essai in range(1, 4):
(page, t_page) = chrono(lambda: s.get(f"{BASE}/seances/{ids['seance_id']}/attente-eleve").text)
csrf = re.search(r"'X-CSRF-TOKEN': '([^']+)'", page).group(1)
urls = [re.sub(r"^https?://[^/]+", BASE, u) for u in re.findall(r"fetch\('(http[^']+/console)'", page)]
(jetons_seq, t_seq) = chrono(lambda: [jeton(u, csrf) for u in urls])
with ThreadPoolExecutor(len(urls)) as pool:
(jetons_par, t_par) = chrono(lambda: list(pool.map(lambda u: jeton(u, csrf), urls)))
(_, t_ws) = chrono(lambda: invite(jetons_par[0]))
navigateur = 1.0 + t_page + t_par + t_ws
print(f"essai {essai} : page {t_page:.2f} s | 3 jetons console : séquentiel {t_seq:.2f} s, parallèle {t_par:.2f} s "
f"| WebSocket → login: {t_ws:.2f} s | chemin navigateur (pire attente d'interrogation 1 s) ≈ {navigateur:.2f} s")