feat: add resource reservation engine

This commit is contained in:
fy59 2026-08-06 20:41:17 +02:00
parent 030994696b
commit 0290331c60
11 changed files with 1152 additions and 0 deletions

View file

@ -48,3 +48,8 @@ Un moteur générique exécute les tâches en file FIFO sur un worker unique. Il
prend en charge progression, pause, reprise et annulation coopérative sans prend en charge progression, pause, reprise et annulation coopérative sans
dépendre de la TUI ni des modules métier. L'écran `F5` affiche l'état courant de dépendre de la TUI ni des modules métier. L'écran `F5` affiche l'état courant de
la file et de ses tâches. la file et de ses tâches.
Les fondations du moteur de ressources séparent la détection matérielle, les
instantanés de disponibilité et les décisions du gouverneur. Le gouverneur
conserve des marges système et GPU, puis autorise, diffère, réduit ou refuse un
lot sans dépendre du scheduler ni d'un traitement métier.

View file

@ -0,0 +1,30 @@
#ifndef LARDON3D_HARDWARE_PROFILE_H
#define LARDON3D_HARDWARE_PROFILE_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
enum {
LARDON3D_HARDWARE_NAME_CAPACITY = 128,
};
typedef struct {
unsigned int logical_cpu_count;
uint64_t page_size_bytes;
uint64_t memory_total_bytes;
bool gpu_available;
bool gpu_memory_known;
bool gpu_uses_shared_memory;
uint64_t gpu_memory_total_bytes;
char cpu_architecture[LARDON3D_HARDWARE_NAME_CAPACITY];
char gpu_name[LARDON3D_HARDWARE_NAME_CAPACITY];
} Lardon3DHardwareProfile;
bool lardon3d_hardware_profile_detect(
Lardon3DHardwareProfile *profile,
char *error_message,
size_t error_message_size
);
#endif

View file

@ -0,0 +1,73 @@
#ifndef LARDON3D_RESOURCE_GOVERNOR_H
#define LARDON3D_RESOURCE_GOVERNOR_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <lardon3d/hardware_profile.h>
#include <lardon3d/resource_snapshot.h>
enum {
LARDON3D_RESOURCE_REASON_CAPACITY = 256,
};
typedef struct Lardon3DResourceGovernor Lardon3DResourceGovernor;
typedef struct {
uint64_t system_memory_reserve_bytes;
uint64_t gpu_memory_reserve_bytes;
unsigned int system_cpu_reserve;
double maximum_cpu_load_ratio;
double maximum_io_pressure_avg10;
} Lardon3DResourcePolicy;
typedef struct {
uint64_t memory_bytes_per_item;
uint64_t gpu_memory_bytes_per_item;
size_t minimum_batch_size;
size_t preferred_batch_size;
unsigned int requested_cpu_threads;
bool io_intensive;
} Lardon3DResourceRequest;
typedef enum {
LARDON3D_RESOURCE_START = 0,
LARDON3D_RESOURCE_WAIT,
LARDON3D_RESOURCE_REDUCE_BATCH,
LARDON3D_RESOURCE_REJECT
} Lardon3DResourceDecisionKind;
typedef struct {
Lardon3DResourceDecisionKind kind;
size_t batch_size;
unsigned int cpu_threads;
char reason[LARDON3D_RESOURCE_REASON_CAPACITY];
} Lardon3DResourceDecision;
bool lardon3d_resource_policy_default(
const Lardon3DHardwareProfile *profile,
Lardon3DResourcePolicy *policy
);
Lardon3DResourceGovernor *lardon3d_resource_governor_create(
const Lardon3DHardwareProfile *profile,
const Lardon3DResourcePolicy *policy
);
void lardon3d_resource_governor_destroy(
Lardon3DResourceGovernor *governor
);
bool lardon3d_resource_governor_set_policy(
Lardon3DResourceGovernor *governor,
const Lardon3DResourcePolicy *policy
);
bool lardon3d_resource_governor_decide(
Lardon3DResourceGovernor *governor,
const Lardon3DResourceSnapshot *snapshot,
const Lardon3DResourceRequest *request,
Lardon3DResourceDecision *decision
);
const char *lardon3d_resource_decision_name(
Lardon3DResourceDecisionKind kind
);
#endif

View file

@ -0,0 +1,32 @@
#ifndef LARDON3D_RESOURCE_SNAPSHOT_H
#define LARDON3D_RESOURCE_SNAPSHOT_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <time.h>
#include <lardon3d/hardware_profile.h>
typedef struct {
struct timespec captured_at;
uint64_t memory_available_bytes;
uint64_t memory_free_bytes;
uint64_t swap_available_bytes;
bool gpu_memory_available_known;
uint64_t gpu_memory_available_bytes;
double cpu_load_1m;
double cpu_load_5m;
double cpu_load_15m;
bool io_pressure_known;
double io_pressure_avg10;
} Lardon3DResourceSnapshot;
bool lardon3d_resource_snapshot_capture(
const Lardon3DHardwareProfile *profile,
Lardon3DResourceSnapshot *snapshot,
char *error_message,
size_t error_message_size
);
#endif

View file

@ -36,6 +36,9 @@ executable(
'src/project.c', 'src/project.c',
'src/task.c', 'src/task.c',
'src/task_queue.c', 'src/task_queue.c',
'src/hardware_profile.c',
'src/resource_snapshot.c',
'src/resource_governor.c',
], ],
include_directories: include_directories('include'), include_directories: include_directories('include'),
dependencies: [ncursesw, threads], dependencies: [ncursesw, threads],
@ -116,3 +119,38 @@ task_queue_test = executable(
) )
test('task-queue', task_queue_test, timeout: 30) test('task-queue', task_queue_test, timeout: 30)
hardware_profile_test = executable(
'test-hardware-profile',
sources: [
'tests/test_hardware_profile.c',
'src/hardware_profile.c',
],
include_directories: include_directories('include'),
)
test('hardware-profile', hardware_profile_test, timeout: 30)
resource_snapshot_test = executable(
'test-resource-snapshot',
sources: [
'tests/test_resource_snapshot.c',
'src/hardware_profile.c',
'src/resource_snapshot.c',
],
include_directories: include_directories('include'),
)
test('resource-snapshot', resource_snapshot_test, timeout: 30)
resource_governor_test = executable(
'test-resource-governor',
sources: [
'tests/test_resource_governor.c',
'src/resource_governor.c',
],
include_directories: include_directories('include'),
dependencies: [threads],
)
test('resource-governor', resource_governor_test, timeout: 30)

182
src/hardware_profile.c Normal file
View file

@ -0,0 +1,182 @@
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <lardon3d/hardware_profile.h>
static void
set_error(char *message, size_t size, const char *text)
{
if (message && size > 0) {
(void)snprintf(message, size, "%s", text);
}
}
static bool
read_text(const char *path, char *text, size_t capacity)
{
if (!text || capacity < 2) {
return false;
}
int descriptor = open(path, O_RDONLY | O_CLOEXEC);
if (descriptor < 0) {
return false;
}
ssize_t count;
do {
count = read(descriptor, text, capacity - 1);
} while (count < 0 && errno == EINTR);
bool success = count >= 0 && close(descriptor) == 0;
if (!success) {
return false;
}
text[(size_t)count] = '\0';
while (count > 0 && (text[(size_t)count - 1] == '\n'
|| text[(size_t)count - 1] == '\r')) {
text[--count] = '\0';
}
return true;
}
static bool
parse_uint64(const char *text, uint64_t *value)
{
if (!text || !text[0] || !value || text[0] == '-') {
return false;
}
errno = 0;
char *end;
unsigned long long parsed = strtoull(text, &end, 0);
if (errno != 0 || end == text) {
return false;
}
while (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r') {
++end;
}
if (*end) {
return false;
}
*value = (uint64_t)parsed;
return true;
}
static const char *
vendor_name(const char *vendor)
{
if (strcmp(vendor, "0x1002") == 0) {
return "AMD";
}
if (strcmp(vendor, "0x8086") == 0) {
return "Intel";
}
if (strcmp(vendor, "0x10de") == 0) {
return "NVIDIA";
}
return vendor;
}
static void
detect_gpu(Lardon3DHardwareProfile *profile)
{
for (unsigned int index = 0; index < 64; ++index) {
char vendor_path[PATH_MAX];
char memory_path[PATH_MAX];
int vendor_written = snprintf(
vendor_path,
sizeof(vendor_path),
"/sys/class/drm/card%u/device/vendor",
index
);
int memory_written = snprintf(
memory_path,
sizeof(memory_path),
"/sys/class/drm/card%u/device/mem_info_vram_total",
index
);
if (vendor_written < 0 || (size_t)vendor_written >= sizeof(vendor_path)
|| memory_written < 0
|| (size_t)memory_written >= sizeof(memory_path)) {
continue;
}
char vendor[32];
if (!read_text(vendor_path, vendor, sizeof(vendor))) {
continue;
}
profile->gpu_available = true;
(void)snprintf(
profile->gpu_name,
sizeof(profile->gpu_name),
"%s GPU",
vendor_name(vendor)
);
char memory[64];
uint64_t bytes;
if (read_text(memory_path, memory, sizeof(memory))
&& parse_uint64(memory, &bytes) && bytes > 0) {
profile->gpu_memory_known = true;
profile->gpu_memory_total_bytes = bytes;
} else {
profile->gpu_uses_shared_memory = true;
}
return;
}
}
bool
lardon3d_hardware_profile_detect(
Lardon3DHardwareProfile *profile,
char *error_message,
size_t error_message_size
)
{
set_error(error_message, error_message_size, "");
if (!profile) {
set_error(error_message, error_message_size, "Profil matériel absent.");
return false;
}
*profile = (Lardon3DHardwareProfile) {0};
long cpu_count = sysconf(_SC_NPROCESSORS_ONLN);
long page_size = sysconf(_SC_PAGESIZE);
long page_count = sysconf(_SC_PHYS_PAGES);
if (cpu_count < 1 || cpu_count > UINT_MAX || page_size < 1
|| page_count < 1) {
set_error(
error_message,
error_message_size,
"Détection CPU ou mémoire impossible."
);
return false;
}
uint64_t page_size_bytes = (uint64_t)page_size;
uint64_t pages = (uint64_t)page_count;
if (pages > UINT64_MAX / page_size_bytes) {
set_error(error_message, error_message_size, "Mémoire physique trop grande.");
return false;
}
profile->logical_cpu_count = (unsigned int)cpu_count;
profile->page_size_bytes = page_size_bytes;
profile->memory_total_bytes = pages * page_size_bytes;
struct utsname system_name;
if (uname(&system_name) == 0) {
(void)snprintf(
profile->cpu_architecture,
sizeof(profile->cpu_architecture),
"%s",
system_name.machine
);
} else {
(void)snprintf(
profile->cpu_architecture,
sizeof(profile->cpu_architecture),
"Linux"
);
}
detect_gpu(profile);
return true;
}

288
src/resource_governor.c Normal file
View file

@ -0,0 +1,288 @@
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <lardon3d/resource_governor.h>
struct Lardon3DResourceGovernor {
pthread_mutex_t mutex;
Lardon3DHardwareProfile profile;
Lardon3DResourcePolicy policy;
};
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
&& (!profile->gpu_memory_known
|| policy->gpu_memory_reserve_bytes
< profile->gpu_memory_total_bytes);
}
static void
set_decision(
Lardon3DResourceDecision *decision,
Lardon3DResourceDecisionKind kind,
size_t batch_size,
unsigned int cpu_threads,
const char *reason
)
{
*decision = (Lardon3DResourceDecision) {
.kind = kind,
.batch_size = batch_size,
.cpu_threads = cpu_threads,
};
(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,
};
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;
}
governor->profile = *profile;
governor->policy = *policy;
return governor;
}
void
lardon3d_resource_governor_destroy(Lardon3DResourceGovernor *governor)
{
if (!governor) {
return;
}
(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 accepted = valid_policy(&governor->profile, policy);
if (accepted) {
governor->policy = *policy;
}
(void)pthread_mutex_unlock(&governor->mutex);
return accepted;
}
static size_t
capacity_for(uint64_t available, uint64_t reserve, uint64_t per_item)
{
if (per_item == 0) {
return SIZE_MAX;
}
if (available <= reserve) {
return 0;
}
uint64_t capacity = (available - reserve) / per_item;
return capacity > SIZE_MAX ? SIZE_MAX : (size_t)capacity;
}
static size_t
minimum_size(size_t left, size_t right)
{
return left < right ? left : right;
}
bool
lardon3d_resource_governor_decide(
Lardon3DResourceGovernor *governor,
const Lardon3DResourceSnapshot *snapshot,
const Lardon3DResourceRequest *request,
Lardon3DResourceDecision *decision
)
{
if (!governor || !snapshot || !request || !decision) {
return false;
}
(void)pthread_mutex_lock(&governor->mutex);
const Lardon3DHardwareProfile profile = governor->profile;
const Lardon3DResourcePolicy policy = governor->policy;
(void)pthread_mutex_unlock(&governor->mutex);
if (!(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)) {
return false;
}
if (request->minimum_batch_size == 0
|| request->preferred_batch_size < request->minimum_batch_size
|| request->requested_cpu_threads == 0) {
set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, 0, "Demande de ressources invalide.");
return true;
}
unsigned int available_threads = profile.logical_cpu_count
- policy.system_cpu_reserve;
unsigned int cpu_threads = request->requested_cpu_threads < available_threads
? request->requested_cpu_threads
: available_threads;
uint64_t system_memory_per_item = request->memory_bytes_per_item;
if (request->gpu_memory_bytes_per_item > 0
&& profile.gpu_uses_shared_memory) {
if (system_memory_per_item
> UINT64_MAX - request->gpu_memory_bytes_per_item) {
set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, cpu_threads, "Besoin mémoire trop grand.");
return true;
}
system_memory_per_item += request->gpu_memory_bytes_per_item;
}
size_t theoretical_batch = capacity_for(
profile.memory_total_bytes,
policy.system_memory_reserve_bytes,
system_memory_per_item
);
if (request->gpu_memory_bytes_per_item > 0) {
if (!profile.gpu_available) {
set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, cpu_threads, "Aucun GPU disponible.");
return true;
}
if (profile.gpu_memory_known && !profile.gpu_uses_shared_memory) {
theoretical_batch = minimum_size(
theoretical_batch,
capacity_for(
profile.gpu_memory_total_bytes,
policy.gpu_memory_reserve_bytes,
request->gpu_memory_bytes_per_item
)
);
}
}
if (theoretical_batch < request->minimum_batch_size) {
set_decision(decision, LARDON3D_RESOURCE_REJECT, 0, cpu_threads, "Tâche impossible sur cette machine.");
return true;
}
double load_limit = (double)profile.logical_cpu_count
* policy.maximum_cpu_load_ratio;
if (snapshot->cpu_load_1m >= load_limit) {
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, cpu_threads, "Charge CPU trop élevée.");
return true;
}
if (request->io_intensive && snapshot->io_pressure_known
&& snapshot->io_pressure_avg10 >= policy.maximum_io_pressure_avg10) {
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, cpu_threads, "Pression d'entrée-sortie trop élevée.");
return true;
}
size_t current_batch = capacity_for(
snapshot->memory_available_bytes,
policy.system_memory_reserve_bytes,
system_memory_per_item
);
if (request->gpu_memory_bytes_per_item > 0
&& !profile.gpu_uses_shared_memory) {
if (!snapshot->gpu_memory_available_known) {
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, cpu_threads, "Mémoire GPU disponible inconnue.");
return true;
}
current_batch = minimum_size(
current_batch,
capacity_for(
snapshot->gpu_memory_available_bytes,
policy.gpu_memory_reserve_bytes,
request->gpu_memory_bytes_per_item
)
);
}
if (current_batch < request->minimum_batch_size) {
set_decision(decision, LARDON3D_RESOURCE_WAIT, 0, cpu_threads, "Ressources temporairement insuffisantes.");
return true;
}
size_t batch_size = minimum_size(
current_batch,
request->preferred_batch_size
);
if (batch_size < request->preferred_batch_size) {
set_decision(decision, LARDON3D_RESOURCE_REDUCE_BATCH, batch_size, cpu_threads, "Taille de lot réduite.");
return true;
}
set_decision(decision, LARDON3D_RESOURCE_START, batch_size, cpu_threads, "Ressources disponibles.");
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";
}
}

192
src/resource_snapshot.c Normal file
View file

@ -0,0 +1,192 @@
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <lardon3d/resource_snapshot.h>
enum {
PROC_BUFFER_CAPACITY = 32768,
};
static void
set_error(char *message, size_t size, const char *text)
{
if (message && size > 0) {
(void)snprintf(message, size, "%s", text);
}
}
static bool
read_file(const char *path, char *buffer, size_t capacity)
{
int descriptor = open(path, O_RDONLY | O_CLOEXEC);
if (descriptor < 0) {
return false;
}
size_t total = 0;
while (total + 1 < capacity) {
ssize_t count = read(descriptor, buffer + total, capacity - total - 1);
if (count < 0 && errno == EINTR) {
continue;
}
if (count < 0) {
(void)close(descriptor);
return false;
}
if (count == 0) {
break;
}
total += (size_t)count;
}
bool success = close(descriptor) == 0;
buffer[total] = '\0';
return success;
}
static bool
meminfo_bytes(const char *buffer, const char *key, uint64_t *bytes)
{
const char *line = buffer;
size_t key_length = strlen(key);
while (*line) {
if (strncmp(line, key, key_length) == 0 && line[key_length] == ':') {
const char *number = line + key_length + 1;
while (*number == ' ' || *number == '\t') {
++number;
}
errno = 0;
char *end;
unsigned long long kibibytes = strtoull(number, &end, 10);
if (errno != 0 || end == number || kibibytes > UINT64_MAX / 1024) {
return false;
}
while (*end == ' ' || *end == '\t') {
++end;
}
if (strncmp(end, "kB", 2) != 0) {
return false;
}
*bytes = (uint64_t)kibibytes * 1024;
return true;
}
const char *newline = strchr(line, '\n');
if (!newline) {
break;
}
line = newline + 1;
}
return false;
}
static bool
capture_load(Lardon3DResourceSnapshot *snapshot)
{
char buffer[256];
if (!read_file("/proc/loadavg", buffer, sizeof(buffer))) {
return false;
}
return sscanf(
buffer,
"%lf %lf %lf",
&snapshot->cpu_load_1m,
&snapshot->cpu_load_5m,
&snapshot->cpu_load_15m
) == 3;
}
static void
capture_io_pressure(Lardon3DResourceSnapshot *snapshot)
{
char buffer[512];
if (!read_file("/proc/pressure/io", buffer, sizeof(buffer))) {
return;
}
double average;
if (sscanf(buffer, "some avg10=%lf", &average) == 1 && average >= 0.0) {
snapshot->io_pressure_known = true;
snapshot->io_pressure_avg10 = average;
}
}
static void
capture_gpu_memory(
const Lardon3DHardwareProfile *profile,
Lardon3DResourceSnapshot *snapshot
)
{
if (!profile->gpu_available) {
return;
}
if (profile->gpu_uses_shared_memory && !profile->gpu_memory_known) {
snapshot->gpu_memory_available_known = true;
snapshot->gpu_memory_available_bytes = snapshot->memory_available_bytes;
return;
}
if (!profile->gpu_memory_known) {
return;
}
for (unsigned int index = 0; index < 64; ++index) {
char path[256];
int written = snprintf(
path,
sizeof(path),
"/sys/class/drm/card%u/device/mem_info_vram_used",
index
);
if (written < 0 || (size_t)written >= sizeof(path)) {
continue;
}
char buffer[64];
if (!read_file(path, buffer, sizeof(buffer))) {
continue;
}
errno = 0;
char *end;
unsigned long long used = strtoull(buffer, &end, 10);
if (errno == 0 && end != buffer
&& (uint64_t)used <= profile->gpu_memory_total_bytes) {
snapshot->gpu_memory_available_known = true;
snapshot->gpu_memory_available_bytes =
profile->gpu_memory_total_bytes - (uint64_t)used;
return;
}
}
}
bool
lardon3d_resource_snapshot_capture(
const Lardon3DHardwareProfile *profile,
Lardon3DResourceSnapshot *snapshot,
char *error_message,
size_t error_message_size
)
{
set_error(error_message, error_message_size, "");
if (!profile || !snapshot || profile->logical_cpu_count == 0
|| profile->memory_total_bytes == 0) {
set_error(error_message, error_message_size, "Profil matériel invalide.");
return false;
}
*snapshot = (Lardon3DResourceSnapshot) {0};
char buffer[PROC_BUFFER_CAPACITY];
if (!read_file("/proc/meminfo", buffer, sizeof(buffer))
|| !meminfo_bytes(
buffer,
"MemAvailable",
&snapshot->memory_available_bytes
)
|| !meminfo_bytes(buffer, "MemFree", &snapshot->memory_free_bytes)
|| !meminfo_bytes(buffer, "SwapFree", &snapshot->swap_available_bytes)
|| !capture_load(snapshot)
|| clock_gettime(CLOCK_REALTIME, &snapshot->captured_at) != 0) {
set_error(error_message, error_message_size, "Instantané système impossible.");
return false;
}
capture_io_pressure(snapshot);
capture_gpu_memory(profile, snapshot);
return true;
}

