feat(resource): implement and freeze Gate G

This commit is contained in:
fy59 2026-08-25 14:55:25 +02:00
parent 2e0104162c
commit 070280b9cc
34 changed files with 1072 additions and 116 deletions

View file

@ -172,8 +172,8 @@ fallback CPU. La feasibility Vulkan SIFT/RootSIFT a été rejetée ; ces deux ma
restent sur OpenCV L2. Track Model/Builder, les primitives géométriques Gate C, restent sur OpenCV L2. Track Model/Builder, les primitives géométriques Gate C,
le noyau Sparse SfM incrémental Gate D et le Bundle Adjustment final Gate E sont le noyau Sparse SfM incrémental Gate D et le Bundle Adjustment final Gate E sont
implémentés et validés. L'orchestration Sparse SfM Gate F est PASS / FROZEN ; implémentés et validés. L'orchestration Sparse SfM Gate F est PASS / FROZEN ;
l'intégration Governor Gate G, le DAG, le viewer et les étapes denses restent des tickets l'intégration Governor Gate G est **PASS / FROZEN** ; le DAG,
séparés planifiés. Le Resource Governor ne le viewer et les étapes denses restent des tickets séparés planifiés. Le Resource Governor ne
constitue pas un Resource System générique : voir la décision darchitecture. constitue pas un Resource System générique : voir la décision darchitecture.
## Licence ## Licence

View file

@ -122,7 +122,8 @@ les Feature Sets persistés.
PASS**, the Sparse SfM v16 persistence model is **FROZEN** after Gate B; PASS**, the Sparse SfM v16 persistence model is **FROZEN** after Gate B;
Gate C geometry and the synchronous in-memory Gate D incremental core are Gate C geometry and the synchronous in-memory Gate D incremental core are
**IMPLEMENTED / PASS**. Final per-component Gate E BA is **PASS / FROZEN**; **IMPLEMENTED / PASS**. Final per-component Gate E BA is **PASS / FROZEN**;
Gate F project/task orchestration is **PASS / FROZEN**. Gate G remains planned. Gate F project/task orchestration is **PASS / FROZEN**. Gate G is
**PASS / FROZEN**.
## Extension v2 multi-descriptor ## Extension v2 multi-descriptor

View file

@ -140,7 +140,7 @@ ni persistance, ni Task Runtime, ni Resource Governor.
Gate F relie ces noyaux au Task Runtime durable, à la Queue/Governor/Reservation Gate F relie ces noyaux au Task Runtime durable, à la Queue/Governor/Reservation
et à la publication Project DB v17 atomique et idempotente. et à la publication Project DB v17 atomique et idempotente.
**Statut :** GATE F — PASS / FROZEN. Gate G reste planifiée. **Statut :** GATE F — PASS / FROZEN. Gate G est **PASS / FROZEN**.
## Résultats et publication live ## Résultats et publication live

View file

@ -26,6 +26,22 @@ observations saines sont nécessaires à chaque palier `1 → 2 → 4 → 8`. Un
nouvelle pression réinitialise cette progression. Cette mémoire est nouvelle pression réinitialise cette progression. Cette mémoire est
process-local, bornée et protégée par le mutex du Governor. process-local, bornée et protégée par le mutex du Governor.
Gate G gèle le rafraîchissement initial : lorsqu'il existe du travail PENDING
en `WAIT` de ressources, le worker unique de la Task Queue dort au plus 500 ms
avant de rescanner la file et de recapturer les ressources. Un signal explicite
le réveille plus tôt. Cette cadence ne remplace pas les 50 ms existantes d'une
tâche déjà active qui attend sa réadmission à une frontière de séquence.
**Gate G — PASS / FROZEN.** Cette réévaluation bornée, la
fraîcheur des snapshots et l'identité GPU sélectionnée sont raccordées aux
chemins de production existants et leur validation finale est terminée.
Les snapshots emploient `CLOCK_MONOTONIC` et leur âge maximal est 1000 ms. Une
capture complète impossible est une erreur opérationnelle, tandis qu'une PSI
ou télémétrie swap optionnelle absente reste inconnue. Le modèle cible un hôte
Linux natif non contraint ; cgroups, limites systemd/RLIMIT, multi-GPU,
monitoring RSS, scratch et stockage externe restent différés.
## Feature Extraction ## Feature Extraction
ORB est déjà une tâche durable par image : source validée, extraction, ORB est déjà une tâche durable par image : source validée, extraction,
@ -106,6 +122,8 @@ une distribution de latence estimator-only.
Le profil maximal explicite et les pools multi-workers restent hors périmètre. Le profil maximal explicite et les pools multi-workers restent hors périmètre.
SIFT/RootSIFT et Feature Extraction Vulkan restent hors de ce contrat. SIFT/RootSIFT et Feature Extraction Vulkan restent hors de ce contrat.
Swap, zram et disque externe ne sont jamais ajoutés au budget RAM. Aucun chemin
scratch/spill ni aucune action modifiant l'hôte n'appartient à Gate G core.
La validation B3 du modèle Sparse SfM v16 a utilisé des processus frais, un La validation B3 du modèle Sparse SfM v16 a utilisé des processus frais, un
fixture synthétique de 100 000 landmarks et 500 000 observations, cinq passes fixture synthétique de 100 000 landmarks et 500 000 observations, cinq passes

View file

@ -4,8 +4,7 @@
**ACCEPTED** — architecture decision for the post-Gate C documentation freeze. **ACCEPTED** — architecture decision for the post-Gate C documentation freeze.
Current Sparse SfM gates A through F are **PASS / FROZEN**. Gate G remains Current Sparse SfM gates A through G are **PASS / FROZEN**.
open/planned.
Project Database: current schema **v17**; historical v16 remains frozen. Project Database: current schema **v17**; historical v16 remains frozen.
This record is normative for the current architecture. It does not introduce This record is normative for the current architecture. It does not introduce
@ -115,8 +114,9 @@ by `lardon3d_task_create_typed()` and submit the task through the normal queue.
It does not inspect resource snapshots, decide admission, create reservations, It does not inspect resource snapshots, decide admission, create reservations,
change Governor or queue policy, or bypass the reservation invariant. The change Governor or queue policy, or bypass the reservation invariant. The
estimate is operational metadata and is excluded from the Sparse SfM parameter estimate is operational metadata and is excluded from the Sparse SfM parameter
fingerprint, candidate identity and scientific determinism. Future Gate G owns fingerprint, candidate identity and scientific determinism. Gate G owns
resource-management policy and operational refinement of that estimate. resource-management and admission policy, but Gate G core does not modify the
fields of the frozen Sparse SfM Gate F v1 estimate.
Gate F v1 freezes its declarative Sparse SfM RAM request as the checked sum Gate F v1 freezes its declarative Sparse SfM RAM request as the checked sum
`128 MiB + I*64 KiB + T*2048 + O*512`, rounded upward to one MiB, where `I`, `128 MiB + I*64 KiB + T*2048 + O*512`, rounded upward to one MiB, where `I`,
@ -126,6 +126,146 @@ fields are zero, batch bounds are one, and the task requests one CPU thread and
one IO slot in the CPU class. These coefficients are conservative operational one IO slot in the CPU class. These coefficients are conservative operational
policy inputs, not measured dynamically or included in scientific identity. policy inputs, not measured dynamically or included in scientific identity.
### Gate G resource-policy implementation
**PASS / FROZEN.** The G0a contract is implemented and validated. All seven G0
human decisions remain resolved.
#### Pending admission and snapshot freshness
When pending work exists but every candidate receives `WAIT`, the existing Task
Queue worker performs a timed wait of at most 500 milliseconds. An earlier
enqueue, resume, resource-change, cancellation or shutdown signal wakes it
immediately. On timeout it repeats the normal stable queue scan and obtains new
snapshots through the existing admission path. No monitor thread, scheduler or
subsystem is added. The separate polling interval for a running sequential task
waiting at `lardon3d_task_sequence_break()` remains 50 milliseconds.
Production snapshots use `CLOCK_MONOTONIC`. A directly supplied snapshot is
fresh through an age of exactly 1000 milliseconds. A snapshot older than 1000
milliseconds or timestamped in the future must produce `WAIT`, must not produce
`START` or `REDUCE_BATCH`, creates no reservation and does not mutate Governor
policy state. Normal production admission captures synchronously through
`lardon3d_resource_governor_reserve_available()` immediately before evaluation.
There is no last-known-good cache, grace cache or telemetry-cache subsystem.
A mandatory whole-snapshot capture failure is an operational/internal resource
error: the queued task becomes `FAILED`, no callback starts and no reservation
leaks. Unavailable optional CPU, memory or IO PSI and vmstat/swap telemetry is
unknown and asserts no artificial pressure. Actual GPU demand with no selected
GPU is `REJECT`; temporarily unknown required live VRAM for a selected dedicated
GPU is `WAIT`. VRAM availability is never fabricated.
#### Conservative RAM accounting and platform
Gate G core preserves the conservative live admission model:
```text
available_ram = max(
0,
min(MemAvailable, physical_ram)
- host_ram_reserve
- active_charged_reservations
)
```
The implementation retains its existing checked, saturating subtraction order.
The host reserve remains one quarter of physical RAM and the emergency floor
remains one eighth of physical RAM.
Possible overlap between `MemAvailable` and already-materialized memory from an
active reservation is accepted. The deliberate bias is false `WAIT`, not
overcommit. Gate G adds no RSS tracking, materialization state, allocation
measurement, reservation resizing or live per-task monitoring. The current
single-worker topology remains unchanged.
Gate G core supports a native unconstrained Linux host process. Capacity uses
the existing `_SC_PHYS_PAGES`, `_SC_PAGESIZE` and `/proc/meminfo` mechanisms.
It is not cgroup v2, systemd `MemoryMax`, `RLIMIT_AS` or `RLIMIT_DATA` aware and
does not claim correct host-capacity admission inside a tighter constrained
container or service. Effective constrained-runtime accounting is deferred. No
cgroup write, systemd dependency or new dependency is authorized.
#### Sparse SfM estimate authority
Gate G consumes and restores the exact Sparse SfM Gate F estimate above and
does not alter any producer field. Batch adaptation is permitted only for task
contracts whose existing minimum/maximum range allows it; Sparse SfM remains
fixed at batch one. Restart retains the estimate persisted when the task was
created and evaluates it against newly captured machine telemetry.
Any future coefficient change requires a separate explicit review and an
operational formula/version contract applying only to newly created tasks. It
must not alter F0, candidate identity, Gate D/E parameters or existing persisted
tasks. Estimate-formula refinement is not part of Gate G core.
#### Selected GPU
Gate G core supports one selected GPU and no multi-GPU scheduling. Hardware
Profile deterministically selects the lowest numeric `/sys/class/drm/cardN`
accepted by its selection rules and retains that identity internally. Snapshot
capacity and live usage must refer to that same device; Resource Snapshot must
not perform an independent first-usable-GPU selection. Memory is never summed
across devices and reservations are not made per device.
Dedicated memory uses the selected device's capacity and usage. UMA/shared GPU
demand is charged exactly once against system RAM and never against a second
fictitious VRAM pool. Backend and fallback selection remain task-producer/task
contract responsibilities, not Governor policy. Multi-GPU and complex hybrid
topologies are deferred.
#### Scratch, persistence and science
Scratch and external-storage support is excluded from Gate G core. It adds no
scratch estimate fields, demand/availability API, manager, allocator, removable
media monitor, mount logic, capacity policy, spill contract, cleanup contract,
project scratch path or SSD detection. Sparse SfM has no scratch consumer,
spill algorithm or out-of-core path. Swap, zram and external SSD capacity never
enlarge scientific RAM admission.
No automatic `swapon`, `swapoff`, mount, unmount, formatting, partitioning or
destructive cleanup is authorized. A future feature requires a real task
consumer, an explicit task-specific contract, user opt-in where appropriate and
a separate architecture review. No dormant generic API is introduced.
Gate G core requires no Project DB v18, resource-history, reservation,
telemetry, policy or scratch table. Reservations and snapshots remain ephemeral;
the generic Task checkpoint already owns estimate durability. Resource policy,
machine data and hardware identity never enter F0, `sfm_version`, candidate
identity or a resource fingerprint. No resource-policy version is required for
Gate G core.
Gate G never changes scientific thresholds, seeds, iteration limits, image or
track selection, Bundle Adjustment parameters, Gate D, Gate E, F0, candidate
identity, Track Model, Track Builder scientific semantics, Feature Store,
calibration identity or Project DB reconstruction semantics.
#### Known derivable implementation defects
These implementation obligations required no further human policy decision and
are implemented:
- **G-D01:** `UINT64_MAX` is the final valid reservation ID; the following
reservation creation fails without an executable decision, reservation or
accounting charge.
- **G-D02:** pressure, recovery and slow-start streak counters must saturate at
the largest meaningful threshold and never wrap.
- **G-D03:** selected dedicated-GPU capacity and live usage must refer to the
same Hardware Profile-selected DRM device.
`TOTAL REMAINING GATE G HUMAN DECISIONS: 0`.
#### Closure validation
The complete normal suite passes 41/41 and the Gate G resource/task core passes
the targeted ASan/UBSan/LSan validation with leak detection enabled. The three
OpenCL-touching tests `candidate-pair-task`, `feature-task` and
`precision-consolidation` pass functionally and under ASan/UBSan with leak
detection disabled. Their LeakSanitizer-only termination has the identical
external `/opt/cuda/lib64/libOpenCL.so` signature of 3808 bytes in 68
allocations and is not classified as a Lardon3D Gate G leak. The real-machine
`orb-vulkan-backend` test, `git diff --check`, and the complete human diff review
pass. No Gate G human decision or implementation blocker remains.
## Project DB boundary ## Project DB boundary
The historical Project DB v16 migration and Sparse SfM reconstruction model The historical Project DB v16 migration and Sparse SfM reconstruction model

View file

@ -30,6 +30,32 @@ RED vers YELLOW, puis trois autres YELLOW vers GREEN. Le plafond reste 1 pendant
ces phases. Une fois GREEN, chaque groupe de trois observations saines double ces phases. Une fois GREEN, chaque groupe de trois observations saines double
le plafond : 1, 2, 4, 8, puis les paliers supérieurs utiles aux autres kinds. le plafond : 1, 2, 4, 8, puis les paliers supérieurs utiles aux autres kinds.
## Contrat Gate G gelé
**PASS / FROZEN.** Les constantes existantes ci-dessus
restent inchangées. La RAM disponible conserve le modèle conservateur
`min(MemAvailable, RAM physique) - réserve hôte - réservations actives`, borné à
zéro. Le double comptage conservateur possible d'une allocation déjà visible
dans `MemAvailable` est accepté : un faux `WAIT` est préféré à un overcommit.
Les snapshots de production emploient `CLOCK_MONOTONIC` et sont valides jusqu'à
un âge exact de 1000 ms inclus. Un snapshot plus ancien ou daté dans le futur
produit `WAIT`, sans réservation ni mutation de l'état de politique du
Governor. La capture synchrone complète impossible reste une erreur
opérationnelle qui fait échouer la tâche avant callback. Une télémétrie PSI ou
vmstat optionnelle absente reste inconnue et ne crée aucune pression fictive.
Gate G core cible un processus Linux natif non contraint et n'est pas cgroup,
systemd `MemoryMax` ou RLIMIT-aware. Il gouverne un seul GPU : le périphérique
DRM de plus petit numéro retenu par Hardware Profile. La capacité et l'usage
doivent provenir de ce même périphérique. La mémoire UMA est débitée exactement
une fois du budget RAM. Le multi-GPU est différé.
Le Governor ne garantit aucune allocation et ne transforme ni swap, ni zram,
ni stockage externe en RAM. Il ne modifie aucun paramètre scientifique. Aucun
scratch, cache de télémétrie, suivi RSS, redimensionnement de réservation ou
monitoring live n'appartient à Gate G core.
## API principale ## API principale
### Création et destruction ### Création et destruction
@ -136,4 +162,12 @@ le plafond : 1, 2, 4, 8, puis les paliers supérieurs utiles aux autres kinds.
- Pas de persistance des métriques - Pas de persistance des métriques
- Pas de communication avec d'autres gouverneurs - Pas de communication avec d'autres gouverneurs
## Statut : IMPLEMENTED Les corrections dérivables G-D01 (`UINT64_MAX` est le dernier ID valide et la
création suivante échoue sans réservation ni charge comptable),
G-D02 (saturation des compteurs de streak) et G-D03 (identité DRM identique
entre capacité et usage) sont implémentées. Les sept décisions G-B01 à G-B07
sont gelées ; il ne reste aucune décision humaine Gate G.
## Statut
**GATE G — PASS / FROZEN.**

View file

@ -41,6 +41,11 @@ Documenter l'intégration architecturale entre le scheduler de tâches et le Res
- Le scheduler saute la tâche en tête de file - Le scheduler saute la tâche en tête de file
- Il évalue la tâche suivante - Il évalue la tâche suivante
- Pas de blocage de la file - Pas de blocage de la file
- Si aucune tâche n'est admissible mais qu'un `WAIT` PENDING subsiste, le worker
effectue une attente temporisée d'au plus 500 ms, puis rescane normalement la
file avec de nouveaux snapshots. Tout signal explicite le réveille plus tôt.
- Cette attente appartient au worker existant : aucun thread de monitoring,
nouveau scheduler ou nouveau sous-système n'est créé.
## Gestion des pauses ## Gestion des pauses
@ -60,6 +65,9 @@ Documenter l'intégration architecturale entre le scheduler de tâches et le Res
- Capturer un nouvel instantané de ressources - Capturer un nouvel instantané de ressources
- Obtenir un contrat actualisé - Obtenir un contrat actualisé
- Reprendre le callback en conservant la progression - Reprendre le callback en conservant la progression
- L'attente d'admission à cette frontière conserve son polling existant de
50 ms ; elle est distincte des 500 ms de réévaluation d'une tâche initialement
PENDING.
### Avantages ### Avantages
- Adaptation dynamique des lots en cours d'exécution - Adaptation dynamique des lots en cours d'exécution
@ -80,4 +88,4 @@ Documenter l'intégration architecturale entre le scheduler de tâches et le Res
- Pas de priorités - Pas de priorités
- Pas de notification automatique de libération externe - Pas de notification automatique de libération externe
## Statut : DOCUMENTATION DE L'IMPLÉMENTATION ACTUELLE ## Statut : GATE G — PASS / FROZEN

View file

@ -198,7 +198,7 @@ as a whole, so Track identity and observation ownership remain simple.
Bundle Adjustment implementation, E01--E35 matrix, normal suite, targeted Bundle Adjustment implementation, E01--E35 matrix, normal suite, targeted
ASan/UBSan with LeakSanitizer, full sequential ASan/UBSan suite and at least ASan/UBSan with LeakSanitizer, full sequential ASan/UBSan suite and at least
20 fresh-process E27 comparisons are validated. Gate F project orchestration 20 fresh-process E27 comparisons are validated. Gate F project orchestration
is now **PASS / FROZEN**; Gate G resource integration remains a later gate. is now **PASS / FROZEN**; Gate G resource integration is **PASS / FROZEN**.
**DECISION: Gate E v1 is a synchronous, independent final per-component Bundle **DECISION: Gate E v1 is a synchronous, independent final per-component Bundle
Adjustment applied as post-processing to a copy of the immutable final Gate D Adjustment applied as post-processing to a copy of the immutable final Gate D
@ -823,13 +823,21 @@ it never depends on current RAM, swap, PSI, load, queue depth, task attempt or
Governor state. Gate F submits through the existing queue and does not perform Governor state. Gate F submits through the existing queue and does not perform
admission or reservation itself. admission or reservation itself.
The existing Governor owns admission and reservation policy. Future Gate G owns The existing Governor owns admission and reservation policy. Gate G owns
telemetry, adaptive tuning, pressure and swap/scratch policy, scheduling policy telemetry, pressure and scheduling policy, but Gate G core neither changes the
and estimate refinement. The estimate never enters the 372-byte parameter frozen Sparse SfM producer estimate nor adds scratch support. The estimate never
record, candidate identity, `sfm_version` or scientific decisions, and cannot enters the 372-byte parameter record, candidate identity, `sfm_version` or scientific decisions, and cannot
change Gate D or Gate E parameters. This clarification preserves change Gate D or Gate E parameters. This clarification preserves
`NO_NEW_SUBSYSTEM` and the mandatory reservation invariant. `NO_NEW_SUBSYSTEM` and the mandatory reservation invariant.
Gate G G0a freezes consumption of the exact Gate F v1 estimate. A restored task
keeps its persisted estimate and is evaluated with newly captured machine
telemetry; it is never recomputed with later coefficients. A future formula
change requires a separate operational formula/version review for newly created
tasks and cannot affect F0, candidate identity, Gate D/E parameters or existing
tasks. Sparse SfM remains batch one and has no scratch, spill or out-of-core
path. Swap, zram and external storage do not enlarge its RAM capacity.
### Gate F durable task payload ### Gate F durable task payload
**FROZEN.** Gate F advances the current Project Database schema head from v16 **FROZEN.** Gate F advances the current Project Database schema head from v16
@ -916,7 +924,7 @@ component keys nor changes candidate identity. No persistent diagnostic table,
sidecar, metadata blob or schema beyond v17 `sparse_sfm_tasks` is introduced. sidecar, metadata blob or schema beyond v17 `sparse_sfm_tasks` is introduced.
Gate F v1 is **PASS / FROZEN**. Gate D and Gate E remain **PASS / FROZEN**; Gate F v1 is **PASS / FROZEN**. Gate D and Gate E remain **PASS / FROZEN**;
Gate G remains open and planned. Gate G architecture decisions and implementation are **PASS / FROZEN**.
### Gate F validation closure ### Gate F validation closure
@ -933,7 +941,7 @@ Gate C — PASS / FROZEN
Gate D — PASS / FROZEN Gate D — PASS / FROZEN
Gate E — PASS / FROZEN Gate E — PASS / FROZEN
Gate F — PASS / FROZEN Gate F — PASS / FROZEN
Gate G — OPEN / PLANNED Gate G — PASS / FROZEN
``` ```
The five candidate-identity dimensions remain separate: The five candidate-identity dimensions remain separate:
@ -985,11 +993,11 @@ bounded storage.
Triangulation/registration are light CPU units and can be batched. Gate E v1 Triangulation/registration are light CPU units and can be batched. Gate E v1
uses local scientific limits for its final per-component BA and does not query uses local scientific limits for its final per-component BA and does not query
the Governor. Future Gate G admission may use `C`, `P`, `O`, solver mode and the Governor. Gate G consumes the frozen Gate F estimate derived from immutable
calibration-variable count without changing scientific results. The existing workload shape without changing scientific results. The existing Resource
Resource Governor owns RAM/PSI/swap policy; Sparse SfM adds no system-pressure Governor owns RAM/PSI/swap policy; Sparse SfM adds no system-pressure thresholds.
thresholds. Swap is never normal working memory, and UMA RAM must preserve Swap is never normal working memory, and UMA RAM must preserve several GiB of
several GiB of desktop/iGPU headroom. desktop/iGPU headroom.
## Hardware and probe study ## Hardware and probe study
@ -1024,8 +1032,9 @@ production Sparse SfM code is created by this gate.
contract frozen here; interleaved local BA is deferred. contract frozen here; interleaved local BA is deferred.
- **Gate F — PASS / FROZEN — Project orchestration:** explicit Track Set/calibration input, - **Gate F — PASS / FROZEN — Project orchestration:** explicit Track Set/calibration input,
atomic publication and durable runtime integration. atomic publication and durable runtime integration.
- **Gate G — OPEN / PLANNED — Resource/freeze:** Governor admission, sustained hardware safety, - **Gate G — PASS / FROZEN — Resource/freeze:** Governor
recovery, full validation and final freeze. admission, sustained hardware safety and recovery are implemented and fully
validated.
## Algorithm comparison and Gate A evidence ## Algorithm comparison and Gate A evidence

View file

@ -44,6 +44,13 @@ Le type public réel est l'opaque `Lardon3DTaskQueue`.
4. Si aucune tâche n'est admissible, le worker attend un changement. 4. Si aucune tâche n'est admissible, le worker attend un changement.
5. L'ordre de soumission est toujours respecté entre tâches de même priorité. 5. L'ordre de soumission est toujours respecté entre tâches de même priorité.
Lorsqu'au moins une tâche PENDING reste en `WAIT` de ressources, cette attente
est temporisée à 500 ms maximum. À l'expiration, le worker reprend le scan stable
depuis la tête et le chemin d'admission capture de nouveaux snapshots. Enqueue,
resume, `resources_changed`, annulation et arrêt continuent de réveiller
immédiatement le worker ; le timeout n'impose donc jamais 500 ms après un signal.
Cette règle utilise le worker unique existant et n'ajoute aucun thread.
## Sélection adaptative ## Sélection adaptative
Le sélecteur saute les tâches pour lesquelles le governor répond `WAIT` et Le sélecteur saute les tâches pour lesquelles le governor répond `WAIT` et
@ -77,8 +84,9 @@ le blocage par la tête de file lorsqu'une tâche ne peut pas démarrer.
## Statut ## Statut
**IMPLÉMENTÉ** — file FIFO avec worker unique, sélection adaptative, pause, **GATE G — PASS / FROZEN** — file FIFO avec worker unique,
annulation coopérative et accueil des tâches restaurées avec ID préassigné. sélection adaptative, réévaluation autonome des `WAIT`, pause, annulation
coopérative et accueil des tâches restaurées avec ID préassigné.
La reprise projet utilise `try_add_ex()` et ne bloque jamais `project_open()`. La reprise projet utilise `try_add_ex()` et ne bloque jamais `project_open()`.
À saturation, elle arrête sa fenêtre : les tâches non transférées restent À saturation, elle arrête sa fenêtre : les tâches non transférées restent
@ -94,3 +102,5 @@ pas un second scheduler.
une place se libère pendant la session courante. une place se libère pendant la session courante.
- Pas de pool de workers CPU/IO/GPU. - Pas de pool de workers CPU/IO/GPU.
- La backpressure borne les producteurs à la capacité configurée. - La backpressure borne les producteurs à la capacité configurée.
- Les 500 ms concernent uniquement l'admission initiale PENDING. Le polling
existant à une rupture de séquence reste 50 ms.

View file

@ -144,7 +144,7 @@ La qualité des triangles de triangulation影响 la précision de la reconstruct
| **Matching & Tracks** | Les contraintes filtrent les matches bruts pour ne garder que les géométries cohérentes. | | **Matching & Tracks** | Les contraintes filtrent les matches bruts pour ne garder que les géométries cohérentes. |
| **Visual Index** | Les candidats du Visual Index sont les entrées des contraintes géométriques. | | **Visual Index** | Les candidats du Visual Index sont les entrées des contraintes géométriques. |
| **Reconstruction Layers** | Chaque couche applique les contraintes appropriées pour valider ses résultats. | | **Reconstruction Layers** | Chaque couche applique les contraintes appropriées pour valider ses résultats. |
| **Resource Governor** | Le gouverneur peut ajuster les seuils de contrainte en fonction des ressources disponibles. | | **Resource Governor** | Admission et lots neutres uniquement ; aucun paramètre scientifique modifié. |
| **Hardware Profile** | Le calcul des matrices fondamentales/essentielles est CPU-bound. | | **Hardware Profile** | Le calcul des matrices fondamentales/essentielles est CPU-bound. |
## Contraintes de conception ## Contraintes de conception

View file

@ -54,6 +54,7 @@ Lardon3D suit une feuille de route ordonnée qui privilégie la stabilité et la
implémentés implémentés
- ✅ Sparse SfM Gate E : Bundle Adjustment final par composante PASS / FROZEN - ✅ Sparse SfM Gate E : Bundle Adjustment final par composante PASS / FROZEN
- ✅ Sparse SfM Gate F : orchestration durable et publication atomique PASS / FROZEN - ✅ Sparse SfM Gate F : orchestration durable et publication atomique PASS / FROZEN
- ✅ Sparse SfM Gate G : politique et cœur **PASS / FROZEN**
### Phase 5 : Reconstruction (PLANNED) ### Phase 5 : Reconstruction (PLANNED)
- 📋 Orchestration de reconstruction incrémentale - 📋 Orchestration de reconstruction incrémentale

View file

