feat: add resource reservation engine
This commit is contained in:
parent
030994696b
commit
0290331c60
11 changed files with 1152 additions and 0 deletions
|
|
@ -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.
|
||||||
|
|
|
||||||
30
include/lardon3d/hardware_profile.h
Normal file
30
include/lardon3d/hardware_profile.h
Normal 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
|
||||||
73
include/lardon3d/resource_governor.h
Normal file
73
include/lardon3d/resource_governor.h
Normal 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
|
||||||
32
include/lardon3d/resource_snapshot.h
Normal file
32
include/lardon3d/resource_snapshot.h
Normal 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
|
||||||
38
meson.build
38
meson.build
|
|
@ -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
182
src/hardware_profile.c
Normal 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
288
src/resource_governor.c
Normal 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
192
src/resource_snapshot.c
Normal 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;
|
||||||
|
}
|
||||||
47
tests/test_hardware_profile.c
Normal file
47
tests/test_hardware_profile.c
Normal 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;
|
||||||
|
}
|
||||||
205
tests/test_resource_governor.c
Normal file
205
tests/test_resource_governor.c
Normal 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;
|
||||||
|
}
|
||||||
60
tests/test_resource_snapshot.c
Normal file
60
tests/test_resource_snapshot.c
Normal 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;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue