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>
This commit is contained in:
parent
42884597a9
commit
0a5df89201
8 changed files with 273 additions and 8 deletions
60
portail/tests/Reels/decomposer-ouverture.py
Normal file
60
portail/tests/Reels/decomposer-ouverture.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#!/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")
|
||||
|
|
@ -16,6 +16,7 @@ import os
|
|||
import re
|
||||
import threading
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
import requests
|
||||
import websocket
|
||||
|
|
@ -70,11 +71,14 @@ etat_change.wait(60)
|
|||
|
||||
page = eleve.get(f"{BASE}/seances/{seance}/attente-eleve").text
|
||||
csrf = re.search(r"'X-CSRF-TOKEN': '([^']+)'", page).group(1)
|
||||
consoles = {}
|
||||
for url, nom in zip(re.findall(r"fetch\('(http[^']+/console)'", page), re.findall(r'data-onglet="\d+"[^>]*>([^<]+)<', page)):
|
||||
url = re.sub(r"^https?://[^/]+", BASE, url)
|
||||
jeton = eleve.post(url, headers={"X-CSRF-TOKEN": csrf}).json()["jeton"]
|
||||
consoles[nom] = websocket.create_connection(f"{BASE_WS}/console/{jeton}", timeout=10)
|
||||
# Comme le navigateur : les jetons des consoles sont demandés en parallèle
|
||||
# (une série de fetch sans attente). Les demander l'un après l'autre
|
||||
# ajoutait ~2 s à la mesure (décomposition : decomposer-ouverture.py).
|
||||
noms = re.findall(r'data-onglet="\d+"[^>]*>([^<]+)<', page)
|
||||
urls = [re.sub(r"^https?://[^/]+", BASE, u) for u in re.findall(r"fetch\('(http[^']+/console)'", page)]
|
||||
with ThreadPoolExecutor(len(urls)) as pool:
|
||||
jetons = list(pool.map(lambda u: eleve.post(u, headers={"X-CSRF-TOKEN": csrf}).json()["jeton"], urls))
|
||||
consoles = {nom: websocket.create_connection(f"{BASE_WS}/console/{j}", timeout=10) for nom, j in zip(noms, jetons)}
|
||||
|
||||
|
||||
def lire_jusqua(ws, motif, delai=15):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue