1019 lines
32 KiB
C
1019 lines
32 KiB
C
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <lardon3d/resource_governor.h>
|
|
|
|
struct Lardon3DResourceReservation {
|
|
Lardon3DResourceReservationInfo information;
|
|
uint64_t charged_memory_bytes;
|
|
struct Lardon3DResourceReservation *next;
|
|
};
|
|
|
|
enum {
|
|
LARDON3D_BATCH_METRICS_CAPACITY = 8,
|
|
};
|
|
|
|
typedef struct {
|
|
size_t batch_size;
|
|
uint64_t duration_ns;
|
|
size_t peak_memory_bytes;
|
|
} Lardon3DBatchMetrics;
|
|
|
|
struct Lardon3DResourceGovernor {
|
|
pthread_mutex_t mutex;
|
|
pthread_cond_t cond;
|
|
uint64_t generation;
|
|
Lardon3DHardwareProfile profile;
|
|
Lardon3DResourcePolicy policy;
|
|
uint64_t next_reservation_id;
|
|
uint64_t memory_reserved_bytes;
|
|
uint64_t gpu_memory_reserved_bytes;
|
|
unsigned int cpu_reserved;
|
|
unsigned int gpu_slots_reserved;
|
|
unsigned int io_slots_reserved;
|
|
Lardon3DBatchMetrics batch_metrics[LARDON3D_RESOURCE_TASK_MIXED + 1][LARDON3D_BATCH_METRICS_CAPACITY];
|
|
size_t batch_metrics_count[LARDON3D_RESOURCE_TASK_MIXED + 1];
|
|
size_t batch_metrics_head[LARDON3D_RESOURCE_TASK_MIXED + 1];
|
|
size_t active_count;
|
|
Lardon3DResourceReservation *active;
|
|
Lardon3DResourceReservation *released;
|
|
};
|
|
|
|
static bool
|
|
valid_profile(const Lardon3DHardwareProfile *profile)
|
|
{
|
|
return profile && profile->logical_cpu_count > 0
|
|
&& profile->memory_total_bytes > 0
|
|
&& (!profile->gpu_memory_known
|
|
|| (profile->gpu_available
|
|
&& profile->gpu_memory_total_bytes > 0));
|
|
}
|
|
|
|
static bool
|
|
valid_policy(
|
|
const Lardon3DHardwareProfile *profile,
|
|
const Lardon3DResourcePolicy *policy
|
|
)
|
|
{
|
|
return valid_profile(profile) && policy
|
|
&& policy->system_memory_reserve_bytes < profile->memory_total_bytes
|
|
&& policy->system_cpu_reserve < profile->logical_cpu_count
|
|
&& policy->maximum_cpu_load_ratio > 0.0
|
|
&& policy->maximum_cpu_load_ratio <= 1.0
|
|
&& policy->maximum_io_pressure_avg10 >= 0.0
|
|
&& policy->maximum_io_pressure_avg10 <= 100.0
|
|
&& policy->io_slot_capacity > 0
|
|
&& (!profile->gpu_available || policy->gpu_slot_capacity > 0)
|
|
&& (!profile->gpu_memory_known
|
|
|| policy->gpu_memory_reserve_bytes
|
|
< profile->gpu_memory_total_bytes);
|
|
}
|
|
|
|
static bool
|
|
valid_snapshot(
|
|
const Lardon3DHardwareProfile *profile,
|
|
const Lardon3DResourceSnapshot *snapshot
|
|
)
|
|
{
|
|
return snapshot && snapshot->cpu_load_1m >= 0.0
|
|
&& (!snapshot->io_pressure_known
|
|
|| (snapshot->io_pressure_avg10 >= 0.0
|
|
&& snapshot->io_pressure_avg10 <= 100.0))
|
|
&& (!snapshot->gpu_memory_available_known
|
|
|| !profile->gpu_memory_known
|
|
|| profile->gpu_uses_shared_memory
|
|
|| snapshot->gpu_memory_available_bytes
|
|
<= profile->gpu_memory_total_bytes);
|
|
}
|
|
|
|
static bool
|
|
valid_estimate(const Lardon3DResourceEstimate *estimate)
|
|
{
|
|
return estimate && estimate->minimum_batch_size > 0
|
|
&& estimate->maximum_batch_size >= estimate->minimum_batch_size
|
|
&& estimate->desired_cpu_threads > 0
|
|
&& estimate->task_class >= LARDON3D_RESOURCE_TASK_GENERAL
|
|
&& estimate->task_class <= LARDON3D_RESOURCE_TASK_MIXED
|
|
&& ((estimate->gpu_memory_fixed_bytes == 0
|
|
&& estimate->gpu_memory_bytes_per_item == 0)
|
|
|| estimate->desired_gpu_slots > 0);
|
|
}
|
|
|
|
static uint64_t
|
|
subtract_floor(uint64_t value, uint64_t amount)
|
|
{
|
|
return value > amount ? value - amount : 0;
|
|
}
|
|
|
|
static unsigned int
|
|
subtract_unsigned(unsigned int value, unsigned int amount)
|
|
{
|
|
return value > amount ? value - amount : 0;
|
|
}
|
|
|
|
static uint64_t
|
|
minimum_uint64(uint64_t left, uint64_t right)
|
|
{
|
|
return left < right ? left : right;
|
|
}
|
|
|
|
static size_t
|
|
minimum_size(size_t left, size_t right)
|
|
{
|
|
return left < right ? left : right;
|
|
}
|
|
|
|
static bool
|
|
resource_size(
|
|
uint64_t fixed,
|
|
uint64_t per_item,
|
|
size_t batch,
|
|
uint64_t *total
|
|
)
|
|
{
|
|
if (per_item > 0 && batch > UINT64_MAX / per_item) {
|
|
return false;
|
|
}
|
|
uint64_t variable = per_item * (uint64_t)batch;
|
|
if (fixed > UINT64_MAX - variable) {
|
|
return false;
|
|
}
|
|
*total = fixed + variable;
|
|
return true;
|
|
}
|
|
|
|
static size_t
|
|
batch_capacity(uint64_t available, uint64_t fixed, uint64_t per_item)
|
|
{
|
|
if (available < fixed) {
|
|
return 0;
|
|
}
|
|
if (per_item == 0) {
|
|
return SIZE_MAX;
|
|
}
|
|
uint64_t capacity = (available - fixed) / per_item;
|
|
return capacity > SIZE_MAX ? SIZE_MAX : (size_t)capacity;
|
|
}
|
|
|
|
static bool
|
|
record_batch_locked(
|
|
Lardon3DResourceGovernor *governor,
|
|
Lardon3DResourceTaskClass task_class,
|
|
size_t batch_size,
|
|
uint64_t duration_ns,
|
|
size_t peak_memory_bytes
|
|
)
|
|
{
|
|
if (batch_size == 0) {
|
|
return false;
|
|
}
|
|
size_t class_index = (size_t)task_class;
|
|
if (class_index > (size_t)LARDON3D_RESOURCE_TASK_MIXED) {
|
|
return false;
|
|
}
|
|
Lardon3DBatchMetrics *metrics = governor->batch_metrics[class_index];
|
|
size_t count = governor->batch_metrics_count[class_index];
|
|
size_t head = governor->batch_metrics_head[class_index];
|
|
metrics[head] = (Lardon3DBatchMetrics) {
|
|
.batch_size = batch_size,
|
|
.duration_ns = duration_ns,
|
|
/* Zéro est le marqueur persistant « mesure inconnue ». La boucle
|
|
* d'adaptation ignore explicitement ces échantillons. */
|
|
.peak_memory_bytes = peak_memory_bytes,
|
|
};
|
|
head = (head + 1) % LARDON3D_BATCH_METRICS_CAPACITY;
|
|
governor->batch_metrics_head[class_index] = head;
|
|
if (count < LARDON3D_BATCH_METRICS_CAPACITY) {
|
|
governor->batch_metrics_count[class_index] = count + 1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static size_t
|
|
adaptive_batch_limit(
|
|
const Lardon3DResourceGovernor *governor,
|
|
Lardon3DResourceTaskClass task_class,
|
|
size_t static_batch,
|
|
uint64_t memory_bytes_per_item
|
|
)
|
|
{
|
|
if (memory_bytes_per_item == 0 || static_batch == 0) {
|
|
return static_batch;
|
|
}
|
|
size_t class_index = (size_t)task_class;
|
|
if (class_index > (size_t)LARDON3D_RESOURCE_TASK_MIXED) {
|
|
return static_batch;
|
|
}
|
|
size_t count = governor->batch_metrics_count[class_index];
|
|
if (count == 0) {
|
|
return static_batch;
|
|
}
|
|
size_t head = governor->batch_metrics_head[class_index];
|
|
uint64_t measured_per_item = 0;
|
|
size_t samples = 0;
|
|
for (size_t i = 0; i < count; ++i) {
|
|
size_t idx = (head + LARDON3D_BATCH_METRICS_CAPACITY - count + i)
|
|
% LARDON3D_BATCH_METRICS_CAPACITY;
|
|
const Lardon3DBatchMetrics *m = &governor->batch_metrics[class_index][idx];
|
|
if (m->batch_size > 0 && m->peak_memory_bytes > 0) {
|
|
/* Coût par élément le plus défavorable observé : une moyenne
|
|
* sous-estimerait le pic et laisserait un lot dépasser son
|
|
* budget. La stabilité de l'hôte prime sur le débit. */
|
|
uint64_t per_item = m->peak_memory_bytes / m->batch_size
|
|
+ (m->peak_memory_bytes % m->batch_size != 0);
|
|
if (per_item > measured_per_item) {
|
|
measured_per_item = per_item;
|
|
}
|
|
++samples;
|
|
}
|
|
}
|
|
if (samples == 0) {
|
|
return static_batch;
|
|
}
|
|
if (measured_per_item <= memory_bytes_per_item) {
|
|
return static_batch;
|
|
}
|
|
/* Éviter l'overflow de la multiplication : si static_batch est trop
|
|
* grand pour être multiplié sans débordement, on retourne 1 (le lot le
|
|
* plus conservateur possible) plutôt que de saturer à SIZE_MAX. */
|
|
if (static_batch > UINT64_MAX / memory_bytes_per_item) {
|
|
return 1;
|
|
}
|
|
uint64_t corrected = (uint64_t)static_batch * memory_bytes_per_item
|
|
/ measured_per_item;
|
|
size_t result = corrected > SIZE_MAX ? SIZE_MAX : (size_t)corrected;
|
|
return result > 0 ? result : 1;
|
|
}
|
|
|
|
static void
|
|
set_decision(
|
|
Lardon3DResourceDecision *decision,
|
|
Lardon3DResourceDecisionKind kind,
|
|
size_t batch_size,
|
|
unsigned int cpu_threads,
|
|
unsigned int gpu_slots,
|
|
unsigned int io_slots,
|
|
const char *reason
|
|
)
|
|
{
|
|
*decision = (Lardon3DResourceDecision) {
|
|
.kind = kind,
|
|
.batch_size = batch_size,
|
|
.cpu_threads = cpu_threads,
|
|
.gpu_slots = gpu_slots,
|
|
.io_slots = io_slots,
|
|
};
|
|
(void)snprintf(decision->reason, sizeof(decision->reason), "%s", reason);
|
|
}
|
|
|
|
bool
|
|
lardon3d_resource_policy_default(
|
|
const Lardon3DHardwareProfile *profile,
|
|
Lardon3DResourcePolicy *policy
|
|
)
|
|
{
|
|
if (!valid_profile(profile) || !policy) {
|
|
return false;
|
|
}
|
|
*policy = (Lardon3DResourcePolicy) {
|
|
.system_memory_reserve_bytes = profile->memory_total_bytes / 8,
|
|
.gpu_memory_reserve_bytes = profile->gpu_memory_known
|
|
? profile->gpu_memory_total_bytes / 8
|
|
: 0,
|
|
.system_cpu_reserve = profile->logical_cpu_count > 2 ? 1 : 0,
|
|
.maximum_cpu_load_ratio = 0.90,
|
|
.maximum_io_pressure_avg10 = 80.0,
|
|
.gpu_slot_capacity = profile->gpu_available ? 1 : 0,
|
|
.io_slot_capacity = 1,
|
|
};
|
|
return valid_policy(profile, policy);
|
|
}
|
|
|
|
Lardon3DResourceGovernor *
|
|
lardon3d_resource_governor_create(
|
|
const Lardon3DHardwareProfile *profile,
|
|
const Lardon3DResourcePolicy *policy
|
|
)
|
|
{
|
|
if (!valid_policy(profile, policy)) {
|
|
return NULL;
|
|
}
|
|
Lardon3DResourceGovernor *governor = calloc(1, sizeof(*governor));
|
|
if (!governor) {
|
|
return NULL;
|
|
}
|
|
if (pthread_mutex_init(&governor->mutex, NULL) != 0) {
|
|
free(governor);
|
|
return NULL;
|
|
}
|
|
pthread_condattr_t attr;
|
|
if (pthread_condattr_init(&attr) != 0) {
|
|
(void)pthread_mutex_destroy(&governor->mutex);
|
|
free(governor);
|
|
return NULL;
|
|
}
|
|
if (pthread_condattr_setclock(&attr, CLOCK_MONOTONIC) != 0) {
|
|
(void)pthread_condattr_destroy(&attr);
|
|
(void)pthread_mutex_destroy(&governor->mutex);
|
|
free(governor);
|
|
return NULL;
|
|
}
|
|
if (pthread_cond_init(&governor->cond, &attr) != 0) {
|
|
(void)pthread_condattr_destroy(&attr);
|
|
(void)pthread_mutex_destroy(&governor->mutex);
|
|
free(governor);
|
|
return NULL;
|
|
}
|
|
(void)pthread_condattr_destroy(&attr);
|
|
governor->generation = 0;
|
|
governor->profile = *profile;
|
|
governor->policy = *policy;
|
|
governor->next_reservation_id = 1;
|
|
return governor;
|
|
}
|
|
|
|
void
|
|
lardon3d_resource_governor_destroy(Lardon3DResourceGovernor *governor)
|
|
{
|
|
if (!governor) {
|
|
return;
|
|
}
|
|
Lardon3DResourceReservation *reservation = governor->active;
|
|
while (reservation) {
|
|
Lardon3DResourceReservation *next = reservation->next;
|
|
free(reservation);
|
|
reservation = next;
|
|
}
|
|
reservation = governor->released;
|
|
while (reservation) {
|
|
Lardon3DResourceReservation *next = reservation->next;
|
|
free(reservation);
|
|
reservation = next;
|
|
}
|
|
(void)pthread_cond_destroy(&governor->cond);
|
|
(void)pthread_mutex_destroy(&governor->mutex);
|
|
free(governor);
|
|
}
|
|
|
|
bool
|
|
lardon3d_resource_governor_set_policy(
|
|
Lardon3DResourceGovernor *governor,
|
|
const Lardon3DResourcePolicy *policy
|
|
)
|
|
{
|
|
if (!governor || !policy) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&governor->mutex);
|
|
bool policy_valid = valid_policy(&governor->profile, policy);
|
|
uint64_t memory_budget = policy_valid
|
|
? governor->profile.memory_total_bytes
|
|
- policy->system_memory_reserve_bytes
|
|
: 0;
|
|
uint64_t gpu_budget = policy_valid && governor->profile.gpu_memory_known
|
|
&& !governor->profile.gpu_uses_shared_memory
|
|
? governor->profile.gpu_memory_total_bytes
|
|
- policy->gpu_memory_reserve_bytes
|
|
: UINT64_MAX;
|
|
bool accepted = policy_valid
|
|
&& memory_budget >= governor->memory_reserved_bytes
|
|
&& gpu_budget >= governor->gpu_memory_reserved_bytes
|
|
&& governor->profile.logical_cpu_count - policy->system_cpu_reserve
|
|
>= governor->cpu_reserved
|
|
&& policy->gpu_slot_capacity >= governor->gpu_slots_reserved
|
|
&& policy->io_slot_capacity >= governor->io_slots_reserved;
|
|
if (accepted) {
|
|
governor->policy = *policy;
|
|
}
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
return accepted;
|
|
}
|
|
|
|
static bool
|
|
availability_locked(
|
|
const Lardon3DResourceGovernor *governor,
|
|
const Lardon3DResourceSnapshot *snapshot,
|
|
Lardon3DResourceAvailability *availability
|
|
)
|
|
{
|
|
if (!valid_snapshot(&governor->profile, snapshot)) {
|
|
return false;
|
|
}
|
|
uint64_t detected_memory = minimum_uint64(
|
|
snapshot->memory_available_bytes,
|
|
governor->profile.memory_total_bytes
|
|
);
|
|
uint64_t memory_budget = subtract_floor(
|
|
detected_memory,
|
|
governor->policy.system_memory_reserve_bytes
|
|
);
|
|
uint64_t gpu_budget = 0;
|
|
bool gpu_known = false;
|
|
if (governor->profile.gpu_uses_shared_memory) {
|
|
gpu_known = true;
|
|
gpu_budget = memory_budget;
|
|
} else if (governor->profile.gpu_memory_known
|
|
&& snapshot->gpu_memory_available_known) {
|
|
gpu_known = true;
|
|
gpu_budget = subtract_floor(
|
|
snapshot->gpu_memory_available_bytes,
|
|
governor->policy.gpu_memory_reserve_bytes
|
|
);
|
|
}
|
|
unsigned int cpu_budget = governor->profile.logical_cpu_count
|
|
- governor->policy.system_cpu_reserve;
|
|
*availability = (Lardon3DResourceAvailability) {
|
|
.memory_budget_bytes = memory_budget,
|
|
.memory_reserved_bytes = governor->memory_reserved_bytes,
|
|
.memory_available_bytes = subtract_floor(
|
|
memory_budget,
|
|
governor->memory_reserved_bytes
|
|
),
|
|
.gpu_memory_known = gpu_known,
|
|
.gpu_memory_budget_bytes = gpu_budget,
|
|
.gpu_memory_reserved_bytes = governor->gpu_memory_reserved_bytes,
|
|
.gpu_memory_available_bytes = governor->profile.gpu_uses_shared_memory
|
|
? subtract_floor(memory_budget, governor->memory_reserved_bytes)
|
|
: subtract_floor(gpu_budget, governor->gpu_memory_reserved_bytes),
|
|
.cpu_budget = cpu_budget,
|
|
.cpu_reserved = governor->cpu_reserved,
|
|
.cpu_available = subtract_unsigned(cpu_budget, governor->cpu_reserved),
|
|
.gpu_slot_budget = governor->policy.gpu_slot_capacity,
|
|
.gpu_slots_reserved = governor->gpu_slots_reserved,
|
|
.gpu_slots_available = subtract_unsigned(
|
|
governor->policy.gpu_slot_capacity,
|
|
governor->gpu_slots_reserved
|
|
),
|
|
.io_slot_budget = governor->policy.io_slot_capacity,
|
|
.io_slots_reserved = governor->io_slots_reserved,
|
|
.io_slots_available = subtract_unsigned(
|
|
governor->policy.io_slot_capacity,
|
|
governor->io_slots_reserved
|
|
),
|
|
.active_reservations = governor->active_count,
|
|
};
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
lardon3d_resource_governor_availability(
|
|
Lardon3DResourceGovernor *governor,
|
|
const Lardon3DResourceSnapshot *snapshot,
|
|
Lardon3DResourceAvailability *availability
|
|
)
|
|
{
|
|
if (!governor || !snapshot || !availability) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&governor->mutex);
|
|
bool success = availability_locked(governor, snapshot, availability);
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
return success;
|
|
}
|
|
|
|
uint64_t
|
|
lardon3d_resource_governor_generation(
|
|
Lardon3DResourceGovernor *governor
|
|
)
|
|
{
|
|
if (!governor) {
|
|
return 0;
|
|
}
|
|
(void)pthread_mutex_lock(&governor->mutex);
|
|
uint64_t generation = governor->generation;
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
return generation;
|
|
}
|
|
|
|
bool
|
|
lardon3d_resource_governor_wait_for_change(
|
|
Lardon3DResourceGovernor *governor,
|
|
uint64_t observed_generation,
|
|
uint64_t timeout_ns
|
|
)
|
|
{
|
|
if (!governor) {
|
|
return false;
|
|
}
|
|
struct timespec deadline;
|
|
if (clock_gettime(CLOCK_MONOTONIC, &deadline) != 0) {
|
|
return false;
|
|
}
|
|
deadline.tv_sec += (time_t)(timeout_ns / 1000000000ULL);
|
|
uint64_t remainder = timeout_ns % 1000000000ULL;
|
|
deadline.tv_nsec += (long)remainder;
|
|
if (deadline.tv_nsec >= 1000000000L) {
|
|
deadline.tv_sec += 1;
|
|
deadline.tv_nsec -= 1000000000L;
|
|
}
|
|
(void)pthread_mutex_lock(&governor->mutex);
|
|
bool changed = false;
|
|
while (governor->generation == observed_generation) {
|
|
int result = pthread_cond_timedwait(
|
|
&governor->cond,
|
|
&governor->mutex,
|
|
&deadline
|
|
);
|
|
if (result == ETIMEDOUT) {
|
|
break;
|
|
}
|
|
if (result != 0) {
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
return false;
|
|
}
|
|
}
|
|
changed = governor->generation != observed_generation;
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
return changed;
|
|
}
|
|
|
|
static void
|
|
evaluate_locked(
|
|
Lardon3DResourceGovernor *governor,
|
|
const Lardon3DResourceSnapshot *snapshot,
|
|
const Lardon3DResourceEstimate *estimate,
|
|
Lardon3DResourceDecision *decision
|
|
)
|
|
{
|
|
if (!valid_estimate(estimate)) {
|
|
set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, 0, 0, 0, "Estimation de ressources invalide.");
|
|
return;
|
|
}
|
|
if ((estimate->desired_gpu_slots > 0
|
|
|| estimate->gpu_memory_fixed_bytes > 0
|
|
|| estimate->gpu_memory_bytes_per_item > 0)
|
|
&& !governor->profile.gpu_available) {
|
|
set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, 0, 0, 0, "Aucun GPU disponible.");
|
|
return;
|
|
}
|
|
|
|
uint64_t memory_fixed = estimate->memory_fixed_bytes;
|
|
uint64_t memory_per_item = estimate->memory_bytes_per_item;
|
|
if (governor->profile.gpu_uses_shared_memory) {
|
|
if (memory_fixed > UINT64_MAX - estimate->gpu_memory_fixed_bytes
|
|
|| memory_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.");
|
|
return;
|
|
}
|
|
memory_fixed += estimate->gpu_memory_fixed_bytes;
|
|
memory_per_item += estimate->gpu_memory_bytes_per_item;
|
|
}
|
|
|
|
size_t theoretical_batch = batch_capacity(
|
|
governor->profile.memory_total_bytes
|
|
- governor->policy.system_memory_reserve_bytes,
|
|
memory_fixed,
|
|
memory_per_item
|
|
);
|
|
if (!governor->profile.gpu_uses_shared_memory
|
|
&& (estimate->gpu_memory_fixed_bytes > 0
|
|
|| estimate->gpu_memory_bytes_per_item > 0)
|
|
&& governor->profile.gpu_memory_known) {
|
|
theoretical_batch = minimum_size(
|
|
theoretical_batch,
|
|
batch_capacity(
|
|
governor->profile.gpu_memory_total_bytes
|
|
- governor->policy.gpu_memory_reserve_bytes,
|
|
estimate->gpu_memory_fixed_bytes,
|
|
estimate->gpu_memory_bytes_per_item
|
|
)
|
|
);
|
|
}
|
|
if (theoretical_batch < estimate->minimum_batch_size) {
|
|
set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, 0, 0, 0, "Tâche impossible sur cette machine.");
|
|
return;
|
|
}
|
|
|
|
double load_limit = (double)governor->profile.logical_cpu_count
|
|
* governor->policy.maximum_cpu_load_ratio;
|
|
if (snapshot->cpu_load_1m >= load_limit) {
|
|
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, "Charge CPU trop élevée.");
|
|
return;
|
|
}
|
|
if (estimate->desired_io_slots > 0 && snapshot->io_pressure_known
|
|
&& snapshot->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.");
|
|
return;
|
|
}
|
|
|
|
Lardon3DResourceAvailability available;
|
|
if (!availability_locked(governor, snapshot, &available)) {
|
|
set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, 0, 0, 0, "Instantané de ressources invalide.");
|
|
return;
|
|
}
|
|
size_t batch = batch_capacity(
|
|
available.memory_available_bytes,
|
|
memory_fixed,
|
|
memory_per_item
|
|
);
|
|
if (!governor->profile.gpu_uses_shared_memory
|
|
&& (estimate->gpu_memory_fixed_bytes > 0
|
|
|| estimate->gpu_memory_bytes_per_item > 0)) {
|
|
if (!available.gpu_memory_known) {
|
|
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, "Mémoire GPU disponible inconnue.");
|
|
return;
|
|
}
|
|
batch = minimum_size(
|
|
batch,
|
|
batch_capacity(
|
|
available.gpu_memory_available_bytes,
|
|
estimate->gpu_memory_fixed_bytes,
|
|
estimate->gpu_memory_bytes_per_item
|
|
)
|
|
);
|
|
}
|
|
/* Le lot maximal visé est corrigé par les métriques mesurées : c'est la
|
|
* nouvelle cible du contrat, pas une réduction faute de ressources. La
|
|
* correction ne descend jamais sous minimum_batch_size pour éviter un
|
|
* WAIT persistant. */
|
|
size_t adapted_maximum = adaptive_batch_limit(
|
|
governor,
|
|
estimate->task_class,
|
|
estimate->maximum_batch_size,
|
|
memory_per_item
|
|
);
|
|
if (adapted_maximum < estimate->minimum_batch_size) {
|
|
adapted_maximum = estimate->minimum_batch_size;
|
|
}
|
|
batch = minimum_size(batch, adapted_maximum);
|
|
if (batch < estimate->minimum_batch_size) {
|
|
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, "Ressources déjà réservées ou temporairement insuffisantes.");
|
|
return;
|
|
}
|
|
if (available.cpu_available == 0
|
|
|| (estimate->desired_gpu_slots > 0
|
|
&& available.gpu_slots_available == 0)
|
|
|| (estimate->desired_io_slots > 0
|
|
&& available.io_slots_available == 0)) {
|
|
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, 0, 0, 0, "Slots de calcul déjà réservés.");
|
|
return;
|
|
}
|
|
unsigned int cpu = estimate->desired_cpu_threads < available.cpu_available
|
|
? estimate->desired_cpu_threads
|
|
: available.cpu_available;
|
|
unsigned int gpu = estimate->desired_gpu_slots
|
|
< available.gpu_slots_available
|
|
? estimate->desired_gpu_slots
|
|
: available.gpu_slots_available;
|
|
unsigned int io = estimate->desired_io_slots < available.io_slots_available
|
|
? estimate->desired_io_slots
|
|
: available.io_slots_available;
|
|
bool reduced = batch < adapted_maximum
|
|
|| cpu < estimate->desired_cpu_threads
|
|
|| gpu < estimate->desired_gpu_slots
|
|
|| io < estimate->desired_io_slots;
|
|
set_decision(
|
|
decision,
|
|
reduced ? LARDON3D_RESOURCE_REDUCE_BATCH : LARDON3D_RESOURCE_START,
|
|
batch,
|
|
cpu,
|
|
gpu,
|
|
io,
|
|
reduced ? "Contrat réduit aux ressources disponibles."
|
|
: "Ressources disponibles."
|
|
);
|
|
}
|
|
|
|
bool
|
|
lardon3d_resource_governor_reserve(
|
|
Lardon3DResourceGovernor *governor,
|
|
const Lardon3DResourceSnapshot *snapshot,
|
|
const Lardon3DResourceEstimate *estimate,
|
|
Lardon3DResourceDecision *decision,
|
|
Lardon3DResourceReservation **reservation
|
|
)
|
|
{
|
|
if (!governor || !snapshot || !estimate || !decision || !reservation) {
|
|
return false;
|
|
}
|
|
*reservation = NULL;
|
|
Lardon3DResourceReservation *created = calloc(1, sizeof(*created));
|
|
if (!created) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&governor->mutex);
|
|
if (!valid_snapshot(&governor->profile, snapshot)) {
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
free(created);
|
|
return false;
|
|
}
|
|
evaluate_locked(governor, snapshot, estimate, decision);
|
|
if ((decision->kind != LARDON3D_RESOURCE_START
|
|
&& decision->kind != LARDON3D_RESOURCE_REDUCE_BATCH)
|
|
|| governor->next_reservation_id == 0) {
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
free(created);
|
|
return true;
|
|
}
|
|
uint64_t memory_bytes;
|
|
uint64_t gpu_memory_bytes;
|
|
if (!resource_size(
|
|
estimate->memory_fixed_bytes,
|
|
estimate->memory_bytes_per_item,
|
|
decision->batch_size,
|
|
&memory_bytes
|
|
)
|
|
|| !resource_size(
|
|
estimate->gpu_memory_fixed_bytes,
|
|
estimate->gpu_memory_bytes_per_item,
|
|
decision->batch_size,
|
|
&gpu_memory_bytes
|
|
)
|
|
|| clock_gettime(CLOCK_REALTIME, &created->information.created_at) != 0) {
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
free(created);
|
|
return false;
|
|
}
|
|
created->information.id = governor->next_reservation_id++;
|
|
created->information.memory_bytes = memory_bytes;
|
|
created->information.gpu_memory_bytes = gpu_memory_bytes;
|
|
created->information.cpu_threads = decision->cpu_threads;
|
|
created->information.gpu_slots = decision->gpu_slots;
|
|
created->information.io_slots = decision->io_slots;
|
|
created->information.batch_size = decision->batch_size;
|
|
created->information.task_class = estimate->task_class;
|
|
created->information.state = LARDON3D_RESERVATION_ACTIVE;
|
|
created->charged_memory_bytes = governor->profile.gpu_uses_shared_memory
|
|
? memory_bytes + gpu_memory_bytes
|
|
: memory_bytes;
|
|
created->next = governor->active;
|
|
governor->active = created;
|
|
governor->memory_reserved_bytes += created->charged_memory_bytes;
|
|
governor->gpu_memory_reserved_bytes += gpu_memory_bytes;
|
|
governor->cpu_reserved += decision->cpu_threads;
|
|
governor->gpu_slots_reserved += decision->gpu_slots;
|
|
governor->io_slots_reserved += decision->io_slots;
|
|
++governor->active_count;
|
|
*reservation = created;
|
|
++governor->generation;
|
|
(void)pthread_cond_broadcast(&governor->cond);
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
lardon3d_resource_governor_reserve_available(
|
|
Lardon3DResourceGovernor *governor,
|
|
const Lardon3DResourceEstimate *estimate,
|
|
Lardon3DResourceDecision *decision,
|
|
Lardon3DResourceReservation **reservation
|
|
)
|
|
{
|
|
if (!governor || !estimate || !decision || !reservation) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&governor->mutex);
|
|
Lardon3DHardwareProfile profile = governor->profile;
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
Lardon3DResourceSnapshot snapshot;
|
|
if (!lardon3d_resource_snapshot_capture(&profile, &snapshot, NULL, 0)) {
|
|
return false;
|
|
}
|
|
return lardon3d_resource_governor_reserve(
|
|
governor,
|
|
&snapshot,
|
|
estimate,
|
|
decision,
|
|
reservation
|
|
);
|
|
}
|
|
|
|
static Lardon3DResourceReservation *
|
|
find_reservation(
|
|
Lardon3DResourceReservation *head,
|
|
const Lardon3DResourceReservation *wanted
|
|
)
|
|
{
|
|
for (Lardon3DResourceReservation *current = head;
|
|
current;
|
|
current = current->next) {
|
|
if (current == wanted) {
|
|
return current;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool
|
|
lardon3d_resource_governor_release(
|
|
Lardon3DResourceGovernor *governor,
|
|
Lardon3DResourceReservation *reservation
|
|
)
|
|
{
|
|
if (!governor || !reservation) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&governor->mutex);
|
|
Lardon3DResourceReservation *previous = NULL;
|
|
Lardon3DResourceReservation *current = governor->active;
|
|
while (current && current != reservation) {
|
|
previous = current;
|
|
current = current->next;
|
|
}
|
|
if (!current) {
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
return false;
|
|
}
|
|
if (previous) {
|
|
previous->next = current->next;
|
|
} else {
|
|
governor->active = current->next;
|
|
}
|
|
governor->memory_reserved_bytes -= current->charged_memory_bytes;
|
|
governor->gpu_memory_reserved_bytes -= current->information.gpu_memory_bytes;
|
|
governor->cpu_reserved -= current->information.cpu_threads;
|
|
governor->gpu_slots_reserved -= current->information.gpu_slots;
|
|
governor->io_slots_reserved -= current->information.io_slots;
|
|
--governor->active_count;
|
|
current->information.state = LARDON3D_RESERVATION_RELEASED;
|
|
current->next = governor->released;
|
|
governor->released = current;
|
|
++governor->generation;
|
|
(void)pthread_cond_broadcast(&governor->cond);
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
lardon3d_resource_governor_reservation_is_valid(
|
|
Lardon3DResourceGovernor *governor,
|
|
const Lardon3DResourceReservation *reservation
|
|
)
|
|
{
|
|
if (!governor || !reservation) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&governor->mutex);
|
|
bool valid = find_reservation(governor->active, reservation) != NULL;
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
return valid;
|
|
}
|
|
|
|
bool
|
|
lardon3d_resource_reservation_get(
|
|
Lardon3DResourceGovernor *governor,
|
|
const Lardon3DResourceReservation *reservation,
|
|
Lardon3DResourceReservationInfo *information
|
|
)
|
|
{
|
|
if (!governor || !reservation || !information) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&governor->mutex);
|
|
Lardon3DResourceReservation *found = find_reservation(
|
|
governor->active,
|
|
reservation
|
|
);
|
|
if (!found) {
|
|
found = find_reservation(governor->released, reservation);
|
|
}
|
|
if (found) {
|
|
*information = found->information;
|
|
}
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
return found != NULL;
|
|
}
|
|
|
|
bool
|
|
lardon3d_resource_reservation_get_active(
|
|
Lardon3DResourceGovernor *governor,
|
|
const Lardon3DResourceReservation *reservation,
|
|
Lardon3DResourceReservationInfo *information
|
|
)
|
|
{
|
|
if (!governor || !reservation || !information) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&governor->mutex);
|
|
Lardon3DResourceReservation *found = find_reservation(
|
|
governor->active,
|
|
reservation
|
|
);
|
|
if (found) {
|
|
*information = found->information;
|
|
}
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
return found != NULL;
|
|
}
|
|
|
|
size_t
|
|
lardon3d_resource_governor_list_reservations(
|
|
Lardon3DResourceGovernor *governor,
|
|
Lardon3DResourceReservationInfo *reservations,
|
|
size_t capacity
|
|
)
|
|
{
|
|
if (!governor || (!reservations && capacity > 0)) {
|
|
return 0;
|
|
}
|
|
(void)pthread_mutex_lock(&governor->mutex);
|
|
size_t count = 0;
|
|
for (Lardon3DResourceReservation *current = governor->active;
|
|
current && count < capacity;
|
|
current = current->next) {
|
|
reservations[count++] = current->information;
|
|
}
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
return count;
|
|
}
|
|
|
|
size_t
|
|
lardon3d_resource_governor_reservation_count(
|
|
Lardon3DResourceGovernor *governor
|
|
)
|
|
{
|
|
if (!governor) {
|
|
return 0;
|
|
}
|
|
(void)pthread_mutex_lock(&governor->mutex);
|
|
size_t count = governor->active_count;
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
return count;
|
|
}
|
|
|
|
bool
|
|
lardon3d_resource_governor_record_batch(
|
|
Lardon3DResourceGovernor *governor,
|
|
Lardon3DResourceTaskClass task_class,
|
|
size_t batch_size,
|
|
uint64_t duration_ns,
|
|
size_t peak_memory_bytes
|
|
)
|
|
{
|
|
if (!governor) {
|
|
return false;
|
|
}
|
|
if (batch_size == 0) {
|
|
/* No-op réussi : aucune métrique, aucun réveil inutile. */
|
|
return true;
|
|
}
|
|
(void)pthread_mutex_lock(&governor->mutex);
|
|
bool recorded = record_batch_locked(
|
|
governor,
|
|
task_class,
|
|
batch_size,
|
|
duration_ns,
|
|
peak_memory_bytes
|
|
);
|
|
if (recorded) {
|
|
++governor->generation;
|
|
(void)pthread_cond_broadcast(&governor->cond);
|
|
}
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
return recorded;
|
|
}
|
|
|
|
bool
|
|
lardon3d_resource_governor_decide(
|
|
Lardon3DResourceGovernor *governor,
|
|
const Lardon3DResourceSnapshot *snapshot,
|
|
const Lardon3DResourceRequest *request,
|
|
Lardon3DResourceDecision *decision
|
|
)
|
|
{
|
|
if (!governor || !snapshot || !request || !decision) {
|
|
return false;
|
|
}
|
|
Lardon3DResourceEstimate estimate = {
|
|
.memory_bytes_per_item = request->memory_bytes_per_item,
|
|
.gpu_memory_bytes_per_item = request->gpu_memory_bytes_per_item,
|
|
.minimum_batch_size = request->minimum_batch_size,
|
|
.maximum_batch_size = request->preferred_batch_size,
|
|
.desired_cpu_threads = request->requested_cpu_threads,
|
|
.desired_gpu_slots = request->gpu_memory_bytes_per_item > 0 ? 1 : 0,
|
|
.desired_io_slots = request->io_intensive ? 1 : 0,
|
|
.task_class = request->io_intensive
|
|
? LARDON3D_RESOURCE_TASK_IO
|
|
: LARDON3D_RESOURCE_TASK_GENERAL,
|
|
};
|
|
(void)pthread_mutex_lock(&governor->mutex);
|
|
if (!valid_snapshot(&governor->profile, snapshot)) {
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
return false;
|
|
}
|
|
evaluate_locked(governor, snapshot, &estimate, decision);
|
|
(void)pthread_mutex_unlock(&governor->mutex);
|
|
return true;
|
|
}
|
|
|
|
const char *
|
|
lardon3d_resource_decision_name(Lardon3DResourceDecisionKind kind)
|
|
{
|
|
switch (kind) {
|
|
case LARDON3D_RESOURCE_START:
|
|
return "Démarrer";
|
|
case LARDON3D_RESOURCE_WAIT:
|
|
return "Attendre";
|
|
case LARDON3D_RESOURCE_REDUCE_BATCH:
|
|
return "Réduire le lot";
|
|
case LARDON3D_RESOURCE_REJECT:
|
|
return "Refuser";
|
|
default:
|
|
return "Inconnue";
|
|
}
|
|
}
|