diff --git a/script/proxmox/nesting.py b/script/proxmox/nesting.py index f73296c..3acd18c 100644 --- a/script/proxmox/nesting.py +++ b/script/proxmox/nesting.py @@ -101,14 +101,25 @@ DISQUE_MIN_GO = 15 # Le plus profond : deux, le seul chiffre dont on ait la preuve qu'il démarre # au quatrième étage. VCPU_IMBRIQUE = 2 -# Les étages imbriqués intermédiaires : un de plus, pour héberger leur enfant +# Les étages imbriqués PEU PROFONDS : un de plus, pour héberger leur enfant # sans être aussi étroits que lui. Deux hébergeant deux, c'est cent pour cent # de surengagement — et l'installation de l'étage 4 dépassait alors 2 h 50 # contre 793 s pour l'étage 3. # -# Trois est une HYPOTHÈSE : on a la preuve que deux démarre au quatrième étage -# et que huit gèle, rien entre les deux. C'est la descente qui tranchera. +# « Peu profonds », et c'est la mesure qui l'impose. Ce troisième vCPU ne coûte +# RIEN aux étages 2 et 3 — leur ssh répond en 37 s et 93 s, comme à deux vCPU — +# et il coûte tout au quatrième : 15 608 s, soit 4 h 20, contre 1 664 s à deux +# vCPU. Un seul vCPU de plus, l'amorçage multiplié par 9,4. VCPU_INTERMEDIAIRE = 3 +# Le premier étage qui doit rester au strict minimum. +# +# Amorçage du quatrième étage, mesuré : 1 664 s à 2 vCPU, 15 608 s à 3, jamais +# à 8 ni à 12 — même RIP à cinq minutes d'intervalle. Le « gel » observé à 8 et +# 12 n'est probablement pas autre chose que cette courbe poussée assez loin : +# 1 664 × 9,4 par vCPU supplémentaire dépasse vite toute patience. +# +# Aux étages 2 et 3, la même largeur ne coûte rien. Le seuil est donc là. +SEUIL_ETROIT = 4 # Le premier étage tourne sur le MÉTAL : aucun risque de gel, et son amorçage # est rapide — onze vCPU y ont démarré en 42 s. Il n'a pourtant qu'un enfant à # trois vCPU à servir ; quatre suffisent, et laissent la machine physique aux @@ -178,8 +189,9 @@ def nesting_plan( étages tienne : la cible du bas, plus un surcoût par étage au-dessus. Le processeur n'en fait PAS partie de la même façon : il ne croît - pas avec la profondeur, puisque tout étage imbriqué reste étroit. Le - premier étage demande VCPU_METAL, quelle que soit la profondeur. + pas avec la profondeur — il DÉCROÎT, et se stabilise à + VCPU_IMBRIQUE dès SEUIL_ETROIT. Le premier étage demande VCPU_METAL, + quelle que soit la profondeur. """ return ( PVE_RAM_CIBLE_MO + (d - 1) * PVE_RAM_MO, @@ -212,15 +224,21 @@ def nesting_plan( niveaux = [ { "niveau": niveau, - # Trois largeurs, et aucune ne dépend de la profondeur : le - # métal en premier, le fond au plus étroit, les intermédiaires - # juste assez larges pour héberger leur enfant. + # Le métal peut être large ; un étage imbriqué peu profond + # gagne son troisième vCPU pour héberger son enfant sans + # surengagement ; à partir de SEUIL_ETROIT, le strict minimum, + # parce que là ce troisième vCPU multiplie l'amorçage par 9,4. + # + # L'étage juste AU-DESSUS du seuil garde donc trois quand son + # enfant en a deux : c'est le seul endroit de la descente où le + # surengagement disparaît, et c'est celui qui compte, puisque + # l'étage 4 est le premier dont l'installation s'effondrait. "vcpu": ( VCPU_METAL if niveau == 1 else ( VCPU_IMBRIQUE - if niveau == atteignable + if niveau >= SEUIL_ETROIT else VCPU_INTERMEDIAIRE ) ), diff --git a/test/test_proxmox_nesting.py b/test/test_proxmox_nesting.py index 36db064..d03079d 100644 --- a/test/test_proxmox_nesting.py +++ b/test/test_proxmox_nesting.py @@ -83,14 +83,41 @@ class TestLePlanDesEtages(unittest.TestCase): ) def test_the_three_widths_are_where_they_belong(self): + """Le coût d'un vCPU dépend de la PROFONDEUR de l'étage, pas d'une + largeur absolue. + + Mesuré : le troisième vCPU ne coûte rien aux étages 2 et 3 — ssh en + 37 s et 93 s, comme à deux vCPU — et coûte 4 h 20 au quatrième, contre + 1 664 s à deux. Un seul vCPU de plus, l'amorçage ×9,4.""" niveaux = nesting.nesting_plan(6, **self.HOTE)["niveaux"] + largeurs = {n["niveau"]: n["vcpu"] for n in niveaux} # Le métal : aucun risque de gel, onze vCPU y ont démarré en 42 s. - self.assertEqual(niveaux[0]["vcpu"], nesting.VCPU_METAL) - # Le fond : le seul chiffre dont on ait la preuve qu'il démarre au - # quatrième étage. - self.assertEqual(niveaux[-1]["vcpu"], nesting.VCPU_IMBRIQUE) - for n in niveaux[1:-1]: - self.assertEqual(n["vcpu"], nesting.VCPU_INTERMEDIAIRE) + self.assertEqual(largeurs[1], nesting.VCPU_METAL) + # Peu profonds : le troisième vCPU est gratuit, et il enlève le + # surengagement là où l'installation s'effondrait. + for niveau in range(2, nesting.SEUIL_ETROIT): + self.assertEqual( + largeurs[niveau], nesting.VCPU_INTERMEDIAIRE, f"étage {niveau}" + ) + # À partir du seuil : le strict minimum, sans exception. + for niveau in range(nesting.SEUIL_ETROIT, 7): + self.assertEqual( + largeurs[niveau], nesting.VCPU_IMBRIQUE, f"étage {niveau}" + ) + + def test_the_level_above_the_threshold_keeps_its_headroom(self): + """C'est le seul endroit de la descente où le surengagement disparaît, + et c'est celui qui compte : l'étage 4 est le premier dont + l'installation s'effondrait, faute d'un parent plus large que lui. + + Les deux combinaisons mesurées étaient (parent 2, enfant 2) — démarre + en 1 664 s puis l'installation ne finit pas — et (parent 3, enfant 3) — + démarre en 15 608 s. Celle-ci est (parent 3, enfant 2).""" + niveaux = nesting.nesting_plan(6, **self.HOTE)["niveaux"] + largeurs = {n["niveau"]: n["vcpu"] for n in niveaux} + parent = largeurs[nesting.SEUIL_ETROIT - 1] + enfant = largeurs[nesting.SEUIL_ETROIT] + self.assertGreater(parent, enfant) def test_a_single_level_descent_runs_on_metal(self): niveaux = nesting.nesting_plan(1, **self.HOTE)["niveaux"]