View file

@ -0,0 +1,47 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <lardon3d/hardware_profile.h>
#define CHECK(condition) \
do { \
if (!(condition)) { \
(void)fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \
return false; \
} \
} while (0)
static bool
run_test(void)
{
char error[256];
CHECK(!lardon3d_hardware_profile_detect(NULL, error, sizeof(error)));
CHECK(error[0]);
Lardon3DHardwareProfile profile;
CHECK(lardon3d_hardware_profile_detect(&profile, error, sizeof(error)));
CHECK(error[0] == '\0');
CHECK(profile.logical_cpu_count > 0);
CHECK(profile.page_size_bytes > 0);
CHECK(profile.memory_total_bytes >= profile.page_size_bytes);
CHECK(profile.cpu_architecture[0]);
if (profile.gpu_memory_known) {
CHECK(profile.gpu_available);
CHECK(profile.gpu_memory_total_bytes > 0);
}
if (profile.gpu_available) {
CHECK(profile.gpu_name[0]);
}
Lardon3DHardwareProfile second;
CHECK(lardon3d_hardware_profile_detect(&second, NULL, 0));
CHECK(second.logical_cpu_count == profile.logical_cpu_count);
CHECK(second.memory_total_bytes == profile.memory_total_bytes);
return true;
}
int
main(void)
{
return run_test() ? EXIT_SUCCESS : EXIT_FAILURE;
}

View file

@ -0,0 +1,205 @@
#include <pthread.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <lardon3d/resource_governor.h>
#define CHECK(condition) \
do { \
if (!(condition)) { \
(void)fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \
return false; \
} \
} while (0)
#define GIBIBYTES(value) ((uint64_t)(value) * 1024 * 1024 * 1024)
#define MEBIBYTES(value) ((uint64_t)(value) * 1024 * 1024)
typedef struct {
Lardon3DResourceGovernor *governor;
Lardon3DResourceSnapshot snapshot;
Lardon3DResourceRequest request;
atomic_bool *failed;
} ThreadContext;
static void *
decide_repeatedly(void *argument)
{
ThreadContext *context = argument;
for (size_t attempt = 0; attempt < 10000; ++attempt) {
Lardon3DResourceDecision decision;
if (!lardon3d_resource_governor_decide(
context->governor,
&context->snapshot,
&context->request,
&decision
)
|| decision.kind != LARDON3D_RESOURCE_START
|| decision.batch_size != context->request.preferred_batch_size) {
atomic_store(context->failed, true);
break;
}
}
return NULL;
}
static bool
run_test(void)
{
Lardon3DHardwareProfile profile = {
.logical_cpu_count = 16,
.page_size_bytes = 4096,
.memory_total_bytes = GIBIBYTES(16),
.cpu_architecture = "test",
};
Lardon3DResourcePolicy policy;
CHECK(lardon3d_resource_policy_default(&profile, &policy));
CHECK(policy.system_memory_reserve_bytes == GIBIBYTES(2));
CHECK(policy.system_cpu_reserve == 1);
policy = (Lardon3DResourcePolicy) {
.system_memory_reserve_bytes = GIBIBYTES(2),
.gpu_memory_reserve_bytes = 0,
.system_cpu_reserve = 1,
.maximum_cpu_load_ratio = 0.90,
.maximum_io_pressure_avg10 = 80.0,
};
CHECK(!lardon3d_resource_governor_create(NULL, &policy));
Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create(
&profile,
&policy
);
CHECK(governor);
Lardon3DResourceSnapshot snapshot = {
.memory_available_bytes = GIBIBYTES(10),
.cpu_load_1m = 2.0,
};
Lardon3DResourceRequest request = {
.memory_bytes_per_item = GIBIBYTES(1),
.minimum_batch_size = 2,
.preferred_batch_size = 8,
.requested_cpu_threads = 16,
};
Lardon3DResourceDecision decision;
CHECK(lardon3d_resource_governor_decide(
governor,
&snapshot,
&request,
&decision
));
CHECK(decision.kind == LARDON3D_RESOURCE_START);
CHECK(decision.batch_size == 8);
CHECK(decision.cpu_threads == 15);
CHECK(decision.reason[0]);
Lardon3DResourceSnapshot invalid_snapshot = snapshot;
invalid_snapshot.cpu_load_1m = -1.0;
CHECK(!lardon3d_resource_governor_decide(
governor,
&invalid_snapshot,
&request,
&decision
));
snapshot.memory_available_bytes = GIBIBYTES(6);
CHECK(lardon3d_resource_governor_decide(governor, &snapshot, &request, &decision));
CHECK(decision.kind == LARDON3D_RESOURCE_REDUCE_BATCH);
CHECK(decision.batch_size == 4);
snapshot.memory_available_bytes = GIBIBYTES(3);
CHECK(lardon3d_resource_governor_decide(governor, &snapshot, &request, &decision));
CHECK(decision.kind == LARDON3D_RESOURCE_WAIT);
snapshot.memory_available_bytes = GIBIBYTES(10);
snapshot.cpu_load_1m = 15.0;
CHECK(lardon3d_resource_governor_decide(governor, &snapshot, &request, &decision));
CHECK(decision.kind == LARDON3D_RESOURCE_WAIT);
snapshot.cpu_load_1m = 2.0;
snapshot.io_pressure_known = true;
snapshot.io_pressure_avg10 = 90.0;
request.io_intensive = true;
CHECK(lardon3d_resource_governor_decide(governor, &snapshot, &request, &decision));
CHECK(decision.kind == LARDON3D_RESOURCE_WAIT);
request.io_intensive = false;
request.memory_bytes_per_item = GIBIBYTES(8);
CHECK(lardon3d_resource_governor_decide(governor, &snapshot, &request, &decision));
CHECK(decision.kind == LARDON3D_RESOURCE_REJECT);
request.memory_bytes_per_item = GIBIBYTES(1);
request.gpu_memory_bytes_per_item = MEBIBYTES(512);
CHECK(lardon3d_resource_governor_decide(governor, &snapshot, &request, &decision));
CHECK(decision.kind == LARDON3D_RESOURCE_REJECT);
lardon3d_resource_governor_destroy(governor);
profile.gpu_available = true;
profile.gpu_uses_shared_memory = true;
policy.gpu_memory_reserve_bytes = 0;
governor = lardon3d_resource_governor_create(&profile, &policy);
CHECK(governor);
request = (Lardon3DResourceRequest) {
.memory_bytes_per_item = GIBIBYTES(1),
.gpu_memory_bytes_per_item = GIBIBYTES(1),
.minimum_batch_size = 1,
.preferred_batch_size = 4,
.requested_cpu_threads = 4,
};
snapshot.memory_available_bytes = GIBIBYTES(6);
snapshot.cpu_load_1m = 2.0;
CHECK(lardon3d_resource_governor_decide(governor, &snapshot, &request, &decision));
CHECK(decision.kind == LARDON3D_RESOURCE_REDUCE_BATCH);
CHECK(decision.batch_size == 2);
lardon3d_resource_governor_destroy(governor);
profile.gpu_available = true;
profile.gpu_uses_shared_memory = false;
profile.gpu_memory_known = true;
profile.gpu_memory_total_bytes = GIBIBYTES(4);
policy.gpu_memory_reserve_bytes = MEBIBYTES(512);
governor = lardon3d_resource_governor_create(&profile, &policy);
CHECK(governor);
request.minimum_batch_size = 1;
request.preferred_batch_size = 3;
request.gpu_memory_bytes_per_item = GIBIBYTES(1);
snapshot.gpu_memory_available_known = false;
CHECK(lardon3d_resource_governor_decide(governor, &snapshot, &request, &decision));
CHECK(decision.kind == LARDON3D_RESOURCE_WAIT);
snapshot.gpu_memory_available_known = true;
snapshot.gpu_memory_available_bytes = GIBIBYTES(2) + MEBIBYTES(512);
CHECK(lardon3d_resource_governor_decide(governor, &snapshot, &request, &decision));
CHECK(decision.kind == LARDON3D_RESOURCE_REDUCE_BATCH);
CHECK(decision.batch_size == 2);
request.gpu_memory_bytes_per_item = 0;
request.preferred_batch_size = 2;
request.memory_bytes_per_item = GIBIBYTES(1);
atomic_bool failed = false;
ThreadContext context = {
.governor = governor,
.snapshot = snapshot,
.request = request,
.failed = &failed,
};
pthread_t threads[8];
for (size_t index = 0; index < 8; ++index) {
CHECK(pthread_create(&threads[index], NULL, decide_repeatedly, &context) == 0);
}
for (size_t index = 0; index < 8; ++index) {
CHECK(pthread_join(threads[index], NULL) == 0);
}
CHECK(!atomic_load(&failed));
Lardon3DResourcePolicy invalid = policy;
invalid.system_cpu_reserve = profile.logical_cpu_count;
CHECK(!lardon3d_resource_governor_set_policy(governor, &invalid));
CHECK(lardon3d_resource_governor_set_policy(governor, &policy));
CHECK(strcmp(lardon3d_resource_decision_name(LARDON3D_RESOURCE_START), "Démarrer") == 0);
lardon3d_resource_governor_destroy(governor);
lardon3d_resource_governor_destroy(NULL);
return true;
}
int
main(void)
{
return run_test() ? EXIT_SUCCESS : EXIT_FAILURE;
}