@ -14,6 +14,7 @@ typedef struct {
uint64_t page_size_bytes; uint64_t page_size_bytes;
uint64_t memory_total_bytes; uint64_t memory_total_bytes;
bool gpu_available; bool gpu_available;
unsigned int gpu_drm_card_index;
bool gpu_memory_known; bool gpu_memory_known;
bool gpu_uses_shared_memory; bool gpu_uses_shared_memory;
uint64_t gpu_memory_total_bytes; uint64_t gpu_memory_total_bytes;

View file

@ -8,6 +8,10 @@
#include <lardon3d/hardware_profile.h> #include <lardon3d/hardware_profile.h>
enum {
LARDON3D_RESOURCE_SNAPSHOT_MAX_AGE_MILLISECONDS = 1000,
};
typedef struct { typedef struct {
struct timespec captured_at; struct timespec captured_at;
uint64_t memory_available_bytes; uint64_t memory_available_bytes;

View file

@ -10,6 +10,8 @@
#include <lardon3d/hardware_profile.h> #include <lardon3d/hardware_profile.h>
#include "hardware_profile_internal.h"
static void static void
set_error(char *message, size_t size, const char *text) set_error(char *message, size_t size, const char *text)
{ {
@ -81,22 +83,30 @@ vendor_name(const char *vendor)
return vendor; return vendor;
} }
static void void
detect_gpu(Lardon3DHardwareProfile *profile) lardon3d_hardware_profile_detect_gpu_at_root(
Lardon3DHardwareProfile *profile,
const char *drm_root
)
{ {
if (!profile || !drm_root) {
return;
}
for (unsigned int index = 0; index < 64; ++index) { for (unsigned int index = 0; index < 64; ++index) {
char vendor_path[PATH_MAX]; char vendor_path[PATH_MAX];
char memory_path[PATH_MAX]; char memory_path[PATH_MAX];
int vendor_written = snprintf( int vendor_written = snprintf(
vendor_path, vendor_path,
sizeof(vendor_path), sizeof(vendor_path),
"/sys/class/drm/card%u/device/vendor", "%s/card%u/device/vendor",
drm_root,
index index
); );
int memory_written = snprintf( int memory_written = snprintf(
memory_path, memory_path,
sizeof(memory_path), sizeof(memory_path),
"/sys/class/drm/card%u/device/mem_info_vram_total", "%s/card%u/device/mem_info_vram_total",
drm_root,
index index
); );
if (vendor_written < 0 || (size_t)vendor_written >= sizeof(vendor_path) if (vendor_written < 0 || (size_t)vendor_written >= sizeof(vendor_path)
@ -109,6 +119,7 @@ detect_gpu(Lardon3DHardwareProfile *profile)
continue; continue;
} }
profile->gpu_available = true; profile->gpu_available = true;
profile->gpu_drm_card_index = index;
(void)snprintf( (void)snprintf(
profile->gpu_name, profile->gpu_name,
sizeof(profile->gpu_name), sizeof(profile->gpu_name),
@ -177,6 +188,6 @@ lardon3d_hardware_profile_detect(
"Linux" "Linux"
); );
} }
detect_gpu(profile); lardon3d_hardware_profile_detect_gpu_at_root(profile, "/sys/class/drm");
return true; return true;
} }

View file

@ -0,0 +1,11 @@
#ifndef LARDON3D_HARDWARE_PROFILE_INTERNAL_H
#define LARDON3D_HARDWARE_PROFILE_INTERNAL_H
#include <lardon3d/hardware_profile.h>
void lardon3d_hardware_profile_detect_gpu_at_root(
Lardon3DHardwareProfile *profile,
const char *drm_root
);
#endif

View file

@ -6,6 +6,8 @@
#include <lardon3d/resource_governor.h> #include <lardon3d/resource_governor.h>
#include "resource_governor_internal.h"
struct Lardon3DResourceReservation { struct Lardon3DResourceReservation {
Lardon3DResourceReservationInfo information; Lardon3DResourceReservationInfo information;
uint64_t charged_memory_bytes; uint64_t charged_memory_bytes;
@ -47,6 +49,9 @@ struct Lardon3DResourceGovernor {
size_t slow_start_limit; size_t slow_start_limit;
bool slow_start_active; bool slow_start_active;
Lardon3DResourcePressure pressure; Lardon3DResourcePressure pressure;
bool internal_now_known;
struct timespec internal_now;
bool internal_force_capture_failure;
Lardon3DResourceReservation *active; Lardon3DResourceReservation *active;
Lardon3DResourceReservation *released; Lardon3DResourceReservation *released;
}; };
@ -56,6 +61,7 @@ valid_profile(const Lardon3DHardwareProfile *profile)
{ {
return profile && profile->logical_cpu_count > 0 return profile && profile->logical_cpu_count > 0
&& profile->memory_total_bytes > 0 && profile->memory_total_bytes > 0
&& (!profile->gpu_available || profile->gpu_drm_card_index < 64)
&& (!profile->gpu_memory_known && (!profile->gpu_memory_known
|| (profile->gpu_available || (profile->gpu_available
&& profile->gpu_memory_total_bytes > 0)); && profile->gpu_memory_total_bytes > 0));
@ -93,7 +99,10 @@ valid_snapshot(
const Lardon3DResourceSnapshot *snapshot const Lardon3DResourceSnapshot *snapshot
) )
{ {
return snapshot && snapshot->cpu_load_1m >= 0.0 return snapshot && snapshot->captured_at.tv_sec >= 0
&& snapshot->captured_at.tv_nsec >= 0
&& snapshot->captured_at.tv_nsec < 1000000000L
&& snapshot->cpu_load_1m >= 0.0
&& (!snapshot->cpu_pressure_known && (!snapshot->cpu_pressure_known
|| (snapshot->cpu_pressure_avg10 >= 0.0 || (snapshot->cpu_pressure_avg10 >= 0.0
&& snapshot->cpu_pressure_avg10 <= 100.0)) && snapshot->cpu_pressure_avg10 <= 100.0))
@ -110,6 +119,53 @@ valid_snapshot(
<= profile->gpu_memory_total_bytes); <= profile->gpu_memory_total_bytes);
} }
static unsigned int
increment_to_limit(unsigned int value, unsigned int limit)
{
return value < limit ? value + 1 : limit;
}
static bool
snapshot_is_fresh_locked(
const Lardon3DResourceGovernor *governor,
const Lardon3DResourceSnapshot *snapshot,
bool *fresh
)
{
struct timespec now;
if (governor->internal_now_known) {
now = governor->internal_now;
} else if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
return false;
}
if (snapshot->captured_at.tv_sec > now.tv_sec
|| (snapshot->captured_at.tv_sec == now.tv_sec
&& snapshot->captured_at.tv_nsec > now.tv_nsec)) {
*fresh = false;
return true;
}
if (snapshot->captured_at.tv_sec == now.tv_sec
&& snapshot->captured_at.tv_nsec == now.tv_nsec) {
*fresh = true;
return true;
}
time_t seconds = now.tv_sec - snapshot->captured_at.tv_sec;
const uint64_t maximum_seconds =
LARDON3D_RESOURCE_SNAPSHOT_MAX_AGE_MILLISECONDS / 1000U;
const long maximum_nanoseconds = (long)(
LARDON3D_RESOURCE_SNAPSHOT_MAX_AGE_MILLISECONDS % 1000U
) * 1000000L;
if ((uint64_t)seconds < maximum_seconds) {
*fresh = true;
} else if ((uint64_t)seconds > maximum_seconds) {
*fresh = false;
} else {
long elapsed_nanoseconds = now.tv_nsec - snapshot->captured_at.tv_nsec;
*fresh = elapsed_nanoseconds <= maximum_nanoseconds;
}
return true;
}
static bool static bool
valid_estimate(const Lardon3DResourceEstimate *estimate) valid_estimate(const Lardon3DResourceEstimate *estimate)
{ {
@ -560,7 +616,7 @@ lardon3d_resource_governor_wait_for_change(
return changed; return changed;
} }
static void static bool
evaluate_locked( evaluate_locked(
Lardon3DResourceGovernor *governor, Lardon3DResourceGovernor *governor,
const Lardon3DResourceSnapshot *snapshot, const Lardon3DResourceSnapshot *snapshot,
@ -570,7 +626,16 @@ evaluate_locked(
{ {
if (!valid_estimate(estimate)) { if (!valid_estimate(estimate)) {
set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, 0, 0, 0, "Estimation de ressources invalide."); set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, 0, 0, 0, "Estimation de ressources invalide.");
return; return true;
}
bool fresh;
if (!snapshot_is_fresh_locked(governor, snapshot, &fresh)) {
return false;
}
if (!fresh) {
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0,
"Instantané de ressources périmé.");
return true;
} }
bool swap_changed = false; bool swap_changed = false;
if (snapshot->swap_activity_known) { if (snapshot->swap_activity_known) {
@ -612,7 +677,10 @@ evaluate_locked(
} else if (pressure_signal) { } else if (pressure_signal) {
governor->recovery_streak = 0; governor->recovery_streak = 0;
governor->slow_start_streak = 0; governor->slow_start_streak = 0;
++governor->pressure_streak; governor->pressure_streak = increment_to_limit(
governor->pressure_streak,
2
);
if (governor->pressure == LARDON3D_RESOURCE_PRESSURE_RED if (governor->pressure == LARDON3D_RESOURCE_PRESSURE_RED
|| governor->pressure_streak >= 2) { || governor->pressure_streak >= 2) {
governor->pressure = LARDON3D_RESOURCE_PRESSURE_RED; governor->pressure = LARDON3D_RESOURCE_PRESSURE_RED;
@ -624,7 +692,10 @@ evaluate_locked(
} else { } else {
governor->pressure_streak = 0; governor->pressure_streak = 0;
if (governor->pressure == LARDON3D_RESOURCE_PRESSURE_RED) { if (governor->pressure == LARDON3D_RESOURCE_PRESSURE_RED) {
++governor->recovery_streak; governor->recovery_streak = increment_to_limit(
governor->recovery_streak,
3
);
if (governor->recovery_streak >= 3) { if (governor->recovery_streak >= 3) {
governor->pressure = LARDON3D_RESOURCE_PRESSURE_YELLOW; governor->pressure = LARDON3D_RESOURCE_PRESSURE_YELLOW;
governor->recovery_streak = 0; governor->recovery_streak = 0;
@ -635,14 +706,20 @@ evaluate_locked(
governor->slow_start_limit = 1; governor->slow_start_limit = 1;
governor->slow_start_streak = 0; governor->slow_start_streak = 0;
} }
++governor->recovery_streak; governor->recovery_streak = increment_to_limit(
governor->recovery_streak,
3
);
if (governor->recovery_streak >= 3) { if (governor->recovery_streak >= 3) {
governor->pressure = LARDON3D_RESOURCE_PRESSURE_GREEN; governor->pressure = LARDON3D_RESOURCE_PRESSURE_GREEN;
governor->recovery_streak = 0; governor->recovery_streak = 0;
governor->slow_start_streak = 0; governor->slow_start_streak = 0;
} }
} else if (governor->slow_start_active) { } else if (governor->slow_start_active) {
++governor->slow_start_streak; governor->slow_start_streak = increment_to_limit(
governor->slow_start_streak,
3
);
if (governor->slow_start_streak >= 3) { if (governor->slow_start_streak >= 3) {
governor->slow_start_streak = 0; governor->slow_start_streak = 0;
if (governor->slow_start_limit > SIZE_MAX / 2) { if (governor->slow_start_limit > SIZE_MAX / 2) {
@ -657,14 +734,14 @@ evaluate_locked(
if (governor->pressure == LARDON3D_RESOURCE_PRESSURE_RED) { if (governor->pressure == LARDON3D_RESOURCE_PRESSURE_RED) {
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0,
"Pression mémoire ou swap persistante."); "Pression mémoire ou swap persistante.");
return; return true;
} }
if ((estimate->desired_gpu_slots > 0 if ((estimate->desired_gpu_slots > 0
|| estimate->gpu_memory_fixed_bytes > 0 || estimate->gpu_memory_fixed_bytes > 0
|| estimate->gpu_memory_bytes_per_item > 0) || estimate->gpu_memory_bytes_per_item > 0)
&& !governor->profile.gpu_available) { && !governor->profile.gpu_available) {
set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, 0, 0, 0, "Aucun GPU disponible."); set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, 0, 0, 0, "Aucun GPU disponible.");
return; return true;
} }
uint64_t memory_fixed = estimate->memory_fixed_bytes; uint64_t memory_fixed = estimate->memory_fixed_bytes;
@ -674,7 +751,7 @@ evaluate_locked(
|| memory_per_item || memory_per_item
> UINT64_MAX - estimate->gpu_memory_bytes_per_item) { > UINT64_MAX - estimate->gpu_memory_bytes_per_item) {
set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, 0, 0, 0, "Estimation mémoire trop grande."); set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, 0, 0, 0, "Estimation mémoire trop grande.");
return; return true;
} }
memory_fixed += estimate->gpu_memory_fixed_bytes; memory_fixed += estimate->gpu_memory_fixed_bytes;
memory_per_item += estimate->gpu_memory_bytes_per_item; memory_per_item += estimate->gpu_memory_bytes_per_item;
@ -702,14 +779,14 @@ evaluate_locked(
} }
if (theoretical_batch < estimate->minimum_batch_size) { if (theoretical_batch < estimate->minimum_batch_size) {
set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, 0, 0, 0, "Tâche impossible sur cette machine."); set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, 0, 0, 0, "Tâche impossible sur cette machine.");
return; return true;
} }
double load_limit = (double)governor->profile.logical_cpu_count double load_limit = (double)governor->profile.logical_cpu_count
* governor->policy.maximum_cpu_load_ratio; * governor->policy.maximum_cpu_load_ratio;
if (snapshot->cpu_load_1m >= load_limit) { if (snapshot->cpu_load_1m >= load_limit) {
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, "Charge CPU trop élevée."); set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, "Charge CPU trop élevée.");
return; return true;
} }
if (governor->policy.maximum_cpu_pressure_avg10 > 0.0 if (governor->policy.maximum_cpu_pressure_avg10 > 0.0
&& snapshot->cpu_pressure_known && snapshot->cpu_pressure_known
@ -717,7 +794,7 @@ evaluate_locked(
>= governor->policy.maximum_cpu_pressure_avg10) { >= governor->policy.maximum_cpu_pressure_avg10) {
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0,
"Pression CPU trop élevée."); "Pression CPU trop élevée.");
return; return true;
} }
if (governor->policy.maximum_memory_pressure_avg10 > 0.0 if (governor->policy.maximum_memory_pressure_avg10 > 0.0
&& snapshot->memory_pressure_known && snapshot->memory_pressure_known
@ -725,19 +802,19 @@ evaluate_locked(
>= governor->policy.maximum_memory_pressure_avg10) { >= governor->policy.maximum_memory_pressure_avg10) {
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0,
"Pression mémoire trop élevée."); "Pression mémoire trop élevée.");
return; return true;
} }
if (estimate->desired_io_slots > 0 && snapshot->io_pressure_known if (estimate->desired_io_slots > 0 && snapshot->io_pressure_known
&& snapshot->io_pressure_avg10 && snapshot->io_pressure_avg10
>= governor->policy.maximum_io_pressure_avg10) { >= governor->policy.maximum_io_pressure_avg10) {
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, "Pression d'entrée-sortie trop élevée."); set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, "Pression d'entrée-sortie trop élevée.");
return; return true;
} }
Lardon3DResourceAvailability available; Lardon3DResourceAvailability available;
if (!availability_locked(governor, snapshot, &available)) { if (!availability_locked(governor, snapshot, &available)) {
set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, 0, 0, 0, "Instantané de ressources invalide."); set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, 0, 0, 0, "Instantané de ressources invalide.");
return; return true;
} }
size_t batch = batch_capacity( size_t batch = batch_capacity(
available.memory_available_bytes, available.memory_available_bytes,
@ -749,7 +826,7 @@ evaluate_locked(
|| estimate->gpu_memory_bytes_per_item > 0)) { || estimate->gpu_memory_bytes_per_item > 0)) {
if (!available.gpu_memory_known) { if (!available.gpu_memory_known) {
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, "Mémoire GPU disponible inconnue."); set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, "Mémoire GPU disponible inconnue.");
return; return true;
} }
batch = minimum_size( batch = minimum_size(
batch, batch,
@ -794,7 +871,7 @@ evaluate_locked(
0, 0,
"Ressources déjà réservées ou temporairement insuffisantes." "Ressources déjà réservées ou temporairement insuffisantes."
); );
return; return true;
} }
if (available.cpu_available == 0 if (available.cpu_available == 0
|| (estimate->desired_gpu_slots > 0 || (estimate->desired_gpu_slots > 0
@ -802,7 +879,7 @@ evaluate_locked(
|| (estimate->desired_io_slots > 0 || (estimate->desired_io_slots > 0
&& available.io_slots_available == 0)) { && available.io_slots_available == 0)) {
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, "Slots de calcul déjà réservés."); set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, "Slots de calcul déjà réservés.");
return; return true;
} }
unsigned int cpu = estimate->desired_cpu_threads < available.cpu_available unsigned int cpu = estimate->desired_cpu_threads < available.cpu_available
? estimate->desired_cpu_threads ? estimate->desired_cpu_threads
@ -828,6 +905,7 @@ evaluate_locked(
reduced ? "Contrat réduit aux ressources disponibles." reduced ? "Contrat réduit aux ressources disponibles."
: "Ressources disponibles." : "Ressources disponibles."
); );
return true;
} }
bool bool
@ -853,14 +931,24 @@ lardon3d_resource_governor_reserve(
free(created); free(created);
return false; return false;
} }
evaluate_locked(governor, snapshot, estimate, decision); if (!evaluate_locked(governor, snapshot, estimate, decision)) {
if ((decision->kind != LARDON3D_RESOURCE_START (void)pthread_mutex_unlock(&governor->mutex);
&& decision->kind != LARDON3D_RESOURCE_REDUCE_BATCH) free(created);
|| governor->next_reservation_id == 0) { return false;
}
if (decision->kind != LARDON3D_RESOURCE_START
&& decision->kind != LARDON3D_RESOURCE_REDUCE_BATCH) {
(void)pthread_mutex_unlock(&governor->mutex); (void)pthread_mutex_unlock(&governor->mutex);
free(created); free(created);
return true; return true;
} }
if (governor->next_reservation_id == 0) {
set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, 0, 0, 0,
"Identités de réservation épuisées.");
(void)pthread_mutex_unlock(&governor->mutex);
free(created);
return false;
}
uint64_t memory_bytes; uint64_t memory_bytes;
uint64_t gpu_memory_bytes; uint64_t gpu_memory_bytes;
if (!resource_size( if (!resource_size(
@ -920,7 +1008,11 @@ lardon3d_resource_governor_reserve_available(
} }
(void)pthread_mutex_lock(&governor->mutex); (void)pthread_mutex_lock(&governor->mutex);
Lardon3DHardwareProfile profile = governor->profile; Lardon3DHardwareProfile profile = governor->profile;
bool force_capture_failure = governor->internal_force_capture_failure;
(void)pthread_mutex_unlock(&governor->mutex); (void)pthread_mutex_unlock(&governor->mutex);
if (force_capture_failure) {
return false;
}
Lardon3DResourceSnapshot snapshot; Lardon3DResourceSnapshot snapshot;
if (!lardon3d_resource_snapshot_capture(&profile, &snapshot, NULL, 0)) { if (!lardon3d_resource_snapshot_capture(&profile, &snapshot, NULL, 0)) {
return false; return false;
@ -1147,11 +1239,95 @@ lardon3d_resource_governor_decide(
(void)pthread_mutex_unlock(&governor->mutex); (void)pthread_mutex_unlock(&governor->mutex);
return false; return false;
} }
evaluate_locked(governor, snapshot, &estimate, decision); bool success = evaluate_locked(governor, snapshot, &estimate, decision);
(void)pthread_mutex_unlock(&governor->mutex);
return success;
}
bool
lardon3d_resource_governor_internal_set_next_reservation_id(
Lardon3DResourceGovernor *governor,
uint64_t next_reservation_id
)
{
if (!governor || next_reservation_id == 0) {
return false;
}
(void)pthread_mutex_lock(&governor->mutex);
governor->next_reservation_id = next_reservation_id;
(void)pthread_mutex_unlock(&governor->mutex); (void)pthread_mutex_unlock(&governor->mutex);
return true; return true;
} }
bool
lardon3d_resource_governor_internal_set_counters(
Lardon3DResourceGovernor *governor,
const Lardon3DResourceGovernorInternalCounters *counters
)
{
if (!governor || !counters) {
return false;
}
(void)pthread_mutex_lock(&governor->mutex);
governor->pressure_streak = counters->pressure_streak;
governor->recovery_streak = counters->recovery_streak;
governor->slow_start_streak = counters->slow_start_streak;
(void)pthread_mutex_unlock(&governor->mutex);
return true;
}
bool
lardon3d_resource_governor_internal_get_counters(
Lardon3DResourceGovernor *governor,
Lardon3DResourceGovernorInternalCounters *counters
)
{
if (!governor || !counters) {
return false;
}
(void)pthread_mutex_lock(&governor->mutex);
*counters = (Lardon3DResourceGovernorInternalCounters) {
.pressure_streak = governor->pressure_streak,
.recovery_streak = governor->recovery_streak,
.slow_start_streak = governor->slow_start_streak,
};
(void)pthread_mutex_unlock(&governor->mutex);
return true;
}
bool
lardon3d_resource_governor_internal_set_monotonic_now(
Lardon3DResourceGovernor *governor,
const struct timespec *now
)
{
if (!governor || (now && (now->tv_sec < 0 || now->tv_nsec < 0
|| now->tv_nsec >= 1000000000L))) {
return false;
}
(void)pthread_mutex_lock(&governor->mutex);
governor->internal_now_known = now != NULL;
if (now) {
governor->internal_now = *now;
}
(void)pthread_mutex_unlock(&governor->mutex);
return true;
}
void
lardon3d_resource_governor_internal_force_capture_failure(
Lardon3DResourceGovernor *governor,
bool force_failure
)
{
if (!governor) {
return;
}
(void)pthread_mutex_lock(&governor->mutex);
governor->internal_force_capture_failure = force_failure;
(void)pthread_mutex_unlock(&governor->mutex);
}
const char * const char *
lardon3d_resource_decision_name(Lardon3DResourceDecisionKind kind) lardon3d_resource_decision_name(Lardon3DResourceDecisionKind kind)
{ {

View file

@ -0,0 +1,37 @@
#ifndef LARDON3D_RESOURCE_GOVERNOR_INTERNAL_H
#define LARDON3D_RESOURCE_GOVERNOR_INTERNAL_H
#include <stdbool.h>
#include <stdint.h>
#include <time.h>
#include <lardon3d/resource_governor.h>
typedef struct {
unsigned int pressure_streak;
unsigned int recovery_streak;
unsigned int slow_start_streak;
} Lardon3DResourceGovernorInternalCounters;
bool lardon3d_resource_governor_internal_set_next_reservation_id(
Lardon3DResourceGovernor *governor,
uint64_t next_reservation_id
);
bool lardon3d_resource_governor_internal_set_counters(
Lardon3DResourceGovernor *governor,
const Lardon3DResourceGovernorInternalCounters *counters
);
bool lardon3d_resource_governor_internal_get_counters(
Lardon3DResourceGovernor *governor,
Lardon3DResourceGovernorInternalCounters *counters
);
bool lardon3d_resource_governor_internal_set_monotonic_now(
Lardon3DResourceGovernor *governor,
const struct timespec *now
);
void lardon3d_resource_governor_internal_force_capture_failure(
Lardon3DResourceGovernor *governor,
bool force_failure
);
#endif

View file

@ -8,6 +8,8 @@
#include <lardon3d/resource_snapshot.h> #include <lardon3d/resource_snapshot.h>
#include "resource_snapshot_internal.h"
enum { enum {
PROC_BUFFER_CAPACITY = 32768, PROC_BUFFER_CAPACITY = 32768,
}; };
@ -132,13 +134,14 @@ vmstat_counter(const char *buffer, const char *key, uint64_t *value)
return false; return false;
} }
static void void
capture_gpu_memory( lardon3d_resource_snapshot_capture_gpu_at_root(
const Lardon3DHardwareProfile *profile, const Lardon3DHardwareProfile *profile,
Lardon3DResourceSnapshot *snapshot Lardon3DResourceSnapshot *snapshot,
const char *drm_root
) )
{ {
if (!profile->gpu_available) { if (!profile || !snapshot || !drm_root || !profile->gpu_available) {
return; return;
} }
if (profile->gpu_uses_shared_memory && !profile->gpu_memory_known) { if (profile->gpu_uses_shared_memory && !profile->gpu_memory_known) {
@ -149,31 +152,29 @@ capture_gpu_memory(
if (!profile->gpu_memory_known) { if (!profile->gpu_memory_known) {
return; return;
} }
for (unsigned int index = 0; index < 64; ++index) { char path[256];
char path[256]; int written = snprintf(
int written = snprintf( path,
path, sizeof(path),
sizeof(path), "%s/card%u/device/mem_info_vram_used",
"/sys/class/drm/card%u/device/mem_info_vram_used", drm_root,
index profile->gpu_drm_card_index
); );
if (written < 0 || (size_t)written >= sizeof(path)) { if (written < 0 || (size_t)written >= sizeof(path)) {
continue; return;
} }
char buffer[64]; char buffer[64];
if (!read_file(path, buffer, sizeof(buffer))) { if (!read_file(path, buffer, sizeof(buffer))) {
continue; return;
} }
errno = 0; errno = 0;
char *end; char *end;
unsigned long long used = strtoull(buffer, &end, 10); unsigned long long used = strtoull(buffer, &end, 10);
if (errno == 0 && end != buffer if (errno == 0 && end != buffer
&& (uint64_t)used <= profile->gpu_memory_total_bytes) { && (uint64_t)used <= profile->gpu_memory_total_bytes) {
snapshot->gpu_memory_available_known = true; snapshot->gpu_memory_available_known = true;
snapshot->gpu_memory_available_bytes = snapshot->gpu_memory_available_bytes =
profile->gpu_memory_total_bytes - (uint64_t)used; profile->gpu_memory_total_bytes - (uint64_t)used;
return;
}
} }
} }
@ -202,7 +203,7 @@ lardon3d_resource_snapshot_capture(
|| !meminfo_bytes(buffer, "MemFree", &snapshot->memory_free_bytes) || !meminfo_bytes(buffer, "MemFree", &snapshot->memory_free_bytes)
|| !meminfo_bytes(buffer, "SwapFree", &snapshot->swap_available_bytes) || !meminfo_bytes(buffer, "SwapFree", &snapshot->swap_available_bytes)
|| !capture_load(snapshot) || !capture_load(snapshot)
|| clock_gettime(CLOCK_REALTIME, &snapshot->captured_at) != 0) { || clock_gettime(CLOCK_MONOTONIC, &snapshot->captured_at) != 0) {
set_error(error_message, error_message_size, "Instantané système impossible."); set_error(error_message, error_message_size, "Instantané système impossible.");
return false; return false;
} }
@ -217,6 +218,10 @@ lardon3d_resource_snapshot_capture(
buffer, "pswpin", &snapshot->swap_pages_in) buffer, "pswpin", &snapshot->swap_pages_in)
&& vmstat_counter(buffer, "pswpout", &snapshot->swap_pages_out); && vmstat_counter(buffer, "pswpout", &snapshot->swap_pages_out);
} }
capture_gpu_memory(profile, snapshot); lardon3d_resource_snapshot_capture_gpu_at_root(
profile,
snapshot,
"/sys/class/drm"
);
return true; return true;
} }

View file

@ -0,0 +1,12 @@
#ifndef LARDON3D_RESOURCE_SNAPSHOT_INTERNAL_H
#define LARDON3D_RESOURCE_SNAPSHOT_INTERNAL_H
#include <lardon3d/resource_snapshot.h>
void lardon3d_resource_snapshot_capture_gpu_at_root(
const Lardon3DHardwareProfile *profile,
Lardon3DResourceSnapshot *snapshot,
const char *drm_root
);
#endif

View file

@ -2,6 +2,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <time.h>
#include <lardon3d/task_queue.h> #include <lardon3d/task_queue.h>
@ -11,6 +12,10 @@ typedef struct TaskNode {
struct TaskNode *next_pending; struct TaskNode *next_pending;
} TaskNode; } TaskNode;
enum {
LARDON3D_PENDING_RESOURCE_WAIT_MILLISECONDS = 500,
};
struct Lardon3DTaskQueue { struct Lardon3DTaskQueue {
pthread_mutex_t mutex; pthread_mutex_t mutex;
pthread_cond_t not_empty; pthread_cond_t not_empty;
@ -63,9 +68,11 @@ unlink_pending(Lardon3DTaskQueue *queue, TaskNode *previous, TaskNode *node)
static Lardon3DTask * static Lardon3DTask *
select_admissible( select_admissible(
Lardon3DTaskQueue *queue, Lardon3DTaskQueue *queue,
Lardon3DResourceReservation **reservation Lardon3DResourceReservation **reservation,
bool *resource_wait_pending
) )
{ {
*resource_wait_pending = false;
TaskNode *previous = NULL; TaskNode *previous = NULL;
TaskNode *node = queue->pending_head; TaskNode *node = queue->pending_head;
while (node) { while (node) {
@ -101,6 +108,7 @@ select_admissible(
continue; continue;
} }
if (decision.kind == LARDON3D_RESOURCE_WAIT) { if (decision.kind == LARDON3D_RESOURCE_WAIT) {
*resource_wait_pending = true;
previous = node; previous = node;
node = next; node = next;
continue; continue;
@ -118,6 +126,30 @@ select_admissible(
return NULL; return NULL;
} }
static void
wait_for_pending_change(Lardon3DTaskQueue *queue, bool resource_wait_pending)
{
if (!resource_wait_pending) {
(void)pthread_cond_wait(&queue->not_empty, &queue->mutex);
return;
}
struct timespec deadline;
if (clock_gettime(CLOCK_MONOTONIC, &deadline) != 0) {
return;
}
deadline.tv_nsec +=
LARDON3D_PENDING_RESOURCE_WAIT_MILLISECONDS * 1000000L;
if (deadline.tv_nsec >= 1000000000L) {
++deadline.tv_sec;
deadline.tv_nsec -= 1000000000L;
}
(void)pthread_cond_timedwait(
&queue->not_empty,
&queue->mutex,
&deadline
);
}
static void * static void *
queue_worker(void *context) queue_worker(void *context)
{ {
@ -132,9 +164,14 @@ queue_worker(void *context)
return NULL; return NULL;
} }
Lardon3DResourceReservation *reservation = NULL; Lardon3DResourceReservation *reservation = NULL;
Lardon3DTask *selected = select_admissible(queue, &reservation); bool resource_wait_pending;
Lardon3DTask *selected = select_admissible(
queue,
&reservation,
&resource_wait_pending
);
if (!selected) { if (!selected) {
(void)pthread_cond_wait(&queue->not_empty, &queue->mutex); wait_for_pending_change(queue, resource_wait_pending);
(void)pthread_mutex_unlock(&queue->mutex); (void)pthread_mutex_unlock(&queue->mutex);
continue; continue;
} }
@ -177,11 +214,26 @@ lardon3d_task_queue_create(Lardon3DResourceGovernor *governor, size_t capacity)
free(queue); free(queue);
return NULL; return NULL;
} }
if (pthread_cond_init(&queue->not_empty, NULL) != 0) { pthread_condattr_t not_empty_attributes;
if (pthread_condattr_init(&not_empty_attributes) != 0) {
(void)pthread_mutex_destroy(&queue->mutex); (void)pthread_mutex_destroy(&queue->mutex);
free(queue); free(queue);
return NULL; return NULL;
} }
if (pthread_condattr_setclock(
&not_empty_attributes,
CLOCK_MONOTONIC
) != 0
|| pthread_cond_init(
&queue->not_empty,
&not_empty_attributes
) != 0) {
(void)pthread_condattr_destroy(&not_empty_attributes);
(void)pthread_mutex_destroy(&queue->mutex);
free(queue);
return NULL;
}
(void)pthread_condattr_destroy(&not_empty_attributes);
if (pthread_cond_init(&queue->not_full, NULL) != 0) { if (pthread_cond_init(&queue->not_full, NULL) != 0) {
(void)pthread_cond_destroy(&queue->not_empty); (void)pthread_cond_destroy(&queue->not_empty);
(void)pthread_mutex_destroy(&queue->mutex); (void)pthread_mutex_destroy(&queue->mutex);

View file

@ -0,0 +1,18 @@
#ifndef LARDON3D_RESOURCE_SNAPSHOT_TEST_UTILS_H
#define LARDON3D_RESOURCE_SNAPSHOT_TEST_UTILS_H
#include <stdbool.h>
#include <time.h>
#include <lardon3d/resource_snapshot.h>
static inline bool
lardon3d_test_resource_snapshot_make_fresh(
Lardon3DResourceSnapshot *snapshot
)
{
return snapshot
&& clock_gettime(CLOCK_MONOTONIC, &snapshot->captured_at) == 0;
}
#endif

View file

@ -9,6 +9,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <time.h>
#include <unistd.h> #include <unistd.h>
#include <lardon3d/candidate_pair_gen.h> #include <lardon3d/candidate_pair_gen.h>
@ -95,14 +96,23 @@ static bool runtime(Lardon3DAppState *s) {
static bool wait_state(Lardon3DTaskQueue *q, uint64_t id, static bool wait_state(Lardon3DTaskQueue *q, uint64_t id,
Lardon3DTaskState wanted, Lardon3DTaskSnapshot *out) { Lardon3DTaskState wanted, Lardon3DTaskSnapshot *out) {
for (size_t i = 0; i < 2000000; i++) { struct timespec deadline;
if (clock_gettime(CLOCK_MONOTONIC, &deadline) != 0) return false;
deadline.tv_sec += 5;
for (;;) {
if (lardon3d_task_queue_get(q, id, out) && if (lardon3d_task_queue_get(q, id, out) &&
out->state == wanted) { out->state == wanted) {
return true; return true;
} }
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0 ||
now.tv_sec > deadline.tv_sec ||
(now.tv_sec == deadline.tv_sec &&
now.tv_nsec >= deadline.tv_nsec)) {
return false;
}
sched_yield(); sched_yield();
} }
return false;
} }
static bool run_test(void) { static bool run_test(void) {

View file

@ -10,6 +10,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <time.h>
#include <unistd.h> #include <unistd.h>
#include <lardon3d/feature_store.h> #include <lardon3d/feature_store.h>
@ -106,13 +107,20 @@ static bool runtime(Lardon3DAppState *s) {
} }
static bool wait_state(Lardon3DTaskQueue *q, uint64_t id, Lardon3DTaskState wanted, static bool wait_state(Lardon3DTaskQueue *q, uint64_t id, Lardon3DTaskState wanted,
Lardon3DTaskSnapshot *out) { Lardon3DTaskSnapshot *out) {
for (size_t i = 0; i < 2000000; i++) { struct timespec deadline;
if (clock_gettime(CLOCK_MONOTONIC, &deadline) != 0) return false;
deadline.tv_sec += 5;
for (;;) {
if (lardon3d_task_queue_get(q, id, out) && out->state == wanted) { if (lardon3d_task_queue_get(q, id, out) && out->state == wanted) {
return true; return true;
} }
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0 || now.tv_sec > deadline.tv_sec ||
(now.tv_sec == deadline.tv_sec && now.tv_nsec >= deadline.tv_nsec)) {
return false;
}
sched_yield(); sched_yield();
} }
return false;
} }
typedef struct { typedef struct {

View file

@ -32,6 +32,7 @@ run_test(void)
} }
if (profile.gpu_available) { if (profile.gpu_available) {
CHECK(profile.gpu_name[0]); CHECK(profile.gpu_name[0]);
CHECK(profile.gpu_drm_card_index < 64);
} }
Lardon3DHardwareProfile second; Lardon3DHardwareProfile second;
CHECK(lardon3d_hardware_profile_detect(&second, NULL, 0)); CHECK(lardon3d_hardware_profile_detect(&second, NULL, 0));

View file

@ -15,6 +15,8 @@
#include <lardon3d/task_checkpoint.h> #include <lardon3d/task_checkpoint.h>
#include <lardon3d/task_queue.h> #include <lardon3d/task_queue.h>
#include "resource_snapshot_test_utils.h"
#define CHECK(condition) do { if (!(condition)) { \ #define CHECK(condition) do { if (!(condition)) { \
(void)fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); return false; \ (void)fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); return false; \
} } while (0) } } while (0)
@ -253,8 +255,10 @@ test_selective_capacity_one(void)
}; };
Lardon3DResourceDecision decision; Lardon3DResourceDecision decision;
Lardon3DResourceReservation *held = NULL; Lardon3DResourceReservation *held = NULL;
CHECK(lardon3d_test_resource_snapshot_make_fresh(&available));
CHECK(lardon3d_resource_governor_reserve(state.resource_governor, CHECK(lardon3d_resource_governor_reserve(state.resource_governor,
&available, &held_estimate, &decision, &held)); &available, &held_estimate, &decision, &held));
CHECK(held);
state.task_queue = lardon3d_task_queue_create(state.resource_governor, 1); state.task_queue = lardon3d_task_queue_create(state.resource_governor, 1);
CHECK(state.task_queue CHECK(state.task_queue
&& lardon3d_project_open(&state, "Selective Recovery")); && lardon3d_project_open(&state, "Selective Recovery"));

