lardon3d/tests/test_resource_reservation.c
2026-08-06 21:05:02 +02:00

499 lines
15 KiB
C

#include <pthread.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.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)
enum {
THREAD_COUNT = 16,
RESERVATIONS_PER_THREAD = 25,
RESERVATION_COUNT = THREAD_COUNT * RESERVATIONS_PER_THREAD,
};
typedef struct {
Lardon3DResourceGovernor *governor;
Lardon3DResourceSnapshot snapshot;
Lardon3DResourceEstimate estimate;
Lardon3DResourceReservation **reservations;
size_t first;
pthread_barrier_t *created;
pthread_barrier_t *release;
atomic_bool *failed;
} ReservationThread;
static Lardon3DResourcePolicy
test_policy(unsigned int io_slots)
{
return (Lardon3DResourcePolicy) {
.system_memory_reserve_bytes = 0,
.gpu_memory_reserve_bytes = 0,
.system_cpu_reserve = 0,
.maximum_cpu_load_ratio = 1.0,
.maximum_io_pressure_avg10 = 100.0,
.gpu_slot_capacity = 0,
.io_slot_capacity = io_slots,
};
}
static void *
reserve_concurrently(void *argument)
{
ReservationThread *thread = argument;
for (size_t offset = 0; offset < RESERVATIONS_PER_THREAD; ++offset) {
Lardon3DResourceDecision decision;
size_t index = thread->first + offset;
if (!lardon3d_resource_governor_reserve(
thread->governor,
&thread->snapshot,
&thread->estimate,
&decision,
&thread->reservations[index]
)
|| !thread->reservations[index]
|| decision.kind != LARDON3D_RESOURCE_START) {
atomic_store(thread->failed, true);
}
}
(void)pthread_barrier_wait(thread->created);
(void)pthread_barrier_wait(thread->release);
for (size_t offset = 0; offset < RESERVATIONS_PER_THREAD; ++offset) {
Lardon3DResourceReservation *reservation =
thread->reservations[thread->first + offset];
if (!reservation
|| !lardon3d_resource_governor_release(
thread->governor,
reservation
)
|| lardon3d_resource_governor_release(
thread->governor,
reservation
)) {
atomic_store(thread->failed, true);
}
}
return NULL;
}
static bool
test_adaptive_batches(void)
{
Lardon3DHardwareProfile profile = {
.logical_cpu_count = 64,
.page_size_bytes = 4096,
.memory_total_bytes = GIBIBYTES(64),
.cpu_architecture = "test",
};
Lardon3DResourcePolicy policy = test_policy(64);
Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create(
&profile,
&policy
);
CHECK(governor);
Lardon3DResourceSnapshot snapshot = {
.memory_available_bytes = GIBIBYTES(37),
.cpu_load_1m = 0.0,
};
Lardon3DResourceEstimate estimate = {
.memory_bytes_per_item = GIBIBYTES(1),
.minimum_batch_size = 1,
.maximum_batch_size = 128,
.desired_cpu_threads = 8,
.desired_io_slots = 2,
.task_class = LARDON3D_RESOURCE_TASK_MIXED,
};
Lardon3DResourceDecision decision;
Lardon3DResourceReservation *reservation;
CHECK(lardon3d_resource_governor_reserve(
governor,
&snapshot,
&estimate,
&decision,
&reservation
));
CHECK(reservation);
CHECK(decision.kind == LARDON3D_RESOURCE_REDUCE_BATCH);
CHECK(decision.batch_size == 37);
CHECK(lardon3d_resource_governor_reservation_is_valid(
governor,
reservation
));
Lardon3DResourceReservationInfo information;
CHECK(lardon3d_resource_reservation_get(
governor,
reservation,
&information
));
CHECK(information.id == 1);
CHECK(information.memory_bytes == GIBIBYTES(37));
CHECK(information.batch_size == 37);
CHECK(information.cpu_threads == 8);
CHECK(information.io_slots == 2);
CHECK(information.state == LARDON3D_RESERVATION_ACTIVE);
CHECK(information.created_at.tv_sec > 0);
CHECK(lardon3d_resource_governor_reservation_count(governor) == 1);
Lardon3DResourceAvailability availability;
CHECK(lardon3d_resource_governor_availability(
governor,
&snapshot,
&availability
));
CHECK(availability.memory_reserved_bytes == GIBIBYTES(37));
CHECK(availability.memory_available_bytes == 0);
CHECK(availability.cpu_reserved == 8);
CHECK(availability.io_slots_reserved == 2);
CHECK(availability.active_reservations == 1);
Lardon3DResourcePolicy reduced_policy = policy;
reduced_policy.system_memory_reserve_bytes = GIBIBYTES(28);
CHECK(!lardon3d_resource_governor_set_policy(
governor,
&reduced_policy
));
Lardon3DResourceReservation *second = NULL;
CHECK(lardon3d_resource_governor_reserve(
governor,
&snapshot,
&estimate,
&decision,
&second
));
CHECK(!second);
CHECK(decision.kind == LARDON3D_RESOURCE_WAIT);
CHECK(lardon3d_resource_governor_release(governor, reservation));
CHECK(!lardon3d_resource_governor_release(governor, reservation));
CHECK(!lardon3d_resource_governor_reservation_is_valid(
governor,
reservation
));
CHECK(lardon3d_resource_reservation_get(
governor,
reservation,
&information
));
CHECK(information.state == LARDON3D_RESERVATION_RELEASED);
CHECK(lardon3d_resource_governor_reservation_count(governor) == 0);
CHECK(lardon3d_resource_governor_availability(
governor,
&snapshot,
&availability
));
CHECK(availability.memory_available_bytes == GIBIBYTES(37));
CHECK(lardon3d_resource_governor_set_policy(
governor,
&reduced_policy
));
estimate = (Lardon3DResourceEstimate) {
.memory_fixed_bytes = GIBIBYTES(65),
.minimum_batch_size = 1,
.maximum_batch_size = 1,
.desired_cpu_threads = 1,
.task_class = LARDON3D_RESOURCE_TASK_GENERAL,
};
CHECK(lardon3d_resource_governor_reserve(
governor,
&snapshot,
&estimate,
&decision,
&second
));
CHECK(!second);
CHECK(decision.kind == LARDON3D_RESOURCE_REJECT);
lardon3d_resource_governor_destroy(governor);
return true;
}
static bool
test_multiple_reservations(void)
{
Lardon3DHardwareProfile profile = {
.logical_cpu_count = 16,
.page_size_bytes = 4096,
.memory_total_bytes = GIBIBYTES(16),
.cpu_architecture = "test",
};
Lardon3DResourcePolicy policy = test_policy(16);
Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create(
&profile,
&policy
);
CHECK(governor);
Lardon3DResourceSnapshot snapshot = {
.memory_available_bytes = GIBIBYTES(8),
.cpu_load_1m = 0.0,
};
Lardon3DResourceEstimate fixed = {
.memory_fixed_bytes = GIBIBYTES(3),
.minimum_batch_size = 1,
.maximum_batch_size = 1,
.desired_cpu_threads = 2,
.desired_io_slots = 1,
.task_class = LARDON3D_RESOURCE_TASK_IO,
};
Lardon3DResourceEstimate variable = {
.memory_bytes_per_item = GIBIBYTES(1),
.minimum_batch_size = 1,
.maximum_batch_size = 6,
.desired_cpu_threads = 2,
.desired_io_slots = 1,
.task_class = LARDON3D_RESOURCE_TASK_IO,
};
Lardon3DResourceDecision decision;
Lardon3DResourceReservation *first;
Lardon3DResourceReservation *second;
CHECK(lardon3d_resource_governor_reserve(
governor,
&snapshot,
&fixed,
&decision,
&first
));
CHECK(first && decision.kind == LARDON3D_RESOURCE_START);
CHECK(lardon3d_resource_governor_reserve(
governor,
&snapshot,
&variable,
&decision,
&second
));
CHECK(second && decision.kind == LARDON3D_RESOURCE_REDUCE_BATCH);
CHECK(decision.batch_size == 5);
CHECK(lardon3d_resource_governor_reservation_count(governor) == 2);
Lardon3DResourceReservationInfo listed[2];
CHECK(lardon3d_resource_governor_list_reservations(
governor,
listed,
2
) == 2);
CHECK(listed[0].id != listed[1].id);
CHECK(lardon3d_resource_governor_release(governor, first));
CHECK(lardon3d_resource_governor_release(governor, second));
lardon3d_resource_governor_destroy(governor);
return true;
}
static bool
test_gpu_reservation(void)
{
Lardon3DHardwareProfile profile = {
.logical_cpu_count = 16,
.page_size_bytes = 4096,
.memory_total_bytes = GIBIBYTES(16),
.gpu_available = true,
.gpu_memory_known = true,
.gpu_memory_total_bytes = GIBIBYTES(4),
.cpu_architecture = "test",
};
Lardon3DResourcePolicy policy = test_policy(4);
policy.gpu_memory_reserve_bytes = MEBIBYTES(512);
policy.gpu_slot_capacity = 2;
Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create(
&profile,
&policy
);
Lardon3DResourceGovernor *other = lardon3d_resource_governor_create(
&profile,
&policy
);
CHECK(governor && other);
Lardon3DResourceSnapshot snapshot = {
.memory_available_bytes = GIBIBYTES(16),
.gpu_memory_available_known = true,
.gpu_memory_available_bytes = GIBIBYTES(3) + MEBIBYTES(512),
.cpu_load_1m = 0.0,
};
Lardon3DResourceEstimate estimate = {
.gpu_memory_fixed_bytes = MEBIBYTES(512),
.gpu_memory_bytes_per_item = MEBIBYTES(512),
.minimum_batch_size = 1,
.maximum_batch_size = 6,
.desired_cpu_threads = 2,
.desired_gpu_slots = 1,
.task_class = LARDON3D_RESOURCE_TASK_GPU,
};
Lardon3DResourceDecision decision;
Lardon3DResourceReservation *reservation;
CHECK(lardon3d_resource_governor_reserve(
governor,
&snapshot,
&estimate,
&decision,
&reservation
));
CHECK(reservation && decision.kind == LARDON3D_RESOURCE_REDUCE_BATCH);
CHECK(decision.batch_size == 5);
Lardon3DResourceReservationInfo information;
CHECK(lardon3d_resource_reservation_get(
governor,
reservation,
&information
));
CHECK(information.gpu_memory_bytes == GIBIBYTES(3));
CHECK(information.gpu_slots == 1);
Lardon3DResourceAvailability availability;
CHECK(lardon3d_resource_governor_availability(
governor,
&snapshot,
&availability
));
CHECK(availability.gpu_memory_reserved_bytes == GIBIBYTES(3));
CHECK(availability.gpu_memory_available_bytes == 0);
CHECK(availability.gpu_slots_available == 1);
CHECK(!lardon3d_resource_governor_reservation_is_valid(
other,
reservation
));
CHECK(!lardon3d_resource_governor_release(other, reservation));
CHECK(lardon3d_resource_governor_release(governor, reservation));
lardon3d_resource_governor_destroy(other);
lardon3d_resource_governor_destroy(governor);
return true;
}
static bool
test_concurrency(void)
{
Lardon3DHardwareProfile profile = {
.logical_cpu_count = 512,
.page_size_bytes = 4096,
.memory_total_bytes = GIBIBYTES(4),
.cpu_architecture = "test",
};
Lardon3DResourcePolicy policy = test_policy(512);
Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create(
&profile,
&policy
);
CHECK(governor);
Lardon3DResourceReservation *reservations[RESERVATION_COUNT] = {0};
pthread_barrier_t created;
pthread_barrier_t release;
CHECK(pthread_barrier_init(&created, NULL, THREAD_COUNT + 1) == 0);
CHECK(pthread_barrier_init(&release, NULL, THREAD_COUNT + 1) == 0);
atomic_bool failed = false;
ReservationThread contexts[THREAD_COUNT];
pthread_t threads[THREAD_COUNT];
for (size_t index = 0; index < THREAD_COUNT; ++index) {
contexts[index] = (ReservationThread) {
.governor = governor,
.snapshot = {
.memory_available_bytes = GIBIBYTES(4),
.cpu_load_1m = 0.0,
},
.estimate = {
.memory_fixed_bytes = MEBIBYTES(1),
.minimum_batch_size = 1,
.maximum_batch_size = 1,
.desired_cpu_threads = 1,
.desired_io_slots = 1,
.task_class = LARDON3D_RESOURCE_TASK_GENERAL,
},
.reservations = reservations,
.first = index * RESERVATIONS_PER_THREAD,
.created = &created,
.release = &release,
.failed = &failed,
};
CHECK(pthread_create(
&threads[index],
NULL,
reserve_concurrently,
&contexts[index]
) == 0);
}
(void)pthread_barrier_wait(&created);
CHECK(!atomic_load(&failed));
CHECK(lardon3d_resource_governor_reservation_count(governor)
== RESERVATION_COUNT);
Lardon3DResourceAvailability availability;
CHECK(lardon3d_resource_governor_availability(
governor,
&contexts[0].snapshot,
&availability
));
CHECK(availability.memory_reserved_bytes
== MEBIBYTES(RESERVATION_COUNT));
CHECK(availability.cpu_reserved == RESERVATION_COUNT);
CHECK(availability.io_slots_reserved == RESERVATION_COUNT);
(void)pthread_barrier_wait(&release);
for (size_t index = 0; index < THREAD_COUNT; ++index) {
CHECK(pthread_join(threads[index], NULL) == 0);
}
CHECK(!atomic_load(&failed));
CHECK(lardon3d_resource_governor_reservation_count(governor) == 0);
CHECK(pthread_barrier_destroy(&created) == 0);
CHECK(pthread_barrier_destroy(&release) == 0);
lardon3d_resource_governor_destroy(governor);
return true;
}
static bool
test_destroy_with_active_reservations(void)
{
Lardon3DHardwareProfile profile = {
.logical_cpu_count = 4,
.page_size_bytes = 4096,
.memory_total_bytes = GIBIBYTES(4),
.cpu_architecture = "test",
};
Lardon3DResourcePolicy policy = test_policy(4);
Lardon3DResourceGovernor *governor = lardon3d_resource_governor_create(
&profile,
&policy
);
CHECK(governor);
Lardon3DResourceSnapshot snapshot = {
.memory_available_bytes = GIBIBYTES(4),
.cpu_load_1m = 0.0,
};
Lardon3DResourceEstimate estimate = {
.memory_fixed_bytes = MEBIBYTES(1),
.minimum_batch_size = 1,
.maximum_batch_size = 1,
.desired_cpu_threads = 1,
.task_class = LARDON3D_RESOURCE_TASK_GENERAL,
};
Lardon3DResourceDecision decision;
Lardon3DResourceReservation *reservation;
CHECK(lardon3d_resource_governor_reserve(
governor,
&snapshot,
&estimate,
&decision,
&reservation
));
CHECK(reservation);
lardon3d_resource_governor_destroy(governor);
return true;
}
static bool
run_test(void)
{
CHECK(test_adaptive_batches());
CHECK(test_multiple_reservations());
CHECK(test_gpu_reservation());
CHECK(test_concurrency());
CHECK(test_destroy_with_active_reservations());
return true;
}
int
main(void)
{
return run_test() ? EXIT_SUCCESS : EXIT_FAILURE;
}