[Intel-gfx] [igt-dev] [PATCH i-g-t 02/11] tests/i915/drm_fdinfo: Basic and functional tests for GPU busyness exported via fdinfo
Tvrtko Ursulin
tvrtko.ursulin at linux.intel.com
Thu Mar 31 13:23:27 UTC 2022
On 30/03/2022 21:11, Umesh Nerlige Ramappa wrote:
> This looks very similar to existing perf_pmu tests with the slight
> change that the busyness is now captured from the fdinfo.
Yep, much copy-and-paste was involved. :)
> lgtm,
> Reviewed-by: Umesh Nerlige Ramappa <umesh.nerlige.ramappa at intel.com>
Thanks!
Regards,
Tvrtko
>
> Umesh
>
> On Tue, Feb 22, 2022 at 01:55:56PM +0000, Tvrtko Ursulin wrote:
>> From: Tvrtko Ursulin <tvrtko.ursulin at intel.com>
>>
>> Mostly inherited from the perf_pmu, some basic tests, and some tests to
>> verify exported GPU busyness is as expected.
>>
>> Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin at intel.com>
>> ---
>> tests/i915/drm_fdinfo.c | 555 ++++++++++++++++++++++++++++++++++++++++
>> tests/meson.build | 8 +
>> 2 files changed, 563 insertions(+)
>> create mode 100644 tests/i915/drm_fdinfo.c
>>
>> diff --git a/tests/i915/drm_fdinfo.c b/tests/i915/drm_fdinfo.c
>> new file mode 100644
>> index 000000000000..e3b1ebb0f454
>> --- /dev/null
>> +++ b/tests/i915/drm_fdinfo.c
>> @@ -0,0 +1,555 @@
>> +/*
>> + * Copyright © 2022 Intel Corporation
>> + *
>> + * Permission is hereby granted, free of charge, to any person
>> obtaining a
>> + * copy of this software and associated documentation files (the
>> "Software"),
>> + * to deal in the Software without restriction, including without
>> limitation
>> + * the rights to use, copy, modify, merge, publish, distribute,
>> sublicense,
>> + * and/or sell copies of the Software, and to permit persons to whom the
>> + * Software is furnished to do so, subject to the following conditions:
>> + *
>> + * The above copyright notice and this permission notice (including
>> the next
>> + * paragraph) shall be included in all copies or substantial portions
>> of the
>> + * Software.
>> + *
>> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
>> EXPRESS OR
>> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
>> MERCHANTABILITY,
>> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
>> SHALL
>> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
>> OR OTHER
>> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
>> ARISING
>> + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
>> OTHER DEALINGS
>> + * IN THE SOFTWARE.
>> + *
>> + */
>> +
>> +#include "igt.h"
>> +#include "igt_core.h"
>> +#include "igt_device.h"
>> +#include "igt_drm_fdinfo.h"
>> +#include "i915/gem.h"
>> +#include "intel_ctx.h"
>> +
>> +IGT_TEST_DESCRIPTION("Test the i915 drm fdinfo data");
>> +
>> +const double tolerance = 0.05f;
>> +const unsigned long batch_duration_ns = 500e6;
>> +
>> +#define __assert_within_epsilon(x, ref, tol_up, tol_down) \
>> + igt_assert_f((double)(x) <= (1.0 + (tol_up)) * (double)(ref) && \
>> + (double)(x) >= (1.0 - (tol_down)) * (double)(ref), \
>> + "'%s' != '%s' (%f not within +%.1f%%/-%.1f%% tolerance
>> of %f)\n",\
>> + #x, #ref, (double)(x), \
>> + (tol_up) * 100.0, (tol_down) * 100.0, \
>> + (double)(ref))
>> +
>> +#define assert_within_epsilon(x, ref, tolerance) \
>> + __assert_within_epsilon(x, ref, tolerance, tolerance)
>> +
>> +static void basics(int i915, unsigned int num_classes)
>> +{
>> + struct drm_client_fdinfo info = { };
>> + bool ret;
>> +
>> + ret = igt_parse_drm_fdinfo(i915, &info);
>> + igt_assert(ret);
>> +
>> + igt_assert(!strcmp(info.driver, "i915"));
>> +
>> + igt_assert_eq(info.num_engines, num_classes);
>> +}
>> +
>> +/*
>> + * Helper for cases where we assert on time spent sleeping (directly or
>> + * indirectly), so make it more robust by ensuring the system sleep time
>> + * is within test tolerance to start with.
>> + */
>> +static unsigned int measured_usleep(unsigned int usec)
>> +{
>> + struct timespec ts = { };
>> + unsigned int slept;
>> +
>> + slept = igt_nsec_elapsed(&ts);
>> + igt_assert(slept == 0);
>> + do {
>> + usleep(usec - slept);
>> + slept = igt_nsec_elapsed(&ts) / 1000;
>> + } while (slept < usec);
>> +
>> + return igt_nsec_elapsed(&ts);
>> +}
>> +
>> +#define TEST_BUSY (1)
>> +#define FLAG_SYNC (2)
>> +#define TEST_TRAILING_IDLE (4)
>> +#define FLAG_HANG (8)
>> +#define TEST_ISOLATION (16)
>> +
>> +static igt_spin_t *__spin_poll(int fd, uint64_t ahnd, const
>> intel_ctx_t *ctx,
>> + const struct intel_execution_engine2 *e)
>> +{
>> + struct igt_spin_factory opts = {
>> + .ahnd = ahnd,
>> + .ctx = ctx,
>> + .engine = e->flags,
>> + };
>> +
>> + if (gem_class_can_store_dword(fd, e->class))
>> + opts.flags |= IGT_SPIN_POLL_RUN;
>> +
>> + return __igt_spin_factory(fd, &opts);
>> +}
>> +
>> +static unsigned long __spin_wait(int fd, igt_spin_t *spin)
>> +{
>> + struct timespec start = { };
>> +
>> + igt_nsec_elapsed(&start);
>> +
>> + if (igt_spin_has_poll(spin)) {
>> + unsigned long timeout = 0;
>> +
>> + while (!igt_spin_has_started(spin)) {
>> + unsigned long t = igt_nsec_elapsed(&start);
>> +
>> + igt_assert(gem_bo_busy(fd, spin->handle));
>> + if ((t - timeout) > 250e6) {
>> + timeout = t;
>> + igt_warn("Spinner not running after %.2fms\n",
>> + (double)t / 1e6);
>> + igt_assert(t < 2e9);
>> + }
>> + }
>> + } else {
>> + igt_debug("__spin_wait - usleep mode\n");
>> + usleep(500e3); /* Better than nothing! */
>> + }
>> +
>> + igt_assert(gem_bo_busy(fd, spin->handle));
>> + return igt_nsec_elapsed(&start);
>> +}
>> +
>> +static igt_spin_t *__spin_sync(int fd, uint64_t ahnd, const
>> intel_ctx_t *ctx,
>> + const struct intel_execution_engine2 *e)
>> +{
>> + igt_spin_t *spin = __spin_poll(fd, ahnd, ctx, e);
>> +
>> + __spin_wait(fd, spin);
>> +
>> + return spin;
>> +}
>> +
>> +static igt_spin_t *spin_sync(int fd, uint64_t ahnd, const intel_ctx_t
>> *ctx,
>> + const struct intel_execution_engine2 *e)
>> +{
>> + igt_require_gem(fd);
>> +
>> + return __spin_sync(fd, ahnd, ctx, e);
>> +}
>> +
>> +static void end_spin(int fd, igt_spin_t *spin, unsigned int flags)
>> +{
>> + if (!spin)
>> + return;
>> +
>> + igt_spin_end(spin);
>> +
>> + if (flags & FLAG_SYNC)
>> + gem_sync(fd, spin->handle);
>> +
>> + if (flags & TEST_TRAILING_IDLE) {
>> + unsigned long t, timeout = 0;
>> + struct timespec start = { };
>> +
>> + igt_nsec_elapsed(&start);
>> +
>> + do {
>> + t = igt_nsec_elapsed(&start);
>> +
>> + if (gem_bo_busy(fd, spin->handle) &&
>> + (t - timeout) > 10e6) {
>> + timeout = t;
>> + igt_warn("Spinner not idle after %.2fms\n",
>> + (double)t / 1e6);
>> + }
>> +
>> + usleep(1e3);
>> + } while (t < batch_duration_ns / 5);
>> + }
>> +}
>> +
>> +static uint64_t read_busy(int i915, unsigned int class)
>> +{
>> + struct drm_client_fdinfo info = { };
>> +
>> + igt_assert(igt_parse_drm_fdinfo(i915, &info));
>> +
>> + return info.busy[class];
>> +}
>> +
>> +static void
>> +single(int gem_fd, const intel_ctx_t *ctx,
>> + const struct intel_execution_engine2 *e, unsigned int flags)
>> +{
>> + unsigned long slept;
>> + igt_spin_t *spin;
>> + uint64_t val;
>> + int spin_fd;
>> + uint64_t ahnd;
>> +
>> + if (flags & TEST_ISOLATION) {
>> + spin_fd = gem_reopen_driver(gem_fd);
>> + ctx = intel_ctx_create_all_physical(spin_fd);
>> + } else {
>> + spin_fd = gem_fd;
>> + }
>> +
>> + ahnd = get_reloc_ahnd(spin_fd, ctx->id);
>> +
>> + if (flags & TEST_BUSY)
>> + spin = spin_sync(spin_fd, ahnd, ctx, e);
>> + else
>> + spin = NULL;
>> +
>> + val = read_busy(gem_fd, e->class);
>> + slept = measured_usleep(batch_duration_ns / 1000);
>> + if (flags & TEST_TRAILING_IDLE)
>> + end_spin(spin_fd, spin, flags);
>> + val = read_busy(gem_fd, e->class) - val;
>> +
>> + if (flags & FLAG_HANG)
>> + igt_force_gpu_reset(spin_fd);
>> + else
>> + end_spin(spin_fd, spin, FLAG_SYNC);
>> +
>> + assert_within_epsilon(val,
>> + (flags & TEST_BUSY) && !(flags & TEST_ISOLATION) ?
>> + slept : 0.0f,
>> + tolerance);
>> +
>> + /* Check for idle after hang. */
>> + if (flags & FLAG_HANG) {
>> + gem_quiescent_gpu(spin_fd);
>> + igt_assert(!gem_bo_busy(spin_fd, spin->handle));
>> +
>> + val = read_busy(gem_fd, e->class);
>> + slept = measured_usleep(batch_duration_ns / 1000);
>> + val = read_busy(gem_fd, e->class) - val;
>> +
>> + assert_within_epsilon(val, 0, tolerance);
>> + }
>> +
>> + igt_spin_free(spin_fd, spin);
>> + put_ahnd(ahnd);
>> +
>> + gem_quiescent_gpu(spin_fd);
>> +}
>> +
>> +static void log_busy(unsigned int num_engines, uint64_t *val)
>> +{
>> + char buf[1024];
>> + int rem = sizeof(buf);
>> + unsigned int i;
>> + char *p = buf;
>> +
>> + for (i = 0; i < num_engines; i++) {
>> + int len;
>> +
>> + len = snprintf(p, rem, "%u=%" PRIu64 "\n", i, val[i]);
>> + igt_assert(len > 0);
>> + rem -= len;
>> + p += len;
>> + }
>> +
>> + igt_info("%s", buf);
>> +}
>> +
>> +static void read_busy_all(int i915, uint64_t *val)
>> +{
>> + struct drm_client_fdinfo info = { };
>> +
>> + igt_assert(igt_parse_drm_fdinfo(i915, &info));
>> +
>> + memcpy(val, info.busy, sizeof(info.busy));
>> +}
>> +
>> +static void
>> +busy_check_all(int gem_fd, const intel_ctx_t *ctx,
>> + const struct intel_execution_engine2 *e,
>> + const unsigned int num_engines,
>> + const unsigned int classes[16], const unsigned int
>> num_classes,
>> + unsigned int flags)
>> +{
>> + uint64_t ahnd = get_reloc_ahnd(gem_fd, ctx->id);
>> + uint64_t tval[2][16];
>> + unsigned long slept;
>> + uint64_t val[16];
>> + igt_spin_t *spin;
>> + unsigned int i;
>> +
>> + memset(tval, 0, sizeof(tval));
>> +
>> + spin = spin_sync(gem_fd, ahnd, ctx, e);
>> +
>> + read_busy_all(gem_fd, tval[0]);
>> + slept = measured_usleep(batch_duration_ns / 1000);
>> + if (flags & TEST_TRAILING_IDLE)
>> + end_spin(gem_fd, spin, flags);
>> + read_busy_all(gem_fd, tval[1]);
>> +
>> + end_spin(gem_fd, spin, FLAG_SYNC);
>> + igt_spin_free(gem_fd, spin);
>> + put_ahnd(ahnd);
>> +
>> + for (i = 0; i < num_classes; i++)
>> + val[i] = tval[1][i] - tval[0][i];
>> +
>> + log_busy(num_classes, val);
>> +
>> + for (i = 0; i < num_classes; i++) {
>> + double target = i == e->class ? slept : 0.0f;
>> +
>> + assert_within_epsilon(val[i], target, tolerance);
>> + }
>> +
>> + gem_quiescent_gpu(gem_fd);
>> +}
>> +
>> +static void
>> +__submit_spin(int gem_fd, igt_spin_t *spin,
>> + const struct intel_execution_engine2 *e,
>> + int offset)
>> +{
>> + struct drm_i915_gem_execbuffer2 eb = spin->execbuf;
>> +
>> + eb.flags &= ~(0x3f | I915_EXEC_BSD_MASK);
>> + eb.flags |= e->flags | I915_EXEC_NO_RELOC;
>> + eb.batch_start_offset += offset;
>> +
>> + gem_execbuf(gem_fd, &eb);
>> +}
>> +
>> +static void
>> +most_busy_check_all(int gem_fd, const intel_ctx_t *ctx,
>> + const struct intel_execution_engine2 *e,
>> + const unsigned int num_engines,
>> + const unsigned int classes[16],
>> + const unsigned int num_classes,
>> + unsigned int flags)
>> +{
>> + uint64_t ahnd = get_reloc_ahnd(gem_fd, ctx->id);
>> + unsigned int busy_class[num_classes];
>> + struct intel_execution_engine2 *e_;
>> + igt_spin_t *spin = NULL;
>> + uint64_t tval[2][16];
>> + unsigned long slept;
>> + uint64_t val[16];
>> + unsigned int i;
>> +
>> + memset(busy_class, 0, sizeof(busy_class));
>> + memset(tval, 0, sizeof(tval));
>> +
>> + for_each_ctx_engine(gem_fd, ctx, e_) {
>> + if (e->class == e_->class && e->instance == e_->instance) {
>> + continue;
>> + } else if (spin) {
>> + __submit_spin(gem_fd, spin, e_, 64);
>> + busy_class[e_->class]++;
>> + } else {
>> + spin = __spin_poll(gem_fd, ahnd, ctx, e_);
>> + busy_class[e_->class]++;
>> + }
>> + }
>> + igt_require(spin); /* at least one busy engine */
>> +
>> + /* Small delay to allow engines to start. */
>> + usleep(__spin_wait(gem_fd, spin) * num_engines / 1e3);
>> +
>> + read_busy_all(gem_fd, tval[0]);
>> + slept = measured_usleep(batch_duration_ns / 1000);
>> + if (flags & TEST_TRAILING_IDLE)
>> + end_spin(gem_fd, spin, flags);
>> + read_busy_all(gem_fd, tval[1]);
>> +
>> + end_spin(gem_fd, spin, FLAG_SYNC);
>> + igt_spin_free(gem_fd, spin);
>> + put_ahnd(ahnd);
>> +
>> + for (i = 0; i < num_classes; i++)
>> + val[i] = tval[1][i] - tval[0][i];
>> +
>> + log_busy(num_classes, val);
>> +
>> + for (i = 0; i < num_classes; i++) {
>> + double target = slept * busy_class[i];
>> +
>> + assert_within_epsilon(val[i], target, tolerance);
>> + }
>> + gem_quiescent_gpu(gem_fd);
>> +}
>> +
>> +static void
>> +all_busy_check_all(int gem_fd, const intel_ctx_t *ctx,
>> + const unsigned int num_engines,
>> + const unsigned int classes[16],
>> + const unsigned int num_classes,
>> + unsigned int flags)
>> +{
>> + uint64_t ahnd = get_reloc_ahnd(gem_fd, ctx->id);
>> + unsigned int busy_class[num_classes];
>> + struct intel_execution_engine2 *e;
>> + igt_spin_t *spin = NULL;
>> + uint64_t tval[2][16];
>> + unsigned long slept;
>> + uint64_t val[16];
>> + unsigned int i;
>> +
>> + memset(busy_class, 0, sizeof(busy_class));
>> + memset(tval, 0, sizeof(tval));
>> +
>> + for_each_ctx_engine(gem_fd, ctx, e) {
>> + if (spin)
>> + __submit_spin(gem_fd, spin, e, 64);
>> + else
>> + spin = __spin_poll(gem_fd, ahnd, ctx, e);
>> + busy_class[e->class]++;
>> + }
>> +
>> + /* Small delay to allow engines to start. */
>> + usleep(__spin_wait(gem_fd, spin) * num_engines / 1e3);
>> +
>> + read_busy_all(gem_fd, tval[0]);
>> + slept = measured_usleep(batch_duration_ns / 1000);
>> + if (flags & TEST_TRAILING_IDLE)
>> + end_spin(gem_fd, spin, flags);
>> + read_busy_all(gem_fd, tval[1]);
>> +
>> + end_spin(gem_fd, spin, FLAG_SYNC);
>> + igt_spin_free(gem_fd, spin);
>> + put_ahnd(ahnd);
>> +
>> + for (i = 0; i < num_classes; i++)
>> + val[i] = tval[1][i] - tval[0][i];
>> +
>> + log_busy(num_classes, val);
>> +
>> + for (i = 0; i < num_classes; i++) {
>> + double target = slept * busy_class[i];
>> +
>> + assert_within_epsilon(val[i], target, tolerance);
>> + }
>> + gem_quiescent_gpu(gem_fd);
>> +}
>> +
>> +#define test_each_engine(T, i915, ctx, e) \
>> + igt_subtest_with_dynamic(T) for_each_ctx_engine(i915, ctx, e) \
>> + igt_dynamic_f("%s", e->name)
>> +
>> +igt_main
>> +{
>> + unsigned int num_engines = 0, num_classes = 0;
>> + const struct intel_execution_engine2 *e;
>> + unsigned int classes[16] = { };
>> + const intel_ctx_t *ctx = NULL;
>> + int i915 = -1;
>> +
>> + igt_fixture {
>> + unsigned int i;
>> +
>> + i915 = __drm_open_driver(DRIVER_INTEL);
>> +
>> + igt_require_gem(i915);
>> +
>> + ctx = intel_ctx_create_all_physical(i915);
>> +
>> + for_each_ctx_engine(i915, ctx, e) {
>> + num_engines++;
>> + igt_assert(e->class < ARRAY_SIZE(classes));
>> + classes[e->class]++;
>> + }
>> + igt_require(num_engines);
>> +
>> + for (i = 0; i < ARRAY_SIZE(classes); i++) {
>> + if (classes[i])
>> + num_classes++;
>> + }
>> + igt_assert(num_classes);
>> + }
>> +
>> + /**
>> + * Test basic fdinfo content.
>> + */
>> + igt_subtest("basics")
>> + basics(i915, num_classes);
>> +
>> + /**
>> + * Test that engines show no load when idle.
>> + */
>> + test_each_engine("idle", i915, ctx, e)
>> + single(i915, ctx, e, 0);
>> +
>> + /**
>> + * Test that a single engine reports load correctly.
>> + */
>> + test_each_engine("busy", i915, ctx, e)
>> + single(i915, ctx, e, TEST_BUSY);
>> +
>> + test_each_engine("busy-idle", i915, ctx, e)
>> + single(i915, ctx, e, TEST_BUSY | TEST_TRAILING_IDLE);
>> +
>> + test_each_engine("busy-hang", i915, ctx, e) {
>> + igt_hang_t hang = igt_allow_hang(i915, ctx->id, 0);
>> +
>> + single(i915, ctx, e, TEST_BUSY | FLAG_HANG);
>> +
>> + igt_disallow_hang(i915, hang);
>> + }
>> +
>> + /**
>> + * Test that when one engine is loaded other report no
>> + * load.
>> + */
>> + test_each_engine("busy-check-all", i915, ctx, e)
>> + busy_check_all(i915, ctx, e, num_engines, classes, num_classes,
>> + TEST_BUSY);
>> +
>> + test_each_engine("busy-idle-check-all", i915, ctx, e)
>> + busy_check_all(i915, ctx, e, num_engines, classes, num_classes,
>> + TEST_BUSY | TEST_TRAILING_IDLE);
>> +
>> + /**
>> + * Test that when all except one engine are loaded all
>> + * loads are correctly reported.
>> + */
>> + test_each_engine("most-busy-check-all", i915, ctx, e)
>> + most_busy_check_all(i915, ctx, e, num_engines,
>> + classes, num_classes,
>> + TEST_BUSY);
>> +
>> + test_each_engine("most-busy-idle-check-all", i915, ctx, e)
>> + most_busy_check_all(i915, ctx, e, num_engines,
>> + classes, num_classes,
>> + TEST_BUSY | TEST_TRAILING_IDLE);
>> +
>> + /**
>> + * Test that when all engines are loaded all loads are
>> + * correctly reported.
>> + */
>> + igt_subtest("all-busy-check-all")
>> + all_busy_check_all(i915, ctx, num_engines, classes, num_classes,
>> + TEST_BUSY);
>> +
>> + igt_subtest("all-busy-idle-check-all")
>> + all_busy_check_all(i915, ctx, num_engines, classes, num_classes,
>> + TEST_BUSY | TEST_TRAILING_IDLE);
>> +
>> + /**
>> + * Test for no cross-client contamination.
>> + */
>> + test_each_engine("isolation", i915, ctx, e)
>> + single(i915, ctx, e, TEST_BUSY | TEST_ISOLATION);
>> +
>> + igt_fixture {
>> + intel_ctx_destroy(i915, ctx);
>> + close(i915);
>> + }
>> +}
>> diff --git a/tests/meson.build b/tests/meson.build
>> index 7003d0641d1d..0a87755d5433 100644
>> --- a/tests/meson.build
>> +++ b/tests/meson.build
>> @@ -290,6 +290,14 @@ foreach prog : i915_progs
>> test_list += prog
>> endforeach
>>
>> +test_executables += executable('drm_fdinfo',
>> + join_paths('i915', 'drm_fdinfo.c'),
>> + dependencies : test_deps + [ lib_igt_drm_fdinfo ],
>> + install_dir : libexecdir,
>> + install_rpath : libexecdir_rpathdir,
>> + install : true)
>> +test_list += 'drm_fdinfo'
>> +
>> test_executables += executable('dumb_buffer', 'dumb_buffer.c',
>> dependencies : test_deps + [ libatomic ],
>> install_dir : libexecdir,
>> --
>> 2.32.0
>>
More information about the Intel-gfx
mailing list