View file

@ -158,12 +158,18 @@ static bool runtime(Lardon3DAppState *s) {
static bool wait_state(Lardon3DTaskQueue *q, uint64_t id, static bool wait_state(Lardon3DTaskQueue *q, uint64_t id,
Lardon3DTaskState wanted, Lardon3DTaskState wanted,
Lardon3DTaskSnapshot *out) { Lardon3DTaskSnapshot *out) {
for (size_t i = 0; i < 2000000; i++) { struct timespec deadline;
if (clock_gettime(CLOCK_MONOTONIC, &deadline) != 0) return false;
deadline.tv_sec += 5;
for (;;) {
if (lardon3d_task_queue_get(q, id, out) && out->state == wanted) if (lardon3d_task_queue_get(q, id, out) && out->state == wanted)
return true; return true;
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0 || now.tv_sec > deadline.tv_sec ||
(now.tv_sec == deadline.tv_sec && now.tv_nsec >= deadline.tv_nsec))
return false;
sched_yield(); sched_yield();
} }
return false;
} }
/* -- Concurrency types -- */ /* -- Concurrency types -- */
@ -1616,4 +1622,4 @@ int main(void) {
"===\n", "===\n",
pass, fail, total); pass, fail, total);
return fail ? 1 : 0; return fail ? 1 : 0;
} }

View file

@ -1,4 +1,5 @@
#include <pthread.h> #include <pthread.h>
#include <limits.h>
#include <stdatomic.h> #include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
@ -7,6 +8,8 @@
#include <lardon3d/resource_governor.h> #include <lardon3d/resource_governor.h>
#include "../src/resource_governor_internal.h"
#define CHECK(condition) \ #define CHECK(condition) \
do { \ do { \
if (!(condition)) { \ if (!(condition)) { \
@ -18,6 +21,13 @@
#define GIBIBYTES(value) ((uint64_t)(value) * 1024 * 1024 * 1024) #define GIBIBYTES(value) ((uint64_t)(value) * 1024 * 1024 * 1024)
#define MEBIBYTES(value) ((uint64_t)(value) * 1024 * 1024) #define MEBIBYTES(value) ((uint64_t)(value) * 1024 * 1024)
static bool
use_fixed_test_clock(Lardon3DResourceGovernor *governor)
{
const struct timespec now = {0};
return lardon3d_resource_governor_internal_set_monotonic_now(governor, &now);
}
typedef struct { typedef struct {
Lardon3DResourceGovernor *governor; Lardon3DResourceGovernor *governor;
Lardon3DResourceSnapshot snapshot; Lardon3DResourceSnapshot snapshot;
@ -104,7 +114,7 @@ run_test(void)
&profile, &profile,
&policy &policy
); );
CHECK(governor); CHECK(governor && use_fixed_test_clock(governor));
Lardon3DResourceSnapshot snapshot = { Lardon3DResourceSnapshot snapshot = {
.memory_available_bytes = GIBIBYTES(10), .memory_available_bytes = GIBIBYTES(10),
.cpu_load_1m = 2.0, .cpu_load_1m = 2.0,
@ -214,7 +224,7 @@ run_test(void)
lardon3d_resource_governor_destroy(governor); lardon3d_resource_governor_destroy(governor);
governor = lardon3d_resource_governor_create(&profile, &policy); governor = lardon3d_resource_governor_create(&profile, &policy);
CHECK(governor); CHECK(governor && use_fixed_test_clock(governor));
snapshot.cpu_pressure_known = false; snapshot.cpu_pressure_known = false;
snapshot.memory_pressure_known = false; snapshot.memory_pressure_known = false;
snapshot.io_pressure_known = false; snapshot.io_pressure_known = false;
@ -230,7 +240,7 @@ run_test(void)
policy.emergency_memory_floor_bytes = GIBIBYTES(1); policy.emergency_memory_floor_bytes = GIBIBYTES(1);
governor = lardon3d_resource_governor_create(&profile, &policy); governor = lardon3d_resource_governor_create(&profile, &policy);
CHECK(governor); CHECK(governor && use_fixed_test_clock(governor));
request = (Lardon3DResourceRequest) { request = (Lardon3DResourceRequest) {
.minimum_batch_size = 1, .minimum_batch_size = 1,
.preferred_batch_size = 8, .preferred_batch_size = 8,
@ -255,7 +265,7 @@ run_test(void)
lardon3d_resource_governor_destroy(governor); lardon3d_resource_governor_destroy(governor);
governor = lardon3d_resource_governor_create(&profile, &policy); governor = lardon3d_resource_governor_create(&profile, &policy);
CHECK(governor); CHECK(governor && use_fixed_test_clock(governor));
snapshot.memory_available_bytes = GIBIBYTES(10); snapshot.memory_available_bytes = GIBIBYTES(10);
snapshot.swap_activity_known = true; snapshot.swap_activity_known = true;
snapshot.swap_pages_in = 50; snapshot.swap_pages_in = 50;
@ -281,7 +291,7 @@ run_test(void)
policy.gpu_memory_reserve_bytes = 0; policy.gpu_memory_reserve_bytes = 0;
policy.gpu_slot_capacity = 2; policy.gpu_slot_capacity = 2;
governor = lardon3d_resource_governor_create(&profile, &policy); governor = lardon3d_resource_governor_create(&profile, &policy);
CHECK(governor); CHECK(governor && use_fixed_test_clock(governor));
request = (Lardon3DResourceRequest) { request = (Lardon3DResourceRequest) {
.memory_bytes_per_item = GIBIBYTES(1), .memory_bytes_per_item = GIBIBYTES(1),
.gpu_memory_bytes_per_item = GIBIBYTES(1), .gpu_memory_bytes_per_item = GIBIBYTES(1),
@ -302,7 +312,7 @@ run_test(void)
profile.gpu_memory_total_bytes = GIBIBYTES(4); profile.gpu_memory_total_bytes = GIBIBYTES(4);
policy.gpu_memory_reserve_bytes = MEBIBYTES(512); policy.gpu_memory_reserve_bytes = MEBIBYTES(512);
governor = lardon3d_resource_governor_create(&profile, &policy); governor = lardon3d_resource_governor_create(&profile, &policy);
CHECK(governor); CHECK(governor && use_fixed_test_clock(governor));
request.minimum_batch_size = 1; request.minimum_batch_size = 1;
request.preferred_batch_size = 3; request.preferred_batch_size = 3;
request.gpu_memory_bytes_per_item = GIBIBYTES(1); request.gpu_memory_bytes_per_item = GIBIBYTES(1);
@ -365,7 +375,7 @@ run_generation_test(void)
&profile, &profile,
&policy &policy
); );
CHECK(governor); CHECK(governor && use_fixed_test_clock(governor));
/* Test 1 : génération initiale = 0. */ /* Test 1 : génération initiale = 0. */
CHECK(lardon3d_resource_governor_generation(governor) == 0); CHECK(lardon3d_resource_governor_generation(governor) == 0);
@ -529,7 +539,7 @@ run_adaptive_batch_test(void)
&profile, &profile,
&policy &policy
); );
CHECK(governor); CHECK(governor && use_fixed_test_clock(governor));
Lardon3DResourceSnapshot snapshot = { Lardon3DResourceSnapshot snapshot = {
.memory_available_bytes = GIBIBYTES(10), .memory_available_bytes = GIBIBYTES(10),
@ -734,10 +744,216 @@ run_adaptive_batch_test(void)
return true; return true;
} }
static bool
run_gate_g_boundary_test(void)
{
CHECK(LARDON3D_RESOURCE_SNAPSHOT_MAX_AGE_MILLISECONDS == 1000);
Lardon3DHardwareProfile profile = {
.logical_cpu_count = 8,
.page_size_bytes = 4096,
.memory_total_bytes = GIBIBYTES(8),
.cpu_architecture = "test",
};
Lardon3DResourcePolicy policy = {
.system_memory_reserve_bytes = GIBIBYTES(2),
.emergency_memory_floor_bytes = GIBIBYTES(1),
.system_cpu_reserve = 2,
.maximum_cpu_load_ratio = 1.0,
.maximum_cpu_pressure_avg10 = 20.0,
.maximum_memory_pressure_avg10 = 1.0,
.maximum_io_pressure_avg10 = 80.0,
.io_slot_capacity = 1,
};
Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create(
&profile,
&policy
);
CHECK(governor);
const struct timespec now = {.tv_sec = 10, .tv_nsec = 500};
CHECK(lardon3d_resource_governor_internal_set_monotonic_now(governor, &now));
Lardon3DResourceSnapshot snapshot = {
.captured_at = {.tv_sec = 9, .tv_nsec = 500},
.memory_available_bytes = GIBIBYTES(8),
.cpu_load_1m = 0.0,
};
Lardon3DResourceEstimate estimate = {
.memory_fixed_bytes = MEBIBYTES(1),
.minimum_batch_size = 1,
.maximum_batch_size = 1,
.desired_cpu_threads = 1,
.task_class = LARDON3D_RESOURCE_TASK_CPU,
};
Lardon3DResourceDecision decision;
Lardon3DResourceReservation *reservation = NULL;
CHECK(lardon3d_resource_governor_reserve(
governor, &snapshot, &estimate, &decision, &reservation));
CHECK(reservation);
CHECK(lardon3d_resource_governor_release(governor, reservation));
Lardon3DResourceGovernorInternalCounters counters = {
.pressure_streak = 1,
.recovery_streak = 2,
.slow_start_streak = 2,
};
CHECK(lardon3d_resource_governor_internal_set_counters(governor, &counters));
snapshot.captured_at = (struct timespec) {.tv_sec = 9, .tv_nsec = 499};
snapshot.memory_pressure_known = true;
snapshot.memory_pressure_avg10 = 100.0;
reservation = NULL;
CHECK(lardon3d_resource_governor_reserve(
governor, &snapshot, &estimate, &decision, &reservation));
CHECK(decision.kind == LARDON3D_RESOURCE_WAIT);
CHECK(!reservation);
Lardon3DResourceGovernorInternalCounters after;
CHECK(lardon3d_resource_governor_internal_get_counters(governor, &after));
CHECK(after.pressure_streak == 1);
CHECK(after.recovery_streak == 2);
CHECK(after.slow_start_streak == 2);
const struct timespec future_snapshots[] = {
{.tv_sec = 10, .tv_nsec = 501},
{.tv_sec = 11, .tv_nsec = 0},
};
for (size_t i = 0; i < sizeof(future_snapshots) / sizeof(future_snapshots[0]);
++i) {
snapshot.captured_at = future_snapshots[i];
reservation = NULL;
CHECK(lardon3d_resource_governor_reserve(
governor, &snapshot, &estimate, &decision, &reservation));
CHECK(decision.kind == LARDON3D_RESOURCE_WAIT);
CHECK(!reservation);
CHECK(lardon3d_resource_governor_internal_get_counters(governor, &after));
CHECK(after.pressure_streak == 1);
CHECK(after.recovery_streak == 2);
CHECK(after.slow_start_streak == 2);
}
snapshot.captured_at = now;
CHECK(lardon3d_resource_governor_internal_set_next_reservation_id(
governor,
UINT64_MAX
));
snapshot.memory_pressure_known = false;
CHECK(lardon3d_resource_governor_reserve(
governor, &snapshot, &estimate, &decision, &reservation));
CHECK(reservation);
Lardon3DResourceReservationInfo information;
CHECK(lardon3d_resource_reservation_get_active(
governor, reservation, &information));
CHECK(information.id == UINT64_MAX);
CHECK(lardon3d_resource_governor_release(governor, reservation));
Lardon3DResourceAvailability availability_before;
Lardon3DResourceAvailability availability_after;
CHECK(lardon3d_resource_governor_availability(
governor,
&snapshot,
&availability_before
));
reservation = NULL;
CHECK(!lardon3d_resource_governor_reserve(
governor, &snapshot, &estimate, &decision, &reservation));
CHECK(!reservation);
CHECK(decision.kind != LARDON3D_RESOURCE_START);
CHECK(decision.kind != LARDON3D_RESOURCE_REDUCE_BATCH);
CHECK(lardon3d_resource_governor_reservation_count(governor) == 0);
CHECK(lardon3d_resource_governor_availability(
governor,
&snapshot,
&availability_after
));
CHECK(availability_after.memory_reserved_bytes
== availability_before.memory_reserved_bytes);
CHECK(availability_after.gpu_memory_reserved_bytes
== availability_before.gpu_memory_reserved_bytes);
CHECK(availability_after.cpu_reserved == availability_before.cpu_reserved);
CHECK(availability_after.gpu_slots_reserved
== availability_before.gpu_slots_reserved);
CHECK(availability_after.io_slots_reserved
== availability_before.io_slots_reserved);
counters = (Lardon3DResourceGovernorInternalCounters) {
.pressure_streak = UINT_MAX,
};
CHECK(lardon3d_resource_governor_internal_set_counters(governor, &counters));
snapshot.memory_pressure_known = true;
snapshot.memory_pressure_avg10 = 100.0;
CHECK(lardon3d_resource_governor_decide(
governor,
&snapshot,
&(Lardon3DResourceRequest) {
.minimum_batch_size = 1,
.preferred_batch_size = 1,
.requested_cpu_threads = 1,
},
&decision
));
CHECK(lardon3d_resource_governor_internal_get_counters(governor, &after));
CHECK(after.pressure_streak == 2);
CHECK(lardon3d_resource_governor_pressure(governor)
== LARDON3D_RESOURCE_PRESSURE_RED);
counters = (Lardon3DResourceGovernorInternalCounters) {
.recovery_streak = UINT_MAX,
};
CHECK(lardon3d_resource_governor_internal_set_counters(governor, &counters));
snapshot.memory_pressure_known = false;
CHECK(lardon3d_resource_governor_decide(
governor,
&snapshot,
&(Lardon3DResourceRequest) {
.minimum_batch_size = 1,
.preferred_batch_size = 1,
.requested_cpu_threads = 1,
},
&decision
));
CHECK(lardon3d_resource_governor_internal_get_counters(governor, &after));
CHECK(after.recovery_streak == 0);
CHECK(lardon3d_resource_governor_pressure(governor)
== LARDON3D_RESOURCE_PRESSURE_YELLOW);
counters = (Lardon3DResourceGovernorInternalCounters) {
.recovery_streak = UINT_MAX,
};
CHECK(lardon3d_resource_governor_internal_set_counters(governor, &counters));
CHECK(lardon3d_resource_governor_decide(
governor,
&snapshot,
&(Lardon3DResourceRequest) {
.minimum_batch_size = 1,
.preferred_batch_size = 1,
.requested_cpu_threads = 1,
},
&decision
));
CHECK(lardon3d_resource_governor_pressure(governor)
== LARDON3D_RESOURCE_PRESSURE_GREEN);
counters = (Lardon3DResourceGovernorInternalCounters) {
.slow_start_streak = UINT_MAX,
};
CHECK(lardon3d_resource_governor_internal_set_counters(governor, &counters));
CHECK(lardon3d_resource_governor_decide(
governor,
&snapshot,
&(Lardon3DResourceRequest) {
.minimum_batch_size = 1,
.preferred_batch_size = 1,
.requested_cpu_threads = 1,
},
&decision
));
CHECK(lardon3d_resource_governor_internal_get_counters(governor, &after));
CHECK(after.slow_start_streak == 0);
lardon3d_resource_governor_destroy(governor);
return true;
}
int int
main(void) main(void)
{ {
return (run_test() && run_generation_test() && run_adaptive_batch_test()) return (run_test() && run_generation_test() && run_adaptive_batch_test()
&& run_gate_g_boundary_test())
? EXIT_SUCCESS ? EXIT_SUCCESS
: EXIT_FAILURE; : EXIT_FAILURE;
} }

View file

@ -6,6 +6,8 @@
#include <lardon3d/resource_governor.h> #include <lardon3d/resource_governor.h>
#include "../src/resource_governor_internal.h"
#define CHECK(condition) \ #define CHECK(condition) \
do { \ do { \
if (!(condition)) { \ if (!(condition)) { \
@ -17,6 +19,13 @@
#define GIBIBYTES(value) ((uint64_t)(value) * 1024 * 1024 * 1024) #define GIBIBYTES(value) ((uint64_t)(value) * 1024 * 1024 * 1024)
#define MEBIBYTES(value) ((uint64_t)(value) * 1024 * 1024) #define MEBIBYTES(value) ((uint64_t)(value) * 1024 * 1024)
static bool
use_fixed_test_clock(Lardon3DResourceGovernor *governor)
{
const struct timespec now = {0};
return lardon3d_resource_governor_internal_set_monotonic_now(governor, &now);
}
enum { enum {
THREAD_COUNT = 16, THREAD_COUNT = 16,
RESERVATIONS_PER_THREAD = 25, RESERVATIONS_PER_THREAD = 25,
@ -101,7 +110,7 @@ test_adaptive_batches(void)
&profile, &profile,
&policy &policy
); );
CHECK(governor); CHECK(governor && use_fixed_test_clock(governor));
Lardon3DResourceSnapshot snapshot = { Lardon3DResourceSnapshot snapshot = {
.memory_available_bytes = GIBIBYTES(37), .memory_available_bytes = GIBIBYTES(37),
.cpu_load_1m = 0.0, .cpu_load_1m = 0.0,
@ -231,7 +240,7 @@ test_multiple_reservations(void)
&profile, &profile,
&policy &policy
); );
CHECK(governor); CHECK(governor && use_fixed_test_clock(governor));
Lardon3DResourceSnapshot snapshot = { Lardon3DResourceSnapshot snapshot = {
.memory_available_bytes = GIBIBYTES(8), .memory_available_bytes = GIBIBYTES(8),
.cpu_load_1m = 0.0, .cpu_load_1m = 0.0,
@ -310,6 +319,8 @@ test_gpu_reservation(void)
&policy &policy
); );
CHECK(governor && other); CHECK(governor && other);
CHECK(use_fixed_test_clock(governor));
CHECK(use_fixed_test_clock(other));
Lardon3DResourceSnapshot snapshot = { Lardon3DResourceSnapshot snapshot = {
.memory_available_bytes = GIBIBYTES(16), .memory_available_bytes = GIBIBYTES(16),
.gpu_memory_available_known = true, .gpu_memory_available_known = true,
@ -378,7 +389,7 @@ test_concurrency(void)
&profile, &profile,
&policy &policy
); );
CHECK(governor); CHECK(governor && use_fixed_test_clock(governor));
Lardon3DResourceReservation *reservations[RESERVATION_COUNT] = {0}; Lardon3DResourceReservation *reservations[RESERVATION_COUNT] = {0};
pthread_barrier_t created; pthread_barrier_t created;
pthread_barrier_t release; pthread_barrier_t release;
@ -455,7 +466,7 @@ test_destroy_with_active_reservations(void)
&profile, &profile,
&policy &policy
); );
CHECK(governor); CHECK(governor && use_fixed_test_clock(governor));
Lardon3DResourceSnapshot snapshot = { Lardon3DResourceSnapshot snapshot = {
.memory_available_bytes = GIBIBYTES(4), .memory_available_bytes = GIBIBYTES(4),
.cpu_load_1m = 0.0, .cpu_load_1m = 0.0,

View file

@ -1,10 +1,17 @@
#include <stdbool.h> #include <stdbool.h>
#include <fcntl.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <lardon3d/hardware_profile.h> #include <lardon3d/hardware_profile.h>
#include <lardon3d/resource_snapshot.h> #include <lardon3d/resource_snapshot.h>
#include "../src/hardware_profile_internal.h"
#include "../src/resource_snapshot_internal.h"
#define CHECK(condition) \ #define CHECK(condition) \
do { \ do { \
if (!(condition)) { \ if (!(condition)) { \
@ -13,6 +20,94 @@
} \ } \
} while (0) } while (0)
static bool
write_text(const char *path, const char *text)
{
int descriptor = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600);
if (descriptor < 0) {
return false;
}
size_t length = strlen(text);
ssize_t written = write(descriptor, text, length);
bool success = written == (ssize_t)length;
if (close(descriptor) != 0) {
success = false;
}
return success;
}
static bool
create_card(const char *root, unsigned int index, const char *total, const char *used)
{
char card[256];
char device[256];
char path[256];
if (snprintf(card, sizeof(card), "%s/card%u", root, index) < 0
|| snprintf(device, sizeof(device), "%s/device", card) < 0
|| mkdir(card, 0700) != 0 || mkdir(device, 0700) != 0) {
return false;
}
(void)snprintf(path, sizeof(path), "%s/vendor", device);
if (!write_text(path, "0x1002\n")) {
return false;
}
(void)snprintf(path, sizeof(path), "%s/mem_info_vram_total", device);
if (!write_text(path, total)) {
return false;
}
(void)snprintf(path, sizeof(path), "%s/mem_info_vram_used", device);
return write_text(path, used);
}
static void
remove_card(const char *root, unsigned int index)
{
char path[256];
const char *files[] = {"vendor", "mem_info_vram_total", "mem_info_vram_used"};
for (size_t i = 0; i < sizeof(files) / sizeof(files[0]); ++i) {
(void)snprintf(path, sizeof(path), "%s/card%u/device/%s", root, index, files[i]);
(void)unlink(path);
}
(void)snprintf(path, sizeof(path), "%s/card%u/device", root, index);
(void)rmdir(path);
(void)snprintf(path, sizeof(path), "%s/card%u", root, index);
(void)rmdir(path);
}
static bool
test_selected_gpu_pairing(void)
{
char root[] = "/tmp/lardon3d-drm-XXXXXX";
CHECK(mkdtemp(root));
CHECK(create_card(root, 0, "1000\n", "100\n"));
CHECK(create_card(root, 1, "4000\n", "3000\n"));
Lardon3DHardwareProfile profile = {0};
lardon3d_hardware_profile_detect_gpu_at_root(&profile, root);
CHECK(profile.gpu_available);
CHECK(profile.gpu_drm_card_index == 0);
CHECK(profile.gpu_memory_total_bytes == 1000);
Lardon3DResourceSnapshot snapshot = {.memory_available_bytes = 800};
lardon3d_resource_snapshot_capture_gpu_at_root(&profile, &snapshot, root);
CHECK(snapshot.gpu_memory_available_known);
CHECK(snapshot.gpu_memory_available_bytes == 900);
char selected_usage[256];
(void)snprintf(
selected_usage,
sizeof(selected_usage),
"%s/card0/device/mem_info_vram_used",
root
);
CHECK(unlink(selected_usage) == 0);
snapshot = (Lardon3DResourceSnapshot) {0};
lardon3d_resource_snapshot_capture_gpu_at_root(&profile, &snapshot, root);
CHECK(!snapshot.gpu_memory_available_known);
remove_card(root, 0);
remove_card(root, 1);
CHECK(rmdir(root) == 0);
return true;
}
static bool static bool
run_test(void) run_test(void)
{ {
@ -30,14 +125,24 @@ run_test(void)
Lardon3DHardwareProfile profile; Lardon3DHardwareProfile profile;
CHECK(lardon3d_hardware_profile_detect(&profile, error, sizeof(error))); CHECK(lardon3d_hardware_profile_detect(&profile, error, sizeof(error)));
struct timespec before;
struct timespec after;
CHECK(clock_gettime(CLOCK_MONOTONIC, &before) == 0);
CHECK(lardon3d_resource_snapshot_capture( CHECK(lardon3d_resource_snapshot_capture(
&profile, &profile,
&snapshot, &snapshot,
error, error,
sizeof(error) sizeof(error)
)); ));
CHECK(clock_gettime(CLOCK_MONOTONIC, &after) == 0);
CHECK(error[0] == '\0'); CHECK(error[0] == '\0');
CHECK(snapshot.captured_at.tv_sec > 0); CHECK(snapshot.captured_at.tv_sec > 0);
CHECK(snapshot.captured_at.tv_sec > before.tv_sec
|| (snapshot.captured_at.tv_sec == before.tv_sec
&& snapshot.captured_at.tv_nsec >= before.tv_nsec));
CHECK(snapshot.captured_at.tv_sec < after.tv_sec
|| (snapshot.captured_at.tv_sec == after.tv_sec
&& snapshot.captured_at.tv_nsec <= after.tv_nsec));
CHECK(snapshot.memory_available_bytes > 0); CHECK(snapshot.memory_available_bytes > 0);
CHECK(snapshot.memory_free_bytes <= profile.memory_total_bytes); CHECK(snapshot.memory_free_bytes <= profile.memory_total_bytes);
CHECK(snapshot.cpu_load_1m >= 0.0); CHECK(snapshot.cpu_load_1m >= 0.0);
@ -58,6 +163,7 @@ run_test(void)
CHECK(snapshot.memory_pressure_avg10 >= 0.0); CHECK(snapshot.memory_pressure_avg10 >= 0.0);
CHECK(snapshot.memory_pressure_avg10 <= 100.0); CHECK(snapshot.memory_pressure_avg10 <= 100.0);
} }
CHECK(test_selected_gpu_pairing());
return true; return true;
} }

View file

@ -8,6 +8,8 @@
#include <lardon3d/task.h> #include <lardon3d/task.h>
#include "resource_snapshot_test_utils.h"
#define CHECK(condition) \ #define CHECK(condition) \
do { \ do { \
if (!(condition)) { \ if (!(condition)) { \
@ -222,6 +224,7 @@ run_sequential_test(void)
}; };
Lardon3DResourceDecision decision; Lardon3DResourceDecision decision;
Lardon3DResourceReservation *reservation; Lardon3DResourceReservation *reservation;
CHECK(lardon3d_test_resource_snapshot_make_fresh(&resource_snapshot));
CHECK(lardon3d_resource_governor_reserve( CHECK(lardon3d_resource_governor_reserve(
governor, governor,
&resource_snapshot, &resource_snapshot,
@ -302,6 +305,7 @@ run_wait_retry_test(void)
}; };
Lardon3DResourceDecision decision; Lardon3DResourceDecision decision;
Lardon3DResourceReservation *reservation; Lardon3DResourceReservation *reservation;
CHECK(lardon3d_test_resource_snapshot_make_fresh(&resource_snapshot));
CHECK(lardon3d_resource_governor_reserve( CHECK(lardon3d_resource_governor_reserve(
governor, governor,
&resource_snapshot, &resource_snapshot,
@ -387,6 +391,7 @@ run_cancel_during_wait_test(void)
}; };
Lardon3DResourceDecision decision; Lardon3DResourceDecision decision;
Lardon3DResourceReservation *reservation; Lardon3DResourceReservation *reservation;
CHECK(lardon3d_test_resource_snapshot_make_fresh(&resource_snapshot));
CHECK(lardon3d_resource_governor_reserve( CHECK(lardon3d_resource_governor_reserve(
governor, governor,
&resource_snapshot, &resource_snapshot,
@ -459,6 +464,7 @@ run_pause_during_wait_test(void)
}; };
Lardon3DResourceDecision decision; Lardon3DResourceDecision decision;
Lardon3DResourceReservation *reservation; Lardon3DResourceReservation *reservation;
CHECK(lardon3d_test_resource_snapshot_make_fresh(&resource_snapshot));
CHECK(lardon3d_resource_governor_reserve( CHECK(lardon3d_resource_governor_reserve(
governor, governor,
&resource_snapshot, &resource_snapshot,
@ -545,6 +551,7 @@ run_reject_test(void)
}; };
Lardon3DResourceDecision decision; Lardon3DResourceDecision decision;
Lardon3DResourceReservation *reservation; Lardon3DResourceReservation *reservation;
CHECK(lardon3d_test_resource_snapshot_make_fresh(&resource_snapshot));
CHECK(lardon3d_resource_governor_reserve( CHECK(lardon3d_resource_governor_reserve(
governor, governor,
&resource_snapshot, &resource_snapshot,
@ -647,6 +654,7 @@ run_cancel_test(void)
}; };
Lardon3DResourceDecision decision; Lardon3DResourceDecision decision;
Lardon3DResourceReservation *reservation; Lardon3DResourceReservation *reservation;
CHECK(lardon3d_test_resource_snapshot_make_fresh(&resource_snapshot));
CHECK(lardon3d_resource_governor_reserve( CHECK(lardon3d_resource_governor_reserve(
governor, governor,
&resource_snapshot, &resource_snapshot,
@ -715,4 +723,4 @@ main(void)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View file

@ -7,6 +7,8 @@
#include <lardon3d/task.h> #include <lardon3d/task.h>
#include "resource_snapshot_test_utils.h"
#define CHECK(condition) \ #define CHECK(condition) \
do { \ do { \
if (!(condition)) { \ if (!(condition)) { \
@ -161,6 +163,7 @@ run_test(void)
CHECK(strcmp(snapshot.name, "Tâche de test") == 0); CHECK(strcmp(snapshot.name, "Tâche de test") == 0);
pthread_t thread; pthread_t thread;
CHECK(lardon3d_test_resource_snapshot_make_fresh(&resource_snapshot));
CHECK(lardon3d_resource_governor_reserve( CHECK(lardon3d_resource_governor_reserve(
governor, &resource_snapshot, &estimate, &decision, &reservation governor, &resource_snapshot, &estimate, &decision, &reservation
)); ));
@ -199,6 +202,7 @@ run_test(void)
FinishProbe cancelled_probe = {.governor = governor}; FinishProbe cancelled_probe = {.governor = governor};
CHECK(lardon3d_task_set_finished_callback(task, finished_callback, CHECK(lardon3d_task_set_finished_callback(task, finished_callback,
&cancelled_probe)); &cancelled_probe));
CHECK(lardon3d_test_resource_snapshot_make_fresh(&resource_snapshot));
CHECK(lardon3d_resource_governor_reserve( CHECK(lardon3d_resource_governor_reserve(
governor, &resource_snapshot, &estimate, &decision, &reservation governor, &resource_snapshot, &estimate, &decision, &reservation
)); ));
@ -222,6 +226,7 @@ run_test(void)
FinishProbe failed_probe = {.governor = governor}; FinishProbe failed_probe = {.governor = governor};
CHECK(lardon3d_task_set_finished_callback(task, finished_callback, CHECK(lardon3d_task_set_finished_callback(task, finished_callback,
&failed_probe)); &failed_probe));
CHECK(lardon3d_test_resource_snapshot_make_fresh(&resource_snapshot));
CHECK(lardon3d_resource_governor_reserve( CHECK(lardon3d_resource_governor_reserve(
governor, &resource_snapshot, &estimate, &decision, &reservation governor, &resource_snapshot, &estimate, &decision, &reservation
)); ));

View file

@ -10,6 +10,8 @@
#include <lardon3d/task_checkpoint.h> #include <lardon3d/task_checkpoint.h>
#include "resource_snapshot_test_utils.h"
#define CHECK(condition) \ #define CHECK(condition) \
do { \ do { \
if (!(condition)) { \ if (!(condition)) { \
@ -355,6 +357,7 @@ run_test(void)
&running &running
); );
CHECK(governor && running_task && lardon3d_task_assign_id(running_task, 51)); CHECK(governor && running_task && lardon3d_task_assign_id(running_task, 51));
CHECK(lardon3d_test_resource_snapshot_make_fresh(&resources));
CHECK(lardon3d_resource_governor_reserve( CHECK(lardon3d_resource_governor_reserve(
governor, &resources, &estimate, &decision, &reservation governor, &resources, &estimate, &decision, &reservation
)); ));
@ -374,6 +377,7 @@ run_test(void)
); );
CHECK(restarted_task); CHECK(restarted_task);
reservation = NULL; reservation = NULL;
CHECK(lardon3d_test_resource_snapshot_make_fresh(&resources));
CHECK(lardon3d_resource_governor_reserve( CHECK(lardon3d_resource_governor_reserve(
governor, &resources, &estimate, &decision, &reservation governor, &resources, &estimate, &decision, &reservation
)); ));
@ -393,6 +397,7 @@ run_test(void)
); );
CHECK(sequence_task && lardon3d_task_assign_id(sequence_task, 52)); CHECK(sequence_task && lardon3d_task_assign_id(sequence_task, 52));
reservation = NULL; reservation = NULL;
CHECK(lardon3d_test_resource_snapshot_make_fresh(&resources));
CHECK(lardon3d_resource_governor_reserve( CHECK(lardon3d_resource_governor_reserve(
governor, &resources, &estimate, &decision, &reservation governor, &resources, &estimate, &decision, &reservation
)); ));

View file

@ -6,6 +6,8 @@
#include <lardon3d/task_kind_registry.h> #include <lardon3d/task_kind_registry.h>
#include "resource_snapshot_test_utils.h"
#define CHECK(condition) do { if (!(condition)) { \ #define CHECK(condition) do { if (!(condition)) { \
(void)fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); return false; \ (void)fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); return false; \
} } while (0) } } while (0)
@ -162,6 +164,7 @@ run_test(void)
}; };
Lardon3DResourceDecision decision; Lardon3DResourceDecision decision;
Lardon3DResourceReservation *reservation = NULL; Lardon3DResourceReservation *reservation = NULL;
CHECK(lardon3d_test_resource_snapshot_make_fresh(&resources));
CHECK(governor && lardon3d_resource_governor_reserve( CHECK(governor && lardon3d_resource_governor_reserve(
governor, &resources, &durable.estimate, &decision, &reservation)); governor, &resources, &durable.estimate, &decision, &reservation));
CHECK(lardon3d_task_start(task, governor, reservation)); CHECK(lardon3d_task_start(task, governor, reservation));

View file

@ -6,6 +6,9 @@
#include <lardon3d/task_queue.h> #include <lardon3d/task_queue.h>
#include "../src/resource_governor_internal.h"
#include "resource_snapshot_test_utils.h"
#define CHECK(condition) \ #define CHECK(condition) \
do { \ do { \
if (!(condition)) { \ if (!(condition)) { \
@ -122,7 +125,7 @@ hold_resources(
Lardon3DResourceReservation **reservation Lardon3DResourceReservation **reservation
) )
{ {
const Lardon3DResourceSnapshot blocking_snapshot = { Lardon3DResourceSnapshot blocking_snapshot = {
.memory_available_bytes = UINT64_MAX, .memory_available_bytes = UINT64_MAX,
.cpu_load_1m = 0.0, .cpu_load_1m = 0.0,
}; };
@ -132,7 +135,8 @@ hold_resources(
.desired_cpu_threads = 1024, .desired_cpu_threads = 1024,
}; };
Lardon3DResourceDecision decision; Lardon3DResourceDecision decision;
return lardon3d_resource_governor_reserve( return lardon3d_test_resource_snapshot_make_fresh(&blocking_snapshot)
&& lardon3d_resource_governor_reserve(
governor, governor,
&blocking_snapshot, &blocking_snapshot,
&blocking_estimate, &blocking_estimate,
@ -322,6 +326,7 @@ run_test(void)
}; };
Lardon3DResourceDecision decision; Lardon3DResourceDecision decision;
Lardon3DResourceReservation *blocking_reservation; Lardon3DResourceReservation *blocking_reservation;
CHECK(lardon3d_test_resource_snapshot_make_fresh(&blocking_snapshot));
CHECK(lardon3d_resource_governor_reserve( CHECK(lardon3d_resource_governor_reserve(
governor, governor,
&blocking_snapshot, &blocking_snapshot,
@ -365,11 +370,31 @@ run_test(void)
short_pause(); short_pause();
CHECK(!awakened.contract_seen); CHECK(!awakened.contract_seen);
CHECK(lardon3d_resource_governor_release(governor, blocking_reservation)); CHECK(lardon3d_resource_governor_release(governor, blocking_reservation));
lardon3d_task_queue_resources_changed(queue);
CHECK(wait_terminal(queue, awakened_id, &snapshot)); CHECK(wait_terminal(queue, awakened_id, &snapshot));
CHECK(snapshot.state == TASK_COMPLETED); CHECK(snapshot.state == TASK_COMPLETED);
CHECK(awakened.contract_seen); CHECK(awakened.contract_seen);
CHECK(lardon3d_resource_governor_reservation_count(governor) == 0); CHECK(lardon3d_resource_governor_reservation_count(governor) == 0);
QueueWork capture_failed = {.log = &wait_log, .value = 3, .steps = 1};
Lardon3DTask *capture_failed_task = lardon3d_task_create(
"Échec capture",
&estimate,
queue_callback,
&capture_failed
);
uint64_t capture_failed_id;
CHECK(capture_failed_task);
lardon3d_resource_governor_internal_force_capture_failure(governor, true);
CHECK(lardon3d_task_queue_add(
queue,
capture_failed_task,
&capture_failed_id
));
CHECK(wait_terminal(queue, capture_failed_id, &snapshot));
CHECK(snapshot.state == TASK_FAILED);
CHECK(!capture_failed.contract_seen);
CHECK(lardon3d_resource_governor_reservation_count(governor) == 0);
lardon3d_resource_governor_internal_force_capture_failure(governor, false);
lardon3d_task_queue_destroy(queue); lardon3d_task_queue_destroy(queue);
CHECK(pthread_mutex_destroy(&wait_log.mutex) == 0); CHECK(pthread_mutex_destroy(&wait_log.mutex) == 0);
@ -387,6 +412,7 @@ run_test(void)
}; };
Lardon3DResourceDecision io_decision; Lardon3DResourceDecision io_decision;
Lardon3DResourceReservation *io_blocking_reservation; Lardon3DResourceReservation *io_blocking_reservation;
CHECK(lardon3d_test_resource_snapshot_make_fresh(&io_blocking_snapshot));
CHECK(lardon3d_resource_governor_reserve( CHECK(lardon3d_resource_governor_reserve(
governor, governor,
&io_blocking_snapshot, &io_blocking_snapshot,
@ -432,7 +458,6 @@ run_test(void)
CHECK(snapshot.state == TASK_PENDING); CHECK(snapshot.state == TASK_PENDING);
CHECK(!blocked.contract_seen); CHECK(!blocked.contract_seen);
CHECK(lardon3d_resource_governor_release(governor, io_blocking_reservation)); CHECK(lardon3d_resource_governor_release(governor, io_blocking_reservation));
lardon3d_task_queue_resources_changed(queue);
CHECK(wait_terminal(queue, blocked_id, &snapshot)); CHECK(wait_terminal(queue, blocked_id, &snapshot));
CHECK(snapshot.state == TASK_COMPLETED); CHECK(snapshot.state == TASK_COMPLETED);
CHECK(blocked.contract_seen); CHECK(blocked.contract_seen);