On 23/09/2026 21:44, Thadeu Lima de Souza Cascardo wrote:
Based on the work of Thomas Hellström to test dmem.max eviction, add a
test for dmem.current usage after allocations and setting dmem.max.
Isn't 7/9 testing current after setting max already? I went back and
forth a bit and isn't the new thing in this test is looking at current
after freeing objects? Unless I got confused maybe then rename the test
and update the commit message. One more comment below.
Create a dmem cgroup, allocate close to capacity (or at most 4GiB),
check current usage is within a small slack of the expected allocation.
Then, set max to a small value and check allocations and current usage
are limited to the max set. Set max to less than a single BO size, then
check no allocations are allowed and current usage is also within the
slack. After each allocation, release memory and check current usage
has gone down.
Signed-off-by: Thadeu Lima de Souza Cascardo <[email protected]>
---
tests/cgroup_dmem.c | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 147 insertions(+)
diff --git a/tests/cgroup_dmem.c b/tests/cgroup_dmem.c
index 7f3cfe22db2c..1f01037e1cf2 100644
--- a/tests/cgroup_dmem.c
+++ b/tests/cgroup_dmem.c
@@ -36,6 +36,8 @@
#define BO_SIZE SZ_64M
#define MAX_LIMIT ((uint64_t)4 * SZ_1G)
#define EVICT_STEP SZ_128M
+#define USAGE_POLL_MS 10
+#define USAGE_DROP_TIMEOUT_MS 1000
#define TEST_INTERRUPTIBLE (1 << 0)
#define TEST_NONBLOCK (1 << 1)
@@ -48,6 +50,20 @@
* and configured limits, then destroys the cgroup.
*/
+/**
+ * SUBTEST: current
+ * DESCRIPTION:
+ * Create a dmem cgroup, allocate close to capacity (or at most 4GiB),
+ * check current usage is within a small slack of the expected allocation.
+ * Then, set max to a small value and check allocations and current usage
+ * are limited to the max set.
+ * Set max to less than a single BO size, then check no allocations are
allowed
+ * and current usage is also within the slack.
+ * After each allocation, release memory and check current usage has gone
+ * down.
+ * This subtest requires GPU device with at least one VRAM region.
+ */
+
/**
* SUBTEST: write_eviction
* DESCRIPTION:
@@ -116,6 +132,23 @@ static void install_sigcont_counter(void)
sigaction(SIGCONT, &sa, &sigcont_oldact);
}
+static uint64_t wait_for_usage_drop(struct igt_cgroup *cg, const char *region,
+ uint64_t limit)
+{
+ uint64_t current;
+ unsigned int elapsed = 0;
+
+ do {
+ igt_cgroup_dmem_get_current(cg, region, ¤t);
+ if (current <= limit)
+ return current;
+ usleep(USAGE_POLL_MS * 1000);
+ elapsed += USAGE_POLL_MS;
+ } while (elapsed < USAGE_DROP_TIMEOUT_MS);
+
+ return current;
+}
+
static int allocate_vram(void **handles, const struct igt_dmem_driver *drv,
void *ctx, int max_bo, size_t len)
{
@@ -137,6 +170,119 @@ static int allocate_vram(void **handles, const struct
igt_dmem_driver *drv,
return err;
}
+static void free_vram(void **handles, const struct igt_dmem_driver *drv,
+ void *ctx, int max_bo)
+{
+ int i;
+ for (i = 0; i < max_bo; i++)
+ drv->free_vram(ctx, handles[i]);
+}
+
+static void test_current(int fd, char *cg_region, unsigned int flags, const
struct igt_dmem_driver *drv, void *ctx)
+{
+ struct igt_cgroup *cg;
+ void **handles;
+ uint64_t current, capacity, cg_max;
+ int n_bo = 0, max_bo;
+
+ igt_cgroup_dmem_get_capacity(cg_region, &capacity);
+ igt_require_f(capacity >= 4 * BO_SIZE,
+ "VRAM capacity (%"PRIu64" MiB) too small to test\n",
+ capacity / SZ_1M);
+
+ /*
+ * Use up to 4 GiB, or the full capacity if the device has less.
+ * Leave one BO_SIZE worth of headroom so the device isn't completely
+ * exhausted before the cgroup limit is hit.
+ */
+ cg_max = min(MAX_LIMIT, capacity - BO_SIZE);
+ cg_max = ALIGN_DOWN(cg_max, BO_SIZE);
+
+ /* Create cgroup and move into it */
+ cg = igt_cgroup_new("igt_cgroups_test");
+ igt_cgroup_move_current(cg);
+
+ max_bo = cg_max / BO_SIZE;
+
+ handles = calloc(max_bo, sizeof(handles[0]));
+ igt_assert_f(handles, "failed to allocate handles array");
+
+ n_bo = allocate_vram(handles, drv, ctx, max_bo, BO_SIZE);
+ igt_assert_f(n_bo > 0, "failed to allocate VRAM\n");
+
+ igt_cgroup_dmem_get_current(cg, cg_region, ¤t);
+ igt_debug("After fill: cgroup current = %"PRIu64" MiB, "
+ "max = %"PRIu64" MiB\n",
+ current / SZ_1M, cg_max / SZ_1M);
+ igt_assert_f(current == cg_max,
+ "current usage (%"PRIu64" MiB) is not requested allocation
(%"PRIu64" MiB)\n",
+ current / SZ_1M, cg_max / SZ_1M);
+
+ free_vram(handles, drv, ctx, n_bo);
+ wait_for_usage_drop(cg, cg_region, 0);
The fact in there is a sleeping loop makes me think dmem cgroup contract
is not for current to go down immediate when bos are freed from
userspace, but when the driver feels like it?
In which case, to ask an annoying question, does it ever have to go down
(in a generic case) if no one is touching neither the fd nor the device.
Or maybe I am misunderstanding something.
Regards,
Tvrtko
+
+ igt_cgroup_dmem_get_current(cg, cg_region, ¤t);
+ igt_debug("After free: cgroup current = %"PRIu64" MiB, "
+ "max = %"PRIu64" MiB\n",
+ current / SZ_1M, cg_max / SZ_1M);
+ igt_assert_f(current == 0,
+ "current usage (%"PRIu64" MiB) is not zero\n",
+ current / SZ_1M);
+
+ igt_cgroup_dmem_set_max(cg, cg_region, 2 * BO_SIZE, false);
+
+ n_bo = allocate_vram(handles, drv, ctx, max_bo, BO_SIZE);
+ igt_assert_f(n_bo > 0, "failed to allocate VRAM\n");
+
+ igt_cgroup_dmem_get_current(cg, cg_region, ¤t);
+ igt_debug("After fill: cgroup current = %"PRIu64" MiB, "
+ "max = %"PRIu64" MiB\n",
+ current / SZ_1M, cg_max / SZ_1M);
+ igt_assert_f(current == 2 * BO_SIZE,
+ "current usage (%"PRIu64" MiB) is not requested max allocation
(%"PRIu64" MiB)\n",
+ current / SZ_1M, cg_max / SZ_1M);
+
+ free_vram(handles, drv, ctx, n_bo);
+ wait_for_usage_drop(cg, cg_region, 0);
+
+ igt_cgroup_dmem_get_current(cg, cg_region, ¤t);
+ igt_debug("After free: cgroup current = %"PRIu64" MiB, "
+ "max = %"PRIu64" MiB\n",
+ current / SZ_1M, cg_max / SZ_1M);
+ igt_assert_f(current == 0,
+ "current usage (%"PRIu64" MiB) is not zero\n",
+ current / SZ_1M);
+
+ igt_cgroup_dmem_set_max(cg, cg_region, 0, false);
+
+ n_bo = allocate_vram(handles, drv, ctx, max_bo, BO_SIZE);
+
+ /*
+ * amdgpu may succeed the allocation, by falling back to GTT, so no
assertion here.
+ * Verify by reading current usage.
+ */
+
+ igt_cgroup_dmem_get_current(cg, cg_region, ¤t);
+ igt_debug("After fill: cgroup current = %"PRIu64" MiB, "
+ "max = %"PRIu64" MiB\n",
+ current / SZ_1M, cg_max / SZ_1M);
+ igt_assert_f(current == 0,
+ "current usage (%"PRIu64" MiB) is not zero\n",
+ current / SZ_1M);
+
+ if (n_bo > 0)
+ free_vram(handles, drv, ctx, n_bo);
+ wait_for_usage_drop(cg, cg_region, 0);
+
+ igt_cgroup_dmem_get_current(cg, cg_region, ¤t);
+ igt_debug("After free: cgroup current = %"PRIu64" MiB, "
+ "max = %"PRIu64" MiB\n",
+ current / SZ_1M, cg_max / SZ_1M);
+ igt_assert_f(current == 0,
+ "current usage (%"PRIu64" MiB) is not zero\n",
+ current / SZ_1M);
+}
+
static void test_write_eviction(int fd, char *cg_region, unsigned int flags,
const struct igt_dmem_driver *drv, void *ctx)
{
struct igt_cgroup *cg;
@@ -246,6 +392,7 @@ static const struct {
void (*test_fn)(int fd, char *cg_region, unsigned int flags, const
struct igt_dmem_driver *drv, void *ctx);
unsigned int flags;
} subtests[] = {
+ { "current", test_current, 0 },
{ "write_eviction", test_write_eviction, 0 },
{ "write_eviction_interruptible", test_write_eviction,
TEST_INTERRUPTIBLE },
{ "write_eviction_nonblock", test_write_eviction, TEST_NONBLOCK },