From: Tvrtko Ursulin <tvrtko.ursulin@xxxxxxxxx> A set of subtests which exercises different paths to our shrinker code (including the OOM killer) in predictable and reasonable time budget. v2: * Fix blacklist regexp. (Petri Latvala) v3: * Skip userptr test if not userptr support. Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@xxxxxxxxx> --- lib/igt_core.c | 19 ++ lib/igt_core.h | 1 + tests/i915/gem_shrink.c | 401 ++++++++++++++++++++++++++ tests/intel-ci/blacklist.txt | 2 +- tests/intel-ci/fast-feedback.testlist | 3 + 5 files changed, 425 insertions(+), 1 deletion(-) diff --git a/lib/igt_core.c b/lib/igt_core.c index 50d6008f6c82..351da0b4e020 100644 --- a/lib/igt_core.c +++ b/lib/igt_core.c @@ -1685,6 +1685,25 @@ void igt_stop_helper(struct igt_helper_process *proc) assert(helper_was_alive(proc, status)); } +/** + * igt_try_stop_helper: + * @proc: #igt_helper_process structure + * + * Terminates a helper process if it is still running and returns true, or false + * if the process wasn't running. + */ +bool igt_try_stop_helper(struct igt_helper_process *proc) +{ + int status; + + /* failure here means the pid is already dead and so waiting is safe */ + kill(proc->pid, proc->use_SIGKILL ? SIGKILL : SIGTERM); + + status = igt_wait_helper(proc); + + return helper_was_alive(proc, status); +} + static void children_exit_handler(int sig) { int status; diff --git a/lib/igt_core.h b/lib/igt_core.h index 6f8c3852a686..ed5ceebf1205 100644 --- a/lib/igt_core.h +++ b/lib/igt_core.h @@ -795,6 +795,7 @@ bool __igt_fork_helper(struct igt_helper_process *proc); for (; __igt_fork_helper(proc); exit(0)) int igt_wait_helper(struct igt_helper_process *proc); void igt_stop_helper(struct igt_helper_process *proc); +bool igt_try_stop_helper(struct igt_helper_process *proc); /* exit handler code */ diff --git a/tests/i915/gem_shrink.c b/tests/i915/gem_shrink.c index c8e05814ee70..3d48cbebc2f9 100644 --- a/tests/i915/gem_shrink.c +++ b/tests/i915/gem_shrink.c @@ -26,6 +26,10 @@ * * Exercise the shrinker by overallocating GEM objects */ +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <fcntl.h> #include "igt.h" #include "igt_gt.h" @@ -366,6 +370,378 @@ static void reclaim(unsigned engine, int timeout) close(fd); } +static unsigned long get_meminfo(const char *info, const char *tag) +{ + const char *str; + unsigned long val; + + str = strstr(info, tag); + if (str && sscanf(str + strlen(tag), " %lu", &val) == 1) + return val >> 10; + + igt_warn("Unrecognised /proc/meminfo field: '%s'\n", tag); + return 0; +} + +static unsigned long get_avail_ram_mb(void) +{ + int fd; + int ret; + char buf[4096]; + unsigned long ram; + + fd = open("/proc/meminfo", O_RDONLY); + igt_assert_fd(fd); + + ret = read(fd, buf, sizeof(buf)); + igt_assert(ret >= 0); + + close(fd); + + ram = get_meminfo(buf, "MemAvailable:"); + ram += get_meminfo(buf, "Buffers:"); + ram += get_meminfo(buf, "Cached:"); + ram += get_meminfo(buf, "SwapCached:"); + + return ram; +} + +struct test { +#define TEST_BO (1) +#define TEST_USERPTR (2) + unsigned int flags; + int fd; +}; + +static uint32_t __get_pages(int fd, unsigned long alloc) +{ + uint32_t handle = gem_create(fd, alloc); + + gem_set_domain(fd, handle, I915_GEM_DOMAIN_GTT, 0); + gem_madvise(fd, handle, I915_MADV_DONTNEED); + + return handle; +} + +struct test_obj { + void *ptr; + uint32_t handle; +}; + +#define PAGE_SIZE 4096 +static void +__get_userptr(int fd, struct test_obj *obj, unsigned long sz) +{ + struct local_i915_gem_userptr userptr = { }; + void *ptr; + + igt_assert_eq(sz & 4095, 0); + + ptr = mmap(NULL, sz, PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + assert(ptr != MAP_FAILED); + + for (size_t page = 0; page < sz; page += PAGE_SIZE) + *(volatile uint32_t *)((unsigned char *)ptr + page) = 0; + + userptr.user_size = sz; + userptr.user_ptr = to_user_pointer(ptr); + do_ioctl(fd, LOCAL_IOCTL_I915_GEM_USERPTR, &userptr); + + gem_set_domain(fd, userptr.handle, I915_GEM_DOMAIN_GTT, 0); + gem_madvise(fd, userptr.handle, I915_MADV_DONTNEED); + + obj->ptr = ptr; + obj->handle = userptr.handle; +} + +/* + * Use a specific way of using up memory until we are below a certain threshold. + */ +static void *mempressure(void *arg) +{ + const unsigned int free_threshold_mb = 256; + struct test_obj *list = NULL; + struct test *test = arg; + const unsigned int sz_mb = 2; + const unsigned int sz = sz_mb << 20; + unsigned int n = 0, max = 0; + unsigned int blocks; + + for (;;) { + unsigned long ram_mb = get_avail_ram_mb(); + + if (!list) { + /* + * On first pass estimate how many sz_mb sized blocks + * we could need to use up all RAM. + */ + blocks = ram_mb / sz_mb; + list = calloc(blocks, sizeof(*list)); + igt_assert(list); + } else if (ram_mb < free_threshold_mb) { + /* Limit the working set once under the threshold. */ + blocks = max + 1; + } + + /* Free the oldest block once the working set wrapped. */ + if (list[n].ptr || list[n].handle) { + if (test->flags & TEST_USERPTR) { + munmap(list[n].ptr, sz); + gem_close(test->fd, list[n].handle); + } else if (test->flags & TEST_BO) { + gem_close(test->fd, list[n].handle); + } else { + munmap(list[n].ptr, sz); + } + } + + /* + * Allocate memory blocks and record the current working set + * size. + */ + if (test->flags & TEST_BO) { + list[n].handle = __get_pages(test->fd, sz); + } else if (test->flags & TEST_USERPTR) { + __get_userptr(test->fd, &list[n], sz); + } else { + list[n].ptr = mmap(NULL, sz, PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + assert(list[n].ptr != MAP_FAILED); + + madvise(list[n].ptr, sz, MADV_HUGEPAGE); + + for (size_t page = 0; page < sz; page += PAGE_SIZE) + *(volatile uint32_t *)((unsigned char *)list[n].ptr + page) = 0; + } + + if (n > max) + max = n; + + n++; + + /* + * Start freeing the oldest and reallocating once maximum + * working set size has been reached. + */ + if (n >= blocks) + n = 0; + } + + return NULL; +} + +static void oom_adjust(const char *score) +{ + int fd; + + fd = open("/proc/self/oom_score_adj", O_WRONLY); + igt_assert_fd(fd); + igt_assert(write(fd, score, sizeof(score)) == sizeof(score)); + close(fd); +} + +static void trigger_oom(void) +{ + const char *cmd = "f"; + int fd; + + fd = open("/proc/sysrq-trigger", O_WRONLY); + igt_assert_fd(fd); + igt_assert(write(fd, cmd, sizeof(cmd)) == sizeof(cmd)); + close(fd); +} + +static bool has_sysrq_trigger(void) +{ + int fd; + + fd = open("/proc/sysrq-trigger", O_WRONLY); + close(fd); + + return fd >= 0; +} + +/* + * Exercise different paths to our shrinker code, including OOM, in predictable + * and reasonable time budget. + */ +static void reclaim_oom(unsigned int flags, unsigned int passes) +{ + unsigned int count = 0; + + igt_require(!(flags & TEST_USERPTR) || has_userptr()); + + oom_adjust("-1000"); + + do { + struct igt_helper_process gem_child = { .use_SIGKILL = true }; + struct igt_helper_process mem_child = { .use_SIGKILL = true }; + struct igt_helper_process eb_child = { .use_SIGKILL = true }; + struct igt_helper_process drop_child = { .use_SIGKILL = true }; + + igt_debug("Iteration %u...\n", ++count); + + /* + * Apply either anon backed or shmem backed memory pressure + * to the amount to use up almost all available RAM. + */ + igt_fork_helper(&mem_child) { + struct test test = { }; + + if ((flags & (TEST_BO | TEST_USERPTR)) == + (TEST_BO | TEST_USERPTR)) + test.flags = TEST_BO; + + /* Sacrifice the memory hog if it comes to that. */ + oom_adjust("500"); + + if (test.flags == TEST_BO) { + test.fd = drm_open_driver_render(DRIVER_INTEL); + igt_require_gem(test.fd); + } + + mempressure(&test); + + if (test.flags == TEST_BO) + close(test.fd); + } + + /* + * Apply either userptr backed or shmem backed memory pressure + * to the amount to use up almost all available RAM. + */ + igt_fork_helper(&gem_child) { + struct test test = { .flags = flags }; + + if ((flags & (TEST_BO | TEST_USERPTR)) == + (TEST_BO | TEST_USERPTR)) + test.flags = TEST_USERPTR; + + /* Sacrifice the memory hog if it comes to that. */ + oom_adjust("500"); + + test.fd = drm_open_driver_render(DRIVER_INTEL); + igt_require_gem(test.fd); + + mempressure(&test); + + close(test.fd); + } + + /* + * Apply an execbuf load to exercise the request allocation and + * direct reclaim from this path. + * + * Occasionaly sync with execution and pause for a little bit to + * avoid hogging to much from this client. + */ + igt_fork_helper(&eb_child) { + struct test test = { .flags = flags }; + const uint32_t bbe = MI_BATCH_BUFFER_END; + struct drm_i915_gem_exec_object2 obj = { }; + struct drm_i915_gem_execbuffer2 execbuf = { }; + + execbuf.buffers_ptr = to_user_pointer(&obj); + execbuf.buffer_count = 1; + + test.fd = drm_open_driver_render(DRIVER_INTEL); + igt_require_gem(test.fd); + + for (;;) { + unsigned long eb = 0; + struct timespec ts = { }; + unsigned long start; + + igt_nsec_elapsed(&ts); + start = igt_nsec_elapsed(&ts) / 1e6; + + for (;;) { + unsigned long now; + + obj.handle = gem_create(test.fd, 4096); + gem_write(test.fd, obj.handle, 0, &bbe, + sizeof(bbe)); + gem_execbuf(test.fd, &execbuf); + eb++; + now = igt_nsec_elapsed(&ts) / 1e6; + if (now > (start + 1000)) { + gem_sync(test.fd, obj.handle); + if (now > (start + 2000)) { + gem_close(test.fd, + obj.handle); + break; + } + } + gem_close(test.fd, obj.handle); + } + + igt_debug("%lu execbufs\n", eb); + usleep(500e3); + } + + close(test.fd); + } + + /* + * Manually drop cached with the DROP_ACTIVE flag set every now + * and then in order to exercise this path as well. + */ + igt_fork_helper(&drop_child) { + int fd; + + fd = drm_open_driver(DRIVER_INTEL); + igt_require_gem(fd); + + for (;;) { + usleep(334e3); + igt_debug("Dropping caches...\n"); + igt_drop_caches_set(fd, DROP_ACTIVE); + } + + close(fd); + } + + /* + * When memory pressure clients have managed to use up all + * available RAM, let them run for a brief moment yet and then + * manually trigger the OOM condition. + */ + for (unsigned long ram_mb = 0; + (ram_mb = get_avail_ram_mb()) > 512;) { + int status; + pid_t pid; + + igt_debug("[%u] %lu MiB free\n", count, ram_mb); + + pid = waitpid(mem_child.pid, &status, WNOHANG); + if (pid) + break; + + pid = waitpid(gem_child.pid, &status, WNOHANG); + if (pid) + break; + + pid = waitpid(eb_child.pid, &status, WNOHANG); + igt_assert_eq(pid, 0); + + pid = waitpid(drop_child.pid, &status, WNOHANG); + igt_assert_eq(pid, 0); + + sleep(1); + } + + igt_debug("Triggering OOM...\n"); + trigger_oom(); + + sleep(1); + + igt_try_stop_helper(&mem_child); + igt_try_stop_helper(&gem_child); + igt_stop_helper(&eb_child); + igt_stop_helper(&drop_child); + } while (count < passes); +} + igt_main { const struct test { @@ -432,6 +808,31 @@ igt_main igt_subtest("reclaim") reclaim(I915_EXEC_DEFAULT, 2); + igt_subtest_group { + struct { + const char *name; + unsigned int flags; + unsigned int passes; + } *p, passes[] = { + { "reclaims-and-oom-basic", TEST_BO, 1 }, + { "reclaims-and-oom", TEST_BO, 3 }, + { "reclaims-and-oom-userptr-basic", TEST_USERPTR, 1 }, + { "reclaims-and-oom-userptr", TEST_USERPTR, 3 }, + { "reclaims-and-oom-both-basic", TEST_BO | TEST_USERPTR, 1 }, + { "reclaims-and-oom-both", TEST_BO | TEST_USERPTR, 3 }, + { NULL, 0, 0 }, + }; + + igt_fixture { + igt_require(has_sysrq_trigger()); + } + + for (p = passes; p->name; p++) { + igt_subtest(p->name) + reclaim_oom(p->flags, p->passes); + } + } + for(const struct test *t = tests; t->name; t++) { for(const struct mode *m = modes; m->suffix; m++) { igt_subtest_f("%s%s", t->name, m->suffix) diff --git a/tests/intel-ci/blacklist.txt b/tests/intel-ci/blacklist.txt index 73d127603d28..b6e302db381e 100644 --- a/tests/intel-ci/blacklist.txt +++ b/tests/intel-ci/blacklist.txt @@ -59,7 +59,7 @@ igt@gem_ringfill@(?!.*basic).* igt@gem_ring_sync_copy(@.*)? igt@gem_ring_sync_loop(@.*)? igt@gem_seqno_wrap(@.*)? -igt@gem_shrink@(?!reclaim$).* +igt@gem_shrink@(?!reclaim).* igt@gem_softpin@.*(hang|S4).* igt@gem_spin_batch(@.*)? igt@gem_stolen@.*hibernate.* diff --git a/tests/intel-ci/fast-feedback.testlist b/tests/intel-ci/fast-feedback.testlist index 6d42792c67f7..0df7cc2fd9fd 100644 --- a/tests/intel-ci/fast-feedback.testlist +++ b/tests/intel-ci/fast-feedback.testlist @@ -124,6 +124,9 @@ igt@gem_ringfill@basic-default igt@gem_ringfill@basic-default-interruptible igt@gem_ringfill@basic-default-forked igt@gem_ringfill@basic-default-fd +igt@gem_shrink@reclaims-and-oom-basic +igt@gem_shrink@reclaims-and-oom-userptr-basic +igt@gem_shrink@reclaims-and-oom-both-basic igt@gem_sync@basic-all igt@gem_sync@basic-each igt@gem_sync@basic-many-each -- 2.19.1 _______________________________________________ Intel-gfx mailing list Intel-gfx@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/intel-gfx