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
|
||||
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.
|
||||
|
||||
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/task.c',
|
||||
'src/task_queue.c',
|
||||
'src/hardware_profile.c',
|
||||
'src/resource_snapshot.c',
|
||||
'src/resource_governor.c',
|
||||
],
|
||||
include_directories: include_directories('include'),
|
||||
dependencies: [ncursesw, threads],
|
||||
|
|
@ -116,3 +119,38 @@ task_queue_test = executable(
|
|||
)
|
||||
|
||||
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