View file

@ -0,0 +1,60 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <lardon3d/hardware_profile.h>
#include <lardon3d/resource_snapshot.h>
#define CHECK(condition) \
do { \
if (!(condition)) { \
(void)fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \
return false; \
} \
} while (0)
static bool
run_test(void)
{
char error[256];
Lardon3DResourceSnapshot snapshot;
CHECK(!lardon3d_resource_snapshot_capture(NULL, &snapshot, error, sizeof(error)));
CHECK(error[0]);
Lardon3DHardwareProfile invalid = {0};
CHECK(!lardon3d_resource_snapshot_capture(
&invalid,
&snapshot,
error,
sizeof(error)
));
Lardon3DHardwareProfile profile;
CHECK(lardon3d_hardware_profile_detect(&profile, error, sizeof(error)));
CHECK(lardon3d_resource_snapshot_capture(
&profile,
&snapshot,
error,
sizeof(error)
));
CHECK(error[0] == '\0');
CHECK(snapshot.captured_at.tv_sec > 0);
CHECK(snapshot.memory_available_bytes > 0);
CHECK(snapshot.memory_free_bytes <= profile.memory_total_bytes);
CHECK(snapshot.cpu_load_1m >= 0.0);
CHECK(snapshot.cpu_load_5m >= 0.0);
CHECK(snapshot.cpu_load_15m >= 0.0);
if (snapshot.gpu_memory_available_known && profile.gpu_memory_known) {
CHECK(snapshot.gpu_memory_available_bytes <= profile.gpu_memory_total_bytes);
}
if (snapshot.io_pressure_known) {
CHECK(snapshot.io_pressure_avg10 >= 0.0);
CHECK(snapshot.io_pressure_avg10 <= 100.0);
}
return true;
}
int
main(void)
{
return run_test() ? EXIT_SUCCESS : EXIT_FAILURE;
}