All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH i-g-t 00/11] Per client GPU utilisation
@ 2022-02-22 13:55 ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

This series contains four main components:

 1. Per client support for intel_gpu_top.
 2. IGT test for per client data exposed via fdinfo from i915.
 3. Extracting intel_gpu_top code into shared IGT libraries - which makes
    possible to write:
 4. Vendor agnostic rudimentary gputop tool.

Since I had the intel_gpu_top code for years, this flow of the series is what
I ended up with. But it also makes sense since we can have a cut point after
intel_gpu_top, in case the common drm fdinfo specification does not end up
getting enough traction.

Example of the intel_gpu_top output:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
intel-gpu-top: Intel Tigerlake (Gen12) @ /dev/dri/card0 -  220/ 221 MHz
    70% RC6;  0.62/ 7.08 W;      760 irqs/s

         ENGINES     BUSY                                 MI_SEMA MI_WAIT
       Render/3D   23.06% |██████▊                      |      0%      0%
         Blitter    0.00% |                             |      0%      0%
           Video    5.40% |█▋                           |      0%      0%
    VideoEnhance   20.67% |██████                       |      0%      0%

   PID              NAME  Render/3D    Blitter      Video    VideoEnhance
  3082               mpv |          ||          ||▌         ||██        |
  3117         neverball |█▉        ||          ||          ||          |
     1           systemd |▍         ||          ||          ||          |
  2338       gnome-shell |          ||          ||          ||          |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Example of the gputop output:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
DRM minor 0
    PID               NAME    render       copy       video
    3816          kwin_x11 |███▎      ||          ||          ||          |
    3523              Xorg |▊         ||          ||          ||          |
 1120449               mpv |          ||          ||▋         ||          |
 1120529          glxgears |▋         ||          ||          ||          |
 1120449               mpv |▍         ||          ||          ||          |
    3860       plasmashell |▏         ||          ||          ||          |
    4764           krunner |          ||          ||          ||          |
  575206            chrome |          ||          ||          ||          |
  833481           firefox |          ||          ||          ||          |
  892924       thunderbird |          ||          ||          ||          |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Tvrtko Ursulin (11):
  lib: Helper library for parsing i915 fdinfo output
  tests/i915/drm_fdinfo: Basic and functional tests for GPU busyness
    exported via fdinfo
  intel-gpu-top: Add support for per client stats
  lib: Extract igt_drm_clients from intel_gpu_top
  libdrmfdinfo: Allow specifying custom engine map
  libdrmclients: Record client drm minor
  libdrmclient: Support multiple DRM cards
  libdrmfdinfo: Track largest engine index
  libdrmclient/intel_gpu_top: Decouple hardcoded engine assumptions
  libdrmclient: Enforce client status sort order in the library
  gputop: Basic vendor agnostic GPU top tool

 lib/igt_drm_clients.c   | 443 +++++++++++++++++++++++++++++++
 lib/igt_drm_clients.h   | 104 ++++++++
 lib/igt_drm_fdinfo.c    | 200 ++++++++++++++
 lib/igt_drm_fdinfo.h    |  52 ++++
 lib/meson.build         |  15 ++
 man/intel_gpu_top.rst   |   4 +
 tests/i915/drm_fdinfo.c | 565 ++++++++++++++++++++++++++++++++++++++++
 tests/meson.build       |   8 +
 tools/gputop.c          | 276 ++++++++++++++++++++
 tools/intel_gpu_top.c   | 497 ++++++++++++++++++++++++++++++++++-
 tools/meson.build       |   7 +-
 11 files changed, 2159 insertions(+), 12 deletions(-)
 create mode 100644 lib/igt_drm_clients.c
 create mode 100644 lib/igt_drm_clients.h
 create mode 100644 lib/igt_drm_fdinfo.c
 create mode 100644 lib/igt_drm_fdinfo.h
 create mode 100644 tests/i915/drm_fdinfo.c
 create mode 100644 tools/gputop.c

-- 
2.32.0


^ permalink raw reply	[flat|nested] 50+ messages in thread

* [Intel-gfx] [PATCH i-g-t 00/11] Per client GPU utilisation
@ 2022-02-22 13:55 ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

This series contains four main components:

 1. Per client support for intel_gpu_top.
 2. IGT test for per client data exposed via fdinfo from i915.
 3. Extracting intel_gpu_top code into shared IGT libraries - which makes
    possible to write:
 4. Vendor agnostic rudimentary gputop tool.

Since I had the intel_gpu_top code for years, this flow of the series is what
I ended up with. But it also makes sense since we can have a cut point after
intel_gpu_top, in case the common drm fdinfo specification does not end up
getting enough traction.

Example of the intel_gpu_top output:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
intel-gpu-top: Intel Tigerlake (Gen12) @ /dev/dri/card0 -  220/ 221 MHz
    70% RC6;  0.62/ 7.08 W;      760 irqs/s

         ENGINES     BUSY                                 MI_SEMA MI_WAIT
       Render/3D   23.06% |██████▊                      |      0%      0%
         Blitter    0.00% |                             |      0%      0%
           Video    5.40% |█▋                           |      0%      0%
    VideoEnhance   20.67% |██████                       |      0%      0%

   PID              NAME  Render/3D    Blitter      Video    VideoEnhance
  3082               mpv |          ||          ||▌         ||██        |
  3117         neverball |█▉        ||          ||          ||          |
     1           systemd |▍         ||          ||          ||          |
  2338       gnome-shell |          ||          ||          ||          |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Example of the gputop output:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
DRM minor 0
    PID               NAME    render       copy       video
    3816          kwin_x11 |███▎      ||          ||          ||          |
    3523              Xorg |▊         ||          ||          ||          |
 1120449               mpv |          ||          ||▋         ||          |
 1120529          glxgears |▋         ||          ||          ||          |
 1120449               mpv |▍         ||          ||          ||          |
    3860       plasmashell |▏         ||          ||          ||          |
    4764           krunner |          ||          ||          ||          |
  575206            chrome |          ||          ||          ||          |
  833481           firefox |          ||          ||          ||          |
  892924       thunderbird |          ||          ||          ||          |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Tvrtko Ursulin (11):
  lib: Helper library for parsing i915 fdinfo output
  tests/i915/drm_fdinfo: Basic and functional tests for GPU busyness
    exported via fdinfo
  intel-gpu-top: Add support for per client stats
  lib: Extract igt_drm_clients from intel_gpu_top
  libdrmfdinfo: Allow specifying custom engine map
  libdrmclients: Record client drm minor
  libdrmclient: Support multiple DRM cards
  libdrmfdinfo: Track largest engine index
  libdrmclient/intel_gpu_top: Decouple hardcoded engine assumptions
  libdrmclient: Enforce client status sort order in the library
  gputop: Basic vendor agnostic GPU top tool

 lib/igt_drm_clients.c   | 443 +++++++++++++++++++++++++++++++
 lib/igt_drm_clients.h   | 104 ++++++++
 lib/igt_drm_fdinfo.c    | 200 ++++++++++++++
 lib/igt_drm_fdinfo.h    |  52 ++++
 lib/meson.build         |  15 ++
 man/intel_gpu_top.rst   |   4 +
 tests/i915/drm_fdinfo.c | 565 ++++++++++++++++++++++++++++++++++++++++
 tests/meson.build       |   8 +
 tools/gputop.c          | 276 ++++++++++++++++++++
 tools/intel_gpu_top.c   | 497 ++++++++++++++++++++++++++++++++++-
 tools/meson.build       |   7 +-
 11 files changed, 2159 insertions(+), 12 deletions(-)
 create mode 100644 lib/igt_drm_clients.c
 create mode 100644 lib/igt_drm_clients.h
 create mode 100644 lib/igt_drm_fdinfo.c
 create mode 100644 lib/igt_drm_fdinfo.h
 create mode 100644 tests/i915/drm_fdinfo.c
 create mode 100644 tools/gputop.c

-- 
2.32.0


^ permalink raw reply	[flat|nested] 50+ messages in thread

* [igt-dev] [PATCH i-g-t 00/11] Per client GPU utilisation
@ 2022-02-22 13:55 ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

This series contains four main components:

 1. Per client support for intel_gpu_top.
 2. IGT test for per client data exposed via fdinfo from i915.
 3. Extracting intel_gpu_top code into shared IGT libraries - which makes
    possible to write:
 4. Vendor agnostic rudimentary gputop tool.

Since I had the intel_gpu_top code for years, this flow of the series is what
I ended up with. But it also makes sense since we can have a cut point after
intel_gpu_top, in case the common drm fdinfo specification does not end up
getting enough traction.

Example of the intel_gpu_top output:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
intel-gpu-top: Intel Tigerlake (Gen12) @ /dev/dri/card0 -  220/ 221 MHz
    70% RC6;  0.62/ 7.08 W;      760 irqs/s

         ENGINES     BUSY                                 MI_SEMA MI_WAIT
       Render/3D   23.06% |██████▊                      |      0%      0%
         Blitter    0.00% |                             |      0%      0%
           Video    5.40% |█▋                           |      0%      0%
    VideoEnhance   20.67% |██████                       |      0%      0%

   PID              NAME  Render/3D    Blitter      Video    VideoEnhance
  3082               mpv |          ||          ||▌         ||██        |
  3117         neverball |█▉        ||          ||          ||          |
     1           systemd |▍         ||          ||          ||          |
  2338       gnome-shell |          ||          ||          ||          |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Example of the gputop output:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
DRM minor 0
    PID               NAME    render       copy       video
    3816          kwin_x11 |███▎      ||          ||          ||          |
    3523              Xorg |▊         ||          ||          ||          |
 1120449               mpv |          ||          ||▋         ||          |
 1120529          glxgears |▋         ||          ||          ||          |
 1120449               mpv |▍         ||          ||          ||          |
    3860       plasmashell |▏         ||          ||          ||          |
    4764           krunner |          ||          ||          ||          |
  575206            chrome |          ||          ||          ||          |
  833481           firefox |          ||          ||          ||          |
  892924       thunderbird |          ||          ||          ||          |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Tvrtko Ursulin (11):
  lib: Helper library for parsing i915 fdinfo output
  tests/i915/drm_fdinfo: Basic and functional tests for GPU busyness
    exported via fdinfo
  intel-gpu-top: Add support for per client stats
  lib: Extract igt_drm_clients from intel_gpu_top
  libdrmfdinfo: Allow specifying custom engine map
  libdrmclients: Record client drm minor
  libdrmclient: Support multiple DRM cards
  libdrmfdinfo: Track largest engine index
  libdrmclient/intel_gpu_top: Decouple hardcoded engine assumptions
  libdrmclient: Enforce client status sort order in the library
  gputop: Basic vendor agnostic GPU top tool

 lib/igt_drm_clients.c   | 443 +++++++++++++++++++++++++++++++
 lib/igt_drm_clients.h   | 104 ++++++++
 lib/igt_drm_fdinfo.c    | 200 ++++++++++++++
 lib/igt_drm_fdinfo.h    |  52 ++++
 lib/meson.build         |  15 ++
 man/intel_gpu_top.rst   |   4 +
 tests/i915/drm_fdinfo.c | 565 ++++++++++++++++++++++++++++++++++++++++
 tests/meson.build       |   8 +
 tools/gputop.c          | 276 ++++++++++++++++++++
 tools/intel_gpu_top.c   | 497 ++++++++++++++++++++++++++++++++++-
 tools/meson.build       |   7 +-
 11 files changed, 2159 insertions(+), 12 deletions(-)
 create mode 100644 lib/igt_drm_clients.c
 create mode 100644 lib/igt_drm_clients.h
 create mode 100644 lib/igt_drm_fdinfo.c
 create mode 100644 lib/igt_drm_fdinfo.h
 create mode 100644 tests/i915/drm_fdinfo.c
 create mode 100644 tools/gputop.c

-- 
2.32.0

^ permalink raw reply	[flat|nested] 50+ messages in thread

* [PATCH i-g-t 01/11] lib: Helper library for parsing i915 fdinfo output
  2022-02-22 13:55 ` [Intel-gfx] " Tvrtko Ursulin
  (?)
@ 2022-02-22 13:55   ` Tvrtko Ursulin
  -1 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Tests and intel_gpu_top will share common code for parsing this file.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_fdinfo.c | 183 +++++++++++++++++++++++++++++++++++++++++++
 lib/igt_drm_fdinfo.h |  48 ++++++++++++
 lib/meson.build      |   7 ++
 3 files changed, 238 insertions(+)
 create mode 100644 lib/igt_drm_fdinfo.c
 create mode 100644 lib/igt_drm_fdinfo.h

diff --git a/lib/igt_drm_fdinfo.c b/lib/igt_drm_fdinfo.c
new file mode 100644
index 000000000000..28c1bdbda08e
--- /dev/null
+++ b/lib/igt_drm_fdinfo.c
@@ -0,0 +1,183 @@
+/*
+ * 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 <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "drmtest.h"
+
+#include "igt_drm_fdinfo.h"
+
+static size_t read_fdinfo(char *buf, const size_t sz, int at, const char *name)
+{
+	size_t count;
+	int fd;
+
+	fd = openat(at, name, O_RDONLY);
+	if (fd < 0)
+		return 0;
+
+	buf[sz - 1] = 0;
+	count = read(fd, buf, sz);
+	close(fd);
+
+	return count;
+}
+
+static int parse_engine(char *line, struct drm_client_fdinfo *info,
+			size_t prefix_len, uint64_t *val)
+{
+	static const char *e2class[] = {
+		"render",
+		"copy",
+		"video",
+		"video-enhance",
+	};
+	ssize_t name_len;
+	char *name, *p;
+	int found = -1;
+	unsigned int i;
+
+	p = index(line, ':');
+	if (!p || p == line)
+		return -1;
+
+	name_len = p - line - prefix_len;
+	if (name_len < 1)
+		return -1;
+
+	name = line + prefix_len;
+
+	for (i = 0; i < ARRAY_SIZE(e2class); i++) {
+		if (!strncmp(name, e2class[i], name_len)) {
+			found = i;
+			break;
+		}
+	}
+
+	if (found >= 0) {
+		while (*++p && isspace(*p));
+		*val = strtoull(p, NULL, 10);
+	}
+
+	return found;
+}
+
+static const char *find_kv(const char *buf, const char *key, size_t keylen)
+{
+	const char *p = buf;
+
+	p = index(buf, ':');
+	if (!p || p == buf)
+		return NULL;
+
+	if ((p - buf) != keylen)
+		return NULL;
+
+	while (*++p && isspace(*p));
+	if (*p && !strncmp(buf, key, keylen))
+		return p;
+
+	return NULL;
+}
+
+bool
+__igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
+{
+	char buf[4096], *_buf = buf;
+	char *l, *ctx = NULL;
+	unsigned int good = 0;
+	size_t count;
+
+	count = read_fdinfo(buf, sizeof(buf), dir, fd);
+	if (!count)
+		return false;
+
+	while ((l = strtok_r(_buf, "\n", &ctx))) {
+		uint64_t val = 0;
+		const char *v;
+		int idx;
+
+		_buf = NULL;
+
+		if ((v = find_kv(l, "drm-driver", strlen("drm-driver")))) {
+			strncpy(info->driver, v, sizeof(info->driver) - 1);
+			good++;
+		} else if ((v = find_kv(l, "drm-pdev", strlen("drm-pdev")))) {
+			strncpy(info->pdev, v, sizeof(info->pdev) - 1);
+		}  else if ((v = find_kv(l, "drm-client-id",
+					 strlen("drm-client-id")))) {
+			info->id = atol(v);
+			good++;
+		} else if (!strncmp(l, "drm-engine-", 11) &&
+			   strncmp(l, "drm-engine-capacity-", 20)) {
+			idx = parse_engine(l, info, strlen("drm-engine-"),
+					   &val);
+			if (idx >= 0) {
+				if (!info->capacity[idx])
+					info->capacity[idx] = 1;
+				info->busy[idx] = val;
+				info->num_engines++;
+			}
+		} else if (!strncmp(l, "drm-engine-capacity-", 20)) {
+			idx = parse_engine(l, info,
+					   strlen("drm-engine-capacity-"),
+					   &val);
+			if (idx >= 0)
+				info->capacity[idx] = val;
+		}
+	}
+
+	if (good < 2 || !info->num_engines)
+		return false; /* fdinfo format not as expected */
+
+	return true;
+}
+
+bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info)
+{
+	char fd[64];
+	int dir, ret;
+	bool res;
+
+	ret = snprintf(fd, sizeof(fd), "%u", drm_fd);
+	if (ret < 0 || ret == sizeof(fd))
+		return false;
+
+	dir = open("/proc/self/fdinfo", O_DIRECTORY | O_RDONLY);
+	if (dir < 0)
+		return false;
+
+	res = __igt_parse_drm_fdinfo(dir, fd, info);
+
+	close(dir);
+
+	return res;
+}
diff --git a/lib/igt_drm_fdinfo.h b/lib/igt_drm_fdinfo.h
new file mode 100644
index 000000000000..c527bab9a204
--- /dev/null
+++ b/lib/igt_drm_fdinfo.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef IGT_DRM_FDINFO_H
+#define IGT_DRM_FDINFO_H
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+struct drm_client_fdinfo {
+	char driver[128];
+	char pdev[128];
+	unsigned long id;
+
+	unsigned int num_engines;
+	unsigned int capacity[16];
+	uint64_t busy[16];
+};
+
+bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info);
+
+bool __igt_parse_drm_fdinfo(int dir, const char *fd,
+			    struct drm_client_fdinfo *info);
+
+#endif /* IGT_DRM_FDINFO_H */
diff --git a/lib/meson.build b/lib/meson.build
index 3e43316d1e36..d00817dc58bc 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -16,6 +16,7 @@ lib_sources = [
 	'igt_debugfs.c',
 	'igt_device.c',
 	'igt_device_scan.c',
+	'igt_drm_fdinfo.c',
 	'igt_aux.c',
 	'igt_gt.c',
 	'igt_halffloat.c',
@@ -216,6 +217,12 @@ lib_igt_device_scan_build = static_library('igt_device_scan',
 lib_igt_device_scan = declare_dependency(link_with : lib_igt_device_scan_build,
 				  include_directories : inc)
 
+lib_igt_drm_fdinfo_build = static_library('igt_drm_fdinfo',
+	['igt_drm_fdinfo.c'],
+	include_directories : inc)
+
+lib_igt_drm_fdinfo = declare_dependency(link_with : lib_igt_drm_fdinfo_build,
+				  include_directories : inc)
 i915_perf_files = [
   'igt_list.c',
   'i915/perf.c',
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [Intel-gfx] [PATCH i-g-t 01/11] lib: Helper library for parsing i915 fdinfo output
@ 2022-02-22 13:55   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Tests and intel_gpu_top will share common code for parsing this file.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_fdinfo.c | 183 +++++++++++++++++++++++++++++++++++++++++++
 lib/igt_drm_fdinfo.h |  48 ++++++++++++
 lib/meson.build      |   7 ++
 3 files changed, 238 insertions(+)
 create mode 100644 lib/igt_drm_fdinfo.c
 create mode 100644 lib/igt_drm_fdinfo.h

diff --git a/lib/igt_drm_fdinfo.c b/lib/igt_drm_fdinfo.c
new file mode 100644
index 000000000000..28c1bdbda08e
--- /dev/null
+++ b/lib/igt_drm_fdinfo.c
@@ -0,0 +1,183 @@
+/*
+ * 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 <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "drmtest.h"
+
+#include "igt_drm_fdinfo.h"
+
+static size_t read_fdinfo(char *buf, const size_t sz, int at, const char *name)
+{
+	size_t count;
+	int fd;
+
+	fd = openat(at, name, O_RDONLY);
+	if (fd < 0)
+		return 0;
+
+	buf[sz - 1] = 0;
+	count = read(fd, buf, sz);
+	close(fd);
+
+	return count;
+}
+
+static int parse_engine(char *line, struct drm_client_fdinfo *info,
+			size_t prefix_len, uint64_t *val)
+{
+	static const char *e2class[] = {
+		"render",
+		"copy",
+		"video",
+		"video-enhance",
+	};
+	ssize_t name_len;
+	char *name, *p;
+	int found = -1;
+	unsigned int i;
+
+	p = index(line, ':');
+	if (!p || p == line)
+		return -1;
+
+	name_len = p - line - prefix_len;
+	if (name_len < 1)
+		return -1;
+
+	name = line + prefix_len;
+
+	for (i = 0; i < ARRAY_SIZE(e2class); i++) {
+		if (!strncmp(name, e2class[i], name_len)) {
+			found = i;
+			break;
+		}
+	}
+
+	if (found >= 0) {
+		while (*++p && isspace(*p));
+		*val = strtoull(p, NULL, 10);
+	}
+
+	return found;
+}
+
+static const char *find_kv(const char *buf, const char *key, size_t keylen)
+{
+	const char *p = buf;
+
+	p = index(buf, ':');
+	if (!p || p == buf)
+		return NULL;
+
+	if ((p - buf) != keylen)
+		return NULL;
+
+	while (*++p && isspace(*p));
+	if (*p && !strncmp(buf, key, keylen))
+		return p;
+
+	return NULL;
+}
+
+bool
+__igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
+{
+	char buf[4096], *_buf = buf;
+	char *l, *ctx = NULL;
+	unsigned int good = 0;
+	size_t count;
+
+	count = read_fdinfo(buf, sizeof(buf), dir, fd);
+	if (!count)
+		return false;
+
+	while ((l = strtok_r(_buf, "\n", &ctx))) {
+		uint64_t val = 0;
+		const char *v;
+		int idx;
+
+		_buf = NULL;
+
+		if ((v = find_kv(l, "drm-driver", strlen("drm-driver")))) {
+			strncpy(info->driver, v, sizeof(info->driver) - 1);
+			good++;
+		} else if ((v = find_kv(l, "drm-pdev", strlen("drm-pdev")))) {
+			strncpy(info->pdev, v, sizeof(info->pdev) - 1);
+		}  else if ((v = find_kv(l, "drm-client-id",
+					 strlen("drm-client-id")))) {
+			info->id = atol(v);
+			good++;
+		} else if (!strncmp(l, "drm-engine-", 11) &&
+			   strncmp(l, "drm-engine-capacity-", 20)) {
+			idx = parse_engine(l, info, strlen("drm-engine-"),
+					   &val);
+			if (idx >= 0) {
+				if (!info->capacity[idx])
+					info->capacity[idx] = 1;
+				info->busy[idx] = val;
+				info->num_engines++;
+			}
+		} else if (!strncmp(l, "drm-engine-capacity-", 20)) {
+			idx = parse_engine(l, info,
+					   strlen("drm-engine-capacity-"),
+					   &val);
+			if (idx >= 0)
+				info->capacity[idx] = val;
+		}
+	}
+
+	if (good < 2 || !info->num_engines)
+		return false; /* fdinfo format not as expected */
+
+	return true;
+}
+
+bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info)
+{
+	char fd[64];
+	int dir, ret;
+	bool res;
+
+	ret = snprintf(fd, sizeof(fd), "%u", drm_fd);
+	if (ret < 0 || ret == sizeof(fd))
+		return false;
+
+	dir = open("/proc/self/fdinfo", O_DIRECTORY | O_RDONLY);
+	if (dir < 0)
+		return false;
+
+	res = __igt_parse_drm_fdinfo(dir, fd, info);
+
+	close(dir);
+
+	return res;
+}
diff --git a/lib/igt_drm_fdinfo.h b/lib/igt_drm_fdinfo.h
new file mode 100644
index 000000000000..c527bab9a204
--- /dev/null
+++ b/lib/igt_drm_fdinfo.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef IGT_DRM_FDINFO_H
+#define IGT_DRM_FDINFO_H
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+struct drm_client_fdinfo {
+	char driver[128];
+	char pdev[128];
+	unsigned long id;
+
+	unsigned int num_engines;
+	unsigned int capacity[16];
+	uint64_t busy[16];
+};
+
+bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info);
+
+bool __igt_parse_drm_fdinfo(int dir, const char *fd,
+			    struct drm_client_fdinfo *info);
+
+#endif /* IGT_DRM_FDINFO_H */
diff --git a/lib/meson.build b/lib/meson.build
index 3e43316d1e36..d00817dc58bc 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -16,6 +16,7 @@ lib_sources = [
 	'igt_debugfs.c',
 	'igt_device.c',
 	'igt_device_scan.c',
+	'igt_drm_fdinfo.c',
 	'igt_aux.c',
 	'igt_gt.c',
 	'igt_halffloat.c',
@@ -216,6 +217,12 @@ lib_igt_device_scan_build = static_library('igt_device_scan',
 lib_igt_device_scan = declare_dependency(link_with : lib_igt_device_scan_build,
 				  include_directories : inc)
 
+lib_igt_drm_fdinfo_build = static_library('igt_drm_fdinfo',
+	['igt_drm_fdinfo.c'],
+	include_directories : inc)
+
+lib_igt_drm_fdinfo = declare_dependency(link_with : lib_igt_drm_fdinfo_build,
+				  include_directories : inc)
 i915_perf_files = [
   'igt_list.c',
   'i915/perf.c',
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [igt-dev] [PATCH i-g-t 01/11] lib: Helper library for parsing i915 fdinfo output
@ 2022-02-22 13:55   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Tests and intel_gpu_top will share common code for parsing this file.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_fdinfo.c | 183 +++++++++++++++++++++++++++++++++++++++++++
 lib/igt_drm_fdinfo.h |  48 ++++++++++++
 lib/meson.build      |   7 ++
 3 files changed, 238 insertions(+)
 create mode 100644 lib/igt_drm_fdinfo.c
 create mode 100644 lib/igt_drm_fdinfo.h

diff --git a/lib/igt_drm_fdinfo.c b/lib/igt_drm_fdinfo.c
new file mode 100644
index 000000000000..28c1bdbda08e
--- /dev/null
+++ b/lib/igt_drm_fdinfo.c
@@ -0,0 +1,183 @@
+/*
+ * 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 <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "drmtest.h"
+
+#include "igt_drm_fdinfo.h"
+
+static size_t read_fdinfo(char *buf, const size_t sz, int at, const char *name)
+{
+	size_t count;
+	int fd;
+
+	fd = openat(at, name, O_RDONLY);
+	if (fd < 0)
+		return 0;
+
+	buf[sz - 1] = 0;
+	count = read(fd, buf, sz);
+	close(fd);
+
+	return count;
+}
+
+static int parse_engine(char *line, struct drm_client_fdinfo *info,
+			size_t prefix_len, uint64_t *val)
+{
+	static const char *e2class[] = {
+		"render",
+		"copy",
+		"video",
+		"video-enhance",
+	};
+	ssize_t name_len;
+	char *name, *p;
+	int found = -1;
+	unsigned int i;
+
+	p = index(line, ':');
+	if (!p || p == line)
+		return -1;
+
+	name_len = p - line - prefix_len;
+	if (name_len < 1)
+		return -1;
+
+	name = line + prefix_len;
+
+	for (i = 0; i < ARRAY_SIZE(e2class); i++) {
+		if (!strncmp(name, e2class[i], name_len)) {
+			found = i;
+			break;
+		}
+	}
+
+	if (found >= 0) {
+		while (*++p && isspace(*p));
+		*val = strtoull(p, NULL, 10);
+	}
+
+	return found;
+}
+
+static const char *find_kv(const char *buf, const char *key, size_t keylen)
+{
+	const char *p = buf;
+
+	p = index(buf, ':');
+	if (!p || p == buf)
+		return NULL;
+
+	if ((p - buf) != keylen)
+		return NULL;
+
+	while (*++p && isspace(*p));
+	if (*p && !strncmp(buf, key, keylen))
+		return p;
+
+	return NULL;
+}
+
+bool
+__igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
+{
+	char buf[4096], *_buf = buf;
+	char *l, *ctx = NULL;
+	unsigned int good = 0;
+	size_t count;
+
+	count = read_fdinfo(buf, sizeof(buf), dir, fd);
+	if (!count)
+		return false;
+
+	while ((l = strtok_r(_buf, "\n", &ctx))) {
+		uint64_t val = 0;
+		const char *v;
+		int idx;
+
+		_buf = NULL;
+
+		if ((v = find_kv(l, "drm-driver", strlen("drm-driver")))) {
+			strncpy(info->driver, v, sizeof(info->driver) - 1);
+			good++;
+		} else if ((v = find_kv(l, "drm-pdev", strlen("drm-pdev")))) {
+			strncpy(info->pdev, v, sizeof(info->pdev) - 1);
+		}  else if ((v = find_kv(l, "drm-client-id",
+					 strlen("drm-client-id")))) {
+			info->id = atol(v);
+			good++;
+		} else if (!strncmp(l, "drm-engine-", 11) &&
+			   strncmp(l, "drm-engine-capacity-", 20)) {
+			idx = parse_engine(l, info, strlen("drm-engine-"),
+					   &val);
+			if (idx >= 0) {
+				if (!info->capacity[idx])
+					info->capacity[idx] = 1;
+				info->busy[idx] = val;
+				info->num_engines++;
+			}
+		} else if (!strncmp(l, "drm-engine-capacity-", 20)) {
+			idx = parse_engine(l, info,
+					   strlen("drm-engine-capacity-"),
+					   &val);
+			if (idx >= 0)
+				info->capacity[idx] = val;
+		}
+	}
+
+	if (good < 2 || !info->num_engines)
+		return false; /* fdinfo format not as expected */
+
+	return true;
+}
+
+bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info)
+{
+	char fd[64];
+	int dir, ret;
+	bool res;
+
+	ret = snprintf(fd, sizeof(fd), "%u", drm_fd);
+	if (ret < 0 || ret == sizeof(fd))
+		return false;
+
+	dir = open("/proc/self/fdinfo", O_DIRECTORY | O_RDONLY);
+	if (dir < 0)
+		return false;
+
+	res = __igt_parse_drm_fdinfo(dir, fd, info);
+
+	close(dir);
+
+	return res;
+}
diff --git a/lib/igt_drm_fdinfo.h b/lib/igt_drm_fdinfo.h
new file mode 100644
index 000000000000..c527bab9a204
--- /dev/null
+++ b/lib/igt_drm_fdinfo.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef IGT_DRM_FDINFO_H
+#define IGT_DRM_FDINFO_H
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+struct drm_client_fdinfo {
+	char driver[128];
+	char pdev[128];
+	unsigned long id;
+
+	unsigned int num_engines;
+	unsigned int capacity[16];
+	uint64_t busy[16];
+};
+
+bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info);
+
+bool __igt_parse_drm_fdinfo(int dir, const char *fd,
+			    struct drm_client_fdinfo *info);
+
+#endif /* IGT_DRM_FDINFO_H */
diff --git a/lib/meson.build b/lib/meson.build
index 3e43316d1e36..d00817dc58bc 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -16,6 +16,7 @@ lib_sources = [
 	'igt_debugfs.c',
 	'igt_device.c',
 	'igt_device_scan.c',
+	'igt_drm_fdinfo.c',
 	'igt_aux.c',
 	'igt_gt.c',
 	'igt_halffloat.c',
@@ -216,6 +217,12 @@ lib_igt_device_scan_build = static_library('igt_device_scan',
 lib_igt_device_scan = declare_dependency(link_with : lib_igt_device_scan_build,
 				  include_directories : inc)
 
+lib_igt_drm_fdinfo_build = static_library('igt_drm_fdinfo',
+	['igt_drm_fdinfo.c'],
+	include_directories : inc)
+
+lib_igt_drm_fdinfo = declare_dependency(link_with : lib_igt_drm_fdinfo_build,
+				  include_directories : inc)
 i915_perf_files = [
   'igt_list.c',
   'i915/perf.c',
-- 
2.32.0

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [Intel-gfx] [PATCH i-g-t 02/11] tests/i915/drm_fdinfo: Basic and functional tests for GPU busyness exported via fdinfo
  2022-02-22 13:55 ` [Intel-gfx] " Tvrtko Ursulin
  (?)
@ 2022-02-22 13:55   ` Tvrtko Ursulin
  -1 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel

From: Tvrtko Ursulin <tvrtko.ursulin@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@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


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH i-g-t 02/11] tests/i915/drm_fdinfo: Basic and functional tests for GPU busyness exported via fdinfo
@ 2022-02-22 13:55   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@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@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


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [igt-dev] [PATCH i-g-t 02/11] tests/i915/drm_fdinfo: Basic and functional tests for GPU busyness exported via fdinfo
@ 2022-02-22 13:55   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@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@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

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH i-g-t 03/11] intel-gpu-top: Add support for per client stats
  2022-02-22 13:55 ` [Intel-gfx] " Tvrtko Ursulin
  (?)
@ 2022-02-22 13:55   ` Tvrtko Ursulin
  -1 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Use the i915 exported data in /proc/<pid>/fdinfo to show GPU utilization
per DRM client.

Example of the output:

intel-gpu-top: Intel Tigerlake (Gen12) @ /dev/dri/card0 -  220/ 221 MHz
    70% RC6;  0.62/ 7.08 W;      760 irqs/s

         ENGINES     BUSY                                 MI_SEMA MI_WAIT
       Render/3D   23.06% |██████▊                      |      0%      0%
         Blitter    0.00% |                             |      0%      0%
           Video    5.40% |█▋                           |      0%      0%
    VideoEnhance   20.67% |██████                       |      0%      0%

   PID              NAME  Render/3D    Blitter      Video    VideoEnhance
  3082               mpv |          ||          ||▌         ||██        |
  3117         neverball |█▉        ||          ||          ||          |
     1           systemd |▍         ||          ||          ||          |
  2338       gnome-shell |          ||          ||          ||          |

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 man/intel_gpu_top.rst |   4 +
 tools/intel_gpu_top.c | 801 +++++++++++++++++++++++++++++++++++++++++-
 tools/meson.build     |   2 +-
 3 files changed, 804 insertions(+), 3 deletions(-)

diff --git a/man/intel_gpu_top.rst b/man/intel_gpu_top.rst
index b3b765b05feb..f4dbfc5b44d9 100644
--- a/man/intel_gpu_top.rst
+++ b/man/intel_gpu_top.rst
@@ -56,6 +56,10 @@ Supported keys:
     'q'    Exit from the tool.
     'h'    Show interactive help.
     '1'    Toggle between aggregated engine class and physical engine mode.
+    'n'    Toggle display of numeric client busyness overlay.
+    's'    Toggle between sort modes (runtime, total runtime, pid, client id).
+    'i'    Toggle display of clients which used no GPU time.
+    'H'    Toggle between per PID aggregation and individual clients.
 
 DEVICE SELECTION
 ================
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index bc11fce2bb1e..73815cdea8aa 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -43,8 +43,10 @@
 #include <sys/types.h>
 #include <unistd.h>
 #include <termios.h>
+#include <sys/sysmacros.h>
 
 #include "igt_perf.h"
+#include "igt_drm_fdinfo.h"
 
 #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
 
@@ -311,7 +313,8 @@ static int engine_cmp(const void *__a, const void *__b)
 		return a->instance - b->instance;
 }
 
-#define is_igpu_pci(x) (strcmp(x, "0000:00:02.0") == 0)
+#define IGPU_PCI "0000:00:02.0"
+#define is_igpu_pci(x) (strcmp(x, IGPU_PCI) == 0)
 #define is_igpu(x) (strcmp(x, "i915") == 0)
 
 static struct engines *discover_engines(char *device)
@@ -635,6 +638,547 @@ static void pmu_sample(struct engines *engines)
 	}
 }
 
+enum client_status {
+	FREE = 0, /* mbz */
+	ALIVE,
+	PROBE
+};
+
+struct clients;
+
+struct client {
+	struct clients *clients;
+
+	enum client_status status;
+	unsigned int id;
+	unsigned int pid;
+	char name[24];
+	char print_name[24];
+	unsigned int samples;
+	unsigned long total_runtime;
+	unsigned long last_runtime;
+	unsigned long *val;
+	uint64_t *last;
+};
+
+struct clients {
+	unsigned int num_clients;
+	unsigned int active_clients;
+
+	unsigned int num_classes;
+	struct engine_class *class;
+
+	char pci_slot[64];
+
+	struct client *client;
+};
+
+#define for_each_client(clients, c, tmp) \
+	for ((tmp) = (clients)->num_clients, c = (clients)->client; \
+	     (tmp > 0); (tmp)--, (c)++)
+
+static struct clients *init_clients(const char *pci_slot)
+{
+	struct clients *clients;
+
+	clients = malloc(sizeof(*clients));
+	if (!clients)
+		return NULL;
+
+	memset(clients, 0, sizeof(*clients));
+
+	strncpy(clients->pci_slot, pci_slot, sizeof(clients->pci_slot));
+
+	return clients;
+}
+
+static struct client *
+find_client(struct clients *clients, enum client_status status, unsigned int id)
+{
+	unsigned int start, num;
+	struct client *c;
+
+	start = status == FREE ? clients->active_clients : 0; /* Free block at the end. */
+	num = clients->num_clients - start;
+
+	for (c = &clients->client[start]; num; c++, num--) {
+		if (status != c->status)
+			continue;
+
+		if (status == FREE || c->id == id)
+			return c;
+	}
+
+	return NULL;
+}
+
+static void
+update_client(struct client *c, unsigned int pid, char *name, uint64_t val[16])
+{
+	unsigned int i;
+
+	if (c->pid != pid)
+		c->pid = pid;
+
+	if (strcmp(c->name, name)) {
+		char *p;
+
+		strncpy(c->name, name, sizeof(c->name) - 1);
+		strncpy(c->print_name, name, sizeof(c->print_name) - 1);
+
+		p = c->print_name;
+		while (*p) {
+			if (!isprint(*p))
+				*p = '*';
+			p++;
+		}
+	}
+
+	c->last_runtime = 0;
+	c->total_runtime = 0;
+
+	for (i = 0; i < c->clients->num_classes; i++) {
+		if (val[i] < c->last[i])
+			continue; /* It will catch up soon. */
+
+		c->total_runtime += val[i];
+		c->val[i] = val[i] - c->last[i];
+		c->last_runtime += c->val[i];
+		c->last[i] = val[i];
+	}
+
+	c->samples++;
+	c->status = ALIVE;
+}
+
+static void
+add_client(struct clients *clients, unsigned int id, unsigned int pid,
+	   char *name, uint64_t busy[16])
+{
+	struct client *c;
+
+	assert(!find_client(clients, ALIVE, id));
+
+	c = find_client(clients, FREE, 0);
+	if (!c) {
+		unsigned int idx = clients->num_clients;
+
+		clients->num_clients += (clients->num_clients + 2) / 2;
+		clients->client = realloc(clients->client,
+					  clients->num_clients * sizeof(*c));
+		assert(clients->client);
+
+		c = &clients->client[idx];
+		memset(c, 0, (clients->num_clients - idx) * sizeof(*c));
+	}
+
+	c->id = id;
+	c->clients = clients;
+	c->val = calloc(clients->num_classes, sizeof(c->val));
+	c->last = calloc(clients->num_classes, sizeof(c->last));
+	assert(c->val && c->last);
+
+	update_client(c, pid, name, busy);
+}
+
+static void free_client(struct client *c)
+{
+	free(c->val);
+	free(c->last);
+	memset(c, 0, sizeof(*c));
+}
+
+static int client_last_cmp(const void *_a, const void *_b)
+{
+	const struct client *a = _a;
+	const struct client *b = _b;
+	long tot_a, tot_b;
+
+	/*
+	 * Sort clients in descending order of runtime in the previous sampling
+	 * period for active ones, followed by inactive. Tie-breaker is client
+	 * id.
+	 */
+
+	tot_a = a->status == ALIVE ? a->last_runtime : -1;
+	tot_b = b->status == ALIVE ? b->last_runtime : -1;
+
+	tot_b -= tot_a;
+	if (tot_b > 0)
+		return 1;
+	if (tot_b < 0)
+		return -1;
+
+	return (int)b->id - a->id;
+}
+
+static int client_total_cmp(const void *_a, const void *_b)
+{
+	const struct client *a = _a;
+	const struct client *b = _b;
+	long tot_a, tot_b;
+
+	tot_a = a->status == ALIVE ? a->total_runtime : -1;
+	tot_b = b->status == ALIVE ? b->total_runtime : -1;
+
+	tot_b -= tot_a;
+	if (tot_b > 0)
+		return 1;
+	if (tot_b < 0)
+		return -1;
+
+	return (int)b->id - a->id;
+}
+
+static int client_id_cmp(const void *_a, const void *_b)
+{
+	const struct client *a = _a;
+	const struct client *b = _b;
+	int id_a, id_b;
+
+	id_a = a->status == ALIVE ? a->id : -1;
+	id_b = b->status == ALIVE ? b->id : -1;
+
+	id_b -= id_a;
+	if (id_b > 0)
+		return 1;
+	if (id_b < 0)
+		return -1;
+
+	return (int)b->id - a->id;
+}
+
+static int client_pid_cmp(const void *_a, const void *_b)
+{
+	const struct client *a = _a;
+	const struct client *b = _b;
+	int pid_a, pid_b;
+
+	pid_a = a->status == ALIVE ? a->pid : INT_MAX;
+	pid_b = b->status == ALIVE ? b->pid : INT_MAX;
+
+	pid_b -= pid_a;
+	if (pid_b > 0)
+		return -1;
+	if (pid_b < 0)
+		return 1;
+
+	return (int)a->id - b->id;
+}
+
+static int (*client_cmp)(const void *, const void *) = client_last_cmp;
+
+static struct clients *sort_clients(struct clients *clients,
+				    int (*cmp)(const void *, const void *))
+{
+	unsigned int active, free;
+	struct client *c;
+	int tmp;
+
+	if (!clients)
+		return clients;
+
+	qsort(clients->client, clients->num_clients, sizeof(*clients->client),
+	      cmp);
+
+	/* Trim excessive array space. */
+	active = 0;
+	for_each_client(clients, c, tmp) {
+		if (c->status != ALIVE)
+			break; /* Active clients are first in the array. */
+		active++;
+	}
+
+	clients->active_clients = active;
+
+	free = clients->num_clients - active;
+	if (free > clients->num_clients / 2) {
+		active = clients->num_clients - free / 2;
+		if (active != clients->num_clients) {
+			clients->num_clients = active;
+			clients->client = realloc(clients->client,
+						  clients->num_clients *
+						  sizeof(*c));
+		}
+	}
+
+	return clients;
+}
+
+static bool aggregate_pids = true;
+
+static struct clients *display_clients(struct clients *clients)
+{
+	struct client *ac, *c, *cp = NULL;
+	struct clients *aggregated;
+	int tmp, num = 0;
+
+	if (!aggregate_pids)
+		goto out;
+
+	/* Sort by pid first to make it easy to aggregate while walking. */
+	sort_clients(clients, client_pid_cmp);
+
+	aggregated = calloc(1, sizeof(*clients));
+	assert(aggregated);
+
+	ac = calloc(clients->num_clients, sizeof(*c));
+	assert(ac);
+
+	aggregated->num_classes = clients->num_classes;
+	aggregated->class = clients->class;
+	aggregated->client = ac;
+
+	for_each_client(clients, c, tmp) {
+		unsigned int i;
+
+		if (c->status == FREE)
+			break;
+
+		assert(c->status == ALIVE);
+
+		if ((cp && c->pid != cp->pid) || !cp) {
+			ac = &aggregated->client[num++];
+
+			/* New pid. */
+			ac->clients = aggregated;
+			ac->status = ALIVE;
+			ac->id = -c->pid;
+			ac->pid = c->pid;
+			strcpy(ac->name, c->name);
+			strcpy(ac->print_name, c->print_name);
+			ac->val = calloc(clients->num_classes,
+					 sizeof(ac->val[0]));
+			assert(ac->val);
+			ac->samples = 1;
+		}
+
+		cp = c;
+
+		if (c->samples < 2)
+			continue;
+
+		ac->samples = 2; /* All what matters for display. */
+		ac->total_runtime += c->total_runtime;
+		ac->last_runtime += c->last_runtime;
+
+		for (i = 0; i < clients->num_classes; i++)
+			ac->val[i] += c->val[i];
+	}
+
+	aggregated->num_clients = num;
+	aggregated->active_clients = num;
+
+	clients = aggregated;
+
+out:
+	return sort_clients(clients, client_cmp);
+}
+
+static void free_clients(struct clients *clients)
+{
+	struct client *c;
+	unsigned int tmp;
+
+	for_each_client(clients, c, tmp) {
+		free(c->val);
+		free(c->last);
+	}
+
+	free(clients->client);
+	free(clients);
+}
+
+static bool is_drm_fd(DIR *fd_dir, const char *name)
+{
+	struct stat stat;
+	int ret;
+
+	ret = fstatat(dirfd(fd_dir), name, &stat, 0);
+
+	return ret == 0 &&
+	       (stat.st_mode & S_IFMT) == S_IFCHR &&
+	       major(stat.st_rdev) == 226;
+}
+
+static bool get_task_name(const char *buffer, char *out, unsigned long sz)
+{
+	char *s = index(buffer, '(');
+	char *e = rindex(buffer, ')');
+	unsigned int len;
+
+	if (!s || !e)
+		return false;
+
+	len = --e - ++s + 1;
+	if(!len || (len + 1) >= sz)
+		return false;
+
+	strncpy(out, s, len);
+	out[len] = 0;
+
+	return true;
+}
+
+static DIR *opendirat(DIR *at, const char *name)
+{
+	DIR *dir;
+	int fd;
+
+	fd = openat(dirfd(at), name, O_DIRECTORY);
+	if (fd < 0)
+		return NULL;
+
+	dir = fdopendir(fd);
+	if (!dir)
+		close(fd);
+
+	return dir;
+}
+
+static FILE *fropenat(DIR *at, const char *name)
+{
+	FILE *f;
+	int fd;
+
+	fd = openat(dirfd(at), name, O_RDONLY);
+	if (fd < 0)
+		return NULL;
+
+	f = fdopen(fd, "r");
+	if (!f)
+		close(fd);
+
+	return f;
+}
+
+static size_t freadat2buf(char *buf, const size_t sz, DIR *at, const char *name)
+{
+	size_t count;
+	FILE *f;
+
+	f = fropenat(at, name);
+	if (!f)
+		return 0;
+
+	memset(buf, 0, sz);
+	count = fread(buf, 1, sz, f);
+	fclose(f);
+
+	return count;
+}
+
+static struct clients *scan_clients(struct clients *clients)
+{
+	struct dirent *proc_dent;
+	struct client *c;
+	DIR *proc_dir;
+	int tmp;
+
+	if (!clients)
+		return clients;
+
+	for_each_client(clients, c, tmp) {
+		assert(c->status != PROBE);
+		if (c->status == ALIVE)
+			c->status = PROBE;
+		else
+			break; /* Free block at the end of array. */
+	}
+
+	proc_dir = opendir("/proc");
+	if (!proc_dir)
+		return clients;
+
+	while ((proc_dent = readdir(proc_dir)) != NULL) {
+		DIR *pid_dir = NULL, *fd_dir = NULL, *fdinfo_dir = NULL;
+		struct dirent *fdinfo_dent;
+		char client_name[64] = { };
+		unsigned int client_pid;
+		char buf[4096];
+		size_t count;
+
+		if (proc_dent->d_type != DT_DIR)
+			continue;
+		if (!isdigit(proc_dent->d_name[0]))
+			continue;
+
+		pid_dir = opendirat(proc_dir, proc_dent->d_name);
+		if (!pid_dir)
+			continue;
+
+		count = freadat2buf(buf, sizeof(buf), pid_dir, "stat");
+		if (!count)
+			goto next;
+
+		client_pid = atoi(buf);
+		if (!client_pid)
+			goto next;
+
+		if (!get_task_name(buf, client_name, sizeof(client_name)))
+			goto next;
+
+		fd_dir = opendirat(pid_dir, "fd");
+		if (!fd_dir)
+			goto next;
+
+		fdinfo_dir = opendirat(pid_dir, "fdinfo");
+		if (!fdinfo_dir)
+			goto next;
+
+		while ((fdinfo_dent = readdir(fdinfo_dir)) != NULL) {
+			struct drm_client_fdinfo info = { };
+
+			if (fdinfo_dent->d_type != DT_REG)
+				continue;
+			if (!isdigit(fdinfo_dent->d_name[0]))
+				continue;
+
+			if (!is_drm_fd(fd_dir, fdinfo_dent->d_name))
+				continue;
+
+			if (!__igt_parse_drm_fdinfo(dirfd(fdinfo_dir),
+						    fdinfo_dent->d_name,
+						    &info))
+				continue;
+
+			if (strcmp(info.driver, "i915"))
+				continue;
+			if (strcmp(info.pdev, clients->pci_slot))
+				continue;
+			if (find_client(clients, ALIVE, info.id))
+				continue; /* Skip duplicate fds. */
+
+			c = find_client(clients, PROBE, info.id);
+			if (!c)
+				add_client(clients, info.id, client_pid,
+					   client_name, info.busy);
+			else
+				update_client(c, client_pid, client_name,
+					      info.busy);
+		}
+
+next:
+		if (fdinfo_dir)
+			closedir(fdinfo_dir);
+		if (fd_dir)
+			closedir(fd_dir);
+		if (pid_dir)
+			closedir(pid_dir);
+	}
+
+	closedir(proc_dir);
+
+	for_each_client(clients, c, tmp) {
+		if (c->status == PROBE)
+			free_client(c);
+		else if (c->status == FREE)
+			break;
+	}
+
+	return display_clients(clients);
+}
+
 static const char *bars[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" };
 
 static void n_spaces(const unsigned int n)
@@ -776,6 +1320,18 @@ json_close_struct(void)
 		fflush(stdout);
 }
 
+static void
+__json_add_member(const char *key, const char *val)
+{
+	assert(json_indent_level < ARRAY_SIZE(json_indent));
+
+	fprintf(out, "%s%s\"%s\": \"%s\"",
+		json_struct_members ? ",\n" : "",
+		json_indent[json_indent_level], key, val);
+
+	json_struct_members++;
+}
+
 static unsigned int
 json_add_member(const struct cnt_group *parent, struct cnt_item *item,
 		unsigned int headers)
@@ -1501,6 +2057,157 @@ print_engines(struct engines *engines, double t, int lines, int w, int h)
 	return lines;
 }
 
+static int
+print_clients_header(struct clients *clients, int lines,
+		     int con_w, int con_h, int *class_w)
+{
+	if (output_mode == INTERACTIVE) {
+		const char *pidname = "   PID              NAME ";
+		unsigned int num_active = 0;
+		int len = strlen(pidname);
+
+		if (lines++ >= con_h)
+			return lines;
+
+		printf("\033[7m");
+		printf("%s", pidname);
+
+		if (lines++ >= con_h || len >= con_w)
+			return lines;
+
+		if (clients->num_classes) {
+			unsigned int i;
+			int width;
+
+			for (i = 0; i < clients->num_classes; i++) {
+				if (clients->class[i].num_engines)
+					num_active++;
+			}
+
+			*class_w = width = (con_w - len) / num_active;
+
+			for (i = 0; i < clients->num_classes; i++) {
+				const char *name = clients->class[i].name;
+				int name_len = strlen(name);
+				int pad = (width - name_len) / 2;
+				int spaces = width - pad - name_len;
+
+				if (!clients->class[i].num_engines)
+					continue; /* Assert in the ideal world. */
+
+				if (pad < 0 || spaces < 0)
+					continue;
+
+				n_spaces(pad);
+				printf("%s", name);
+				n_spaces(spaces);
+				len += pad + name_len + spaces;
+			}
+		}
+
+		n_spaces(con_w - len);
+		printf("\033[0m\n");
+	} else {
+		if (clients->num_classes)
+			pops->open_struct("clients");
+	}
+
+	return lines;
+}
+
+static bool numeric_clients;
+static bool filter_idle;
+
+static int
+print_client(struct client *c, struct engines *engines, double t, int lines,
+	     int con_w, int con_h, unsigned int period_us, int *class_w)
+{
+	struct clients *clients = c->clients;
+	unsigned int i;
+
+	if (output_mode == INTERACTIVE) {
+		if (filter_idle && (!c->total_runtime || c->samples < 2))
+			return lines;
+
+		lines++;
+
+		printf("%6u %17s ", c->pid, c->print_name);
+
+		for (i = 0; c->samples > 1 && i < clients->num_classes; i++) {
+			double pct;
+
+			if (!clients->class[i].num_engines)
+				continue; /* Assert in the ideal world. */
+
+			pct = (double)c->val[i] / period_us / 1e3 * 100 /
+			      clients->class[i].num_engines;
+
+			/*
+			 * Guard against possible time-drift between sampling
+			 * client data and time we obtained our time-delta from
+			 * PMU.
+			 */
+			if (pct > 100.0)
+				pct = 100.0;
+
+			print_percentage_bar(pct, *class_w, numeric_clients);
+		}
+
+		putchar('\n');
+	} else if (output_mode == JSON) {
+		char buf[64];
+
+		snprintf(buf, sizeof(buf), "%u", c->id);
+		pops->open_struct(buf);
+
+		__json_add_member("name", c->print_name);
+
+		snprintf(buf, sizeof(buf), "%u", c->pid);
+		__json_add_member("pid", buf);
+
+		if (c->samples > 1) {
+			pops->open_struct("engine-classes");
+
+			for (i = 0; i < clients->num_classes; i++) {
+				double pct;
+
+				snprintf(buf, sizeof(buf), "%s",
+					clients->class[i].name);
+				pops->open_struct(buf);
+
+				pct = (double)c->val[i] / period_us / 1e3 * 100;
+				snprintf(buf, sizeof(buf), "%f", pct);
+				__json_add_member("busy", buf);
+
+				__json_add_member("unit", "%");
+
+				pops->close_struct();
+			}
+
+			pops->close_struct();
+		}
+
+		pops->close_struct();
+	}
+
+	return lines;
+}
+
+static int
+print_clients_footer(struct clients *clients, double t,
+		     int lines, int con_w, int con_h)
+{
+	if (output_mode == INTERACTIVE) {
+		if (lines++ < con_h)
+			printf("\n");
+	} else {
+		if (clients->num_classes)
+			pops->close_struct();
+	}
+
+	return lines;
+}
+
 static void restore_term(void)
 {
 	tcsetattr(STDIN_FILENO, TCSANOW, &termios_orig);
@@ -1565,6 +2272,31 @@ static void interactive_stdin(void)
 	assert(ret == 0);
 }
 
+static void select_client_sort(void)
+{
+	struct {
+		int (*cmp)(const void *, const void *);
+		const char *msg;
+	} cmp[] = {
+		{ client_last_cmp, "Sorting clients by current GPU usage." },
+		{ client_total_cmp, "Sorting clients by accummulated GPU usage." },
+		{ client_pid_cmp, "Sorting clients by pid." },
+		{ client_id_cmp, "Sorting clients by DRM id." },
+	};
+	static unsigned int client_sort;
+
+bump:
+	if (++client_sort >= ARRAY_SIZE(cmp))
+		client_sort = 0;
+
+	client_cmp = cmp[client_sort].cmp;
+	header_msg = cmp[client_sort].msg;
+
+	/* Sort by client id makes no sense with pid aggregation. */
+	if (aggregate_pids && client_cmp == client_id_cmp)
+		goto bump;
+}
+
 static bool in_help;
 
 static void process_help_stdin(void)
@@ -1607,9 +2339,29 @@ static void process_normal_stdin(void)
 			else
 				header_msg = "Showing physical engines.";
 			break;
+		case 'i':
+			filter_idle ^= true;
+			if (filter_idle)
+				header_msg = "Hiding inactive clients.";
+			else
+				header_msg = "Showing inactive clients.";
+			break;
+		case 'n':
+			numeric_clients ^= true;
+			break;
+		case 's':
+			select_client_sort();
+			break;
 		case 'h':
 			in_help = true;
 			break;
+		case 'H':
+			aggregate_pids ^= true;
+			if (aggregate_pids)
+				header_msg = "Aggregating clients.";
+			else
+				header_msg = "Showing individual clients.";
+			break;
 		};
 	}
 }
@@ -1637,6 +2389,10 @@ static void show_help_screen(void)
 	printf(
 "Help for interactive commands:\n\n"
 "    '1'    Toggle between aggregated engine class and physical engine mode.\n"
+"    'n'    Toggle display of numeric client busyness overlay.\n"
+"    's'    Toggle between sort modes (runtime, total runtime, pid, client id).\n"
+"    'i'    Toggle display of clients which used no GPU time.\n"
+"    'H'    Toggle between per PID aggregation and individual clients.\n"
 "\n"
 "    'h' or 'q'    Exit interactive help.\n"
 "\n");
@@ -1645,6 +2401,7 @@ static void show_help_screen(void)
 int main(int argc, char **argv)
 {
 	unsigned int period_us = DEFAULT_PERIOD_MS * 1000;
+	struct clients *clients = NULL;
 	int con_w = -1, con_h = -1;
 	char *output_path = NULL;
 	struct engines *engines;
@@ -1783,15 +2540,24 @@ int main(int argc, char **argv)
 
 	ret = EXIT_SUCCESS;
 
+	clients = init_clients(card.pci_slot_name[0] ?
+			       card.pci_slot_name : IGPU_PCI);
 	init_engine_classes(engines);
+	if (clients) {
+		clients->num_classes = engines->num_classes;
+		clients->class = engines->class;
+	}
 
 	pmu_sample(engines);
+	scan_clients(clients);
 	codename = igt_device_get_pretty_name(&card, false);
 
 	while (!stop_top) {
+		struct clients *disp_clients;
 		bool consumed = false;
+		int j, lines = 0;
 		struct winsize ws;
-		int lines = 0;
+		struct client *c;
 		double t;
 
 		/* Update terminal size. */
@@ -1810,6 +2576,8 @@ int main(int argc, char **argv)
 		pmu_sample(engines);
 		t = (double)(engines->ts.cur - engines->ts.prev) / 1e9;
 
+		disp_clients = scan_clients(clients);
+
 		if (stop_top)
 			break;
 
@@ -1829,12 +2597,41 @@ int main(int argc, char **argv)
 
 			lines = print_engines(engines, t, lines, con_w, con_h);
 
+			if (disp_clients) {
+				int class_w;
+
+				lines = print_clients_header(disp_clients, lines,
+							     con_w, con_h,
+							     &class_w);
+
+				for_each_client(disp_clients, c, j) {
+					assert(c->status != PROBE);
+					if (c->status != ALIVE)
+						break; /* Active clients are first in the array. */
+
+					if (lines >= con_h)
+						break;
+
+					lines = print_client(c, engines, t,
+							     lines, con_w,
+							     con_h, period_us,
+							     &class_w);
+				}
+
+				lines = print_clients_footer(disp_clients, t,
+							     lines, con_w,
+							     con_h);
+			}
+
 			pops->close_struct();
 		}
 
 		if (stop_top)
 			break;
 
+		if (disp_clients != clients)
+			free_clients(disp_clients);
+
 		if (output_mode == INTERACTIVE)
 			process_stdin(period_us);
 		else
diff --git a/tools/meson.build b/tools/meson.build
index b6b9753463a9..771d0b9e3d5d 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -91,7 +91,7 @@ install_subdir('registers', install_dir : datadir)
 executable('intel_gpu_top', 'intel_gpu_top.c',
 	   install : true,
 	   install_rpath : bindir_rpathdir,
-	   dependencies : [lib_igt_perf,lib_igt_device_scan,math])
+	   dependencies : [lib_igt_perf,lib_igt_device_scan,lib_igt_drm_fdinfo,math])
 
 executable('amd_hdmi_compliance', 'amd_hdmi_compliance.c',
 	   dependencies : [tool_deps],
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [Intel-gfx] [PATCH i-g-t 03/11] intel-gpu-top: Add support for per client stats
@ 2022-02-22 13:55   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Use the i915 exported data in /proc/<pid>/fdinfo to show GPU utilization
per DRM client.

Example of the output:

intel-gpu-top: Intel Tigerlake (Gen12) @ /dev/dri/card0 -  220/ 221 MHz
    70% RC6;  0.62/ 7.08 W;      760 irqs/s

         ENGINES     BUSY                                 MI_SEMA MI_WAIT
       Render/3D   23.06% |██████▊                      |      0%      0%
         Blitter    0.00% |                             |      0%      0%
           Video    5.40% |█▋                           |      0%      0%
    VideoEnhance   20.67% |██████                       |      0%      0%

   PID              NAME  Render/3D    Blitter      Video    VideoEnhance
  3082               mpv |          ||          ||▌         ||██        |
  3117         neverball |█▉        ||          ||          ||          |
     1           systemd |▍         ||          ||          ||          |
  2338       gnome-shell |          ||          ||          ||          |

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 man/intel_gpu_top.rst |   4 +
 tools/intel_gpu_top.c | 801 +++++++++++++++++++++++++++++++++++++++++-
 tools/meson.build     |   2 +-
 3 files changed, 804 insertions(+), 3 deletions(-)

diff --git a/man/intel_gpu_top.rst b/man/intel_gpu_top.rst
index b3b765b05feb..f4dbfc5b44d9 100644
--- a/man/intel_gpu_top.rst
+++ b/man/intel_gpu_top.rst
@@ -56,6 +56,10 @@ Supported keys:
     'q'    Exit from the tool.
     'h'    Show interactive help.
     '1'    Toggle between aggregated engine class and physical engine mode.
+    'n'    Toggle display of numeric client busyness overlay.
+    's'    Toggle between sort modes (runtime, total runtime, pid, client id).
+    'i'    Toggle display of clients which used no GPU time.
+    'H'    Toggle between per PID aggregation and individual clients.
 
 DEVICE SELECTION
 ================
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index bc11fce2bb1e..73815cdea8aa 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -43,8 +43,10 @@
 #include <sys/types.h>
 #include <unistd.h>
 #include <termios.h>
+#include <sys/sysmacros.h>
 
 #include "igt_perf.h"
+#include "igt_drm_fdinfo.h"
 
 #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
 
@@ -311,7 +313,8 @@ static int engine_cmp(const void *__a, const void *__b)
 		return a->instance - b->instance;
 }
 
-#define is_igpu_pci(x) (strcmp(x, "0000:00:02.0") == 0)
+#define IGPU_PCI "0000:00:02.0"
+#define is_igpu_pci(x) (strcmp(x, IGPU_PCI) == 0)
 #define is_igpu(x) (strcmp(x, "i915") == 0)
 
 static struct engines *discover_engines(char *device)
@@ -635,6 +638,547 @@ static void pmu_sample(struct engines *engines)
 	}
 }
 
+enum client_status {
+	FREE = 0, /* mbz */
+	ALIVE,
+	PROBE
+};
+
+struct clients;
+
+struct client {
+	struct clients *clients;
+
+	enum client_status status;
+	unsigned int id;
+	unsigned int pid;
+	char name[24];
+	char print_name[24];
+	unsigned int samples;
+	unsigned long total_runtime;
+	unsigned long last_runtime;
+	unsigned long *val;
+	uint64_t *last;
+};
+
+struct clients {
+	unsigned int num_clients;
+	unsigned int active_clients;
+
+	unsigned int num_classes;
+	struct engine_class *class;
+
+	char pci_slot[64];
+
+	struct client *client;
+};
+
+#define for_each_client(clients, c, tmp) \
+	for ((tmp) = (clients)->num_clients, c = (clients)->client; \
+	     (tmp > 0); (tmp)--, (c)++)
+
+static struct clients *init_clients(const char *pci_slot)
+{
+	struct clients *clients;
+
+	clients = malloc(sizeof(*clients));
+	if (!clients)
+		return NULL;
+
+	memset(clients, 0, sizeof(*clients));
+
+	strncpy(clients->pci_slot, pci_slot, sizeof(clients->pci_slot));
+
+	return clients;
+}
+
+static struct client *
+find_client(struct clients *clients, enum client_status status, unsigned int id)
+{
+	unsigned int start, num;
+	struct client *c;
+
+	start = status == FREE ? clients->active_clients : 0; /* Free block at the end. */
+	num = clients->num_clients - start;
+
+	for (c = &clients->client[start]; num; c++, num--) {
+		if (status != c->status)
+			continue;
+
+		if (status == FREE || c->id == id)
+			return c;
+	}
+
+	return NULL;
+}
+
+static void
+update_client(struct client *c, unsigned int pid, char *name, uint64_t val[16])
+{
+	unsigned int i;
+
+	if (c->pid != pid)
+		c->pid = pid;
+
+	if (strcmp(c->name, name)) {
+		char *p;
+
+		strncpy(c->name, name, sizeof(c->name) - 1);
+		strncpy(c->print_name, name, sizeof(c->print_name) - 1);
+
+		p = c->print_name;
+		while (*p) {
+			if (!isprint(*p))
+				*p = '*';
+			p++;
+		}
+	}
+
+	c->last_runtime = 0;
+	c->total_runtime = 0;
+
+	for (i = 0; i < c->clients->num_classes; i++) {
+		if (val[i] < c->last[i])
+			continue; /* It will catch up soon. */
+
+		c->total_runtime += val[i];
+		c->val[i] = val[i] - c->last[i];
+		c->last_runtime += c->val[i];
+		c->last[i] = val[i];
+	}
+
+	c->samples++;
+	c->status = ALIVE;
+}
+
+static void
+add_client(struct clients *clients, unsigned int id, unsigned int pid,
+	   char *name, uint64_t busy[16])
+{
+	struct client *c;
+
+	assert(!find_client(clients, ALIVE, id));
+
+	c = find_client(clients, FREE, 0);
+	if (!c) {
+		unsigned int idx = clients->num_clients;
+
+		clients->num_clients += (clients->num_clients + 2) / 2;
+		clients->client = realloc(clients->client,
+					  clients->num_clients * sizeof(*c));
+		assert(clients->client);
+
+		c = &clients->client[idx];
+		memset(c, 0, (clients->num_clients - idx) * sizeof(*c));
+	}
+
+	c->id = id;
+	c->clients = clients;
+	c->val = calloc(clients->num_classes, sizeof(c->val));
+	c->last = calloc(clients->num_classes, sizeof(c->last));
+	assert(c->val && c->last);
+
+	update_client(c, pid, name, busy);
+}
+
+static void free_client(struct client *c)
+{
+	free(c->val);
+	free(c->last);
+	memset(c, 0, sizeof(*c));
+}
+
+static int client_last_cmp(const void *_a, const void *_b)
+{
+	const struct client *a = _a;
+	const struct client *b = _b;
+	long tot_a, tot_b;
+
+	/*
+	 * Sort clients in descending order of runtime in the previous sampling
+	 * period for active ones, followed by inactive. Tie-breaker is client
+	 * id.
+	 */
+
+	tot_a = a->status == ALIVE ? a->last_runtime : -1;
+	tot_b = b->status == ALIVE ? b->last_runtime : -1;
+
+	tot_b -= tot_a;
+	if (tot_b > 0)
+		return 1;
+	if (tot_b < 0)
+		return -1;
+
+	return (int)b->id - a->id;
+}
+
+static int client_total_cmp(const void *_a, const void *_b)
+{
+	const struct client *a = _a;
+	const struct client *b = _b;
+	long tot_a, tot_b;
+
+	tot_a = a->status == ALIVE ? a->total_runtime : -1;
+	tot_b = b->status == ALIVE ? b->total_runtime : -1;
+
+	tot_b -= tot_a;
+	if (tot_b > 0)
+		return 1;
+	if (tot_b < 0)
+		return -1;
+
+	return (int)b->id - a->id;
+}
+
+static int client_id_cmp(const void *_a, const void *_b)
+{
+	const struct client *a = _a;
+	const struct client *b = _b;
+	int id_a, id_b;
+
+	id_a = a->status == ALIVE ? a->id : -1;
+	id_b = b->status == ALIVE ? b->id : -1;
+
+	id_b -= id_a;
+	if (id_b > 0)
+		return 1;
+	if (id_b < 0)
+		return -1;
+
+	return (int)b->id - a->id;
+}
+
+static int client_pid_cmp(const void *_a, const void *_b)
+{
+	const struct client *a = _a;
+	const struct client *b = _b;
+	int pid_a, pid_b;
+
+	pid_a = a->status == ALIVE ? a->pid : INT_MAX;
+	pid_b = b->status == ALIVE ? b->pid : INT_MAX;
+
+	pid_b -= pid_a;
+	if (pid_b > 0)
+		return -1;
+	if (pid_b < 0)
+		return 1;
+
+	return (int)a->id - b->id;
+}
+
+static int (*client_cmp)(const void *, const void *) = client_last_cmp;
+
+static struct clients *sort_clients(struct clients *clients,
+				    int (*cmp)(const void *, const void *))
+{
+	unsigned int active, free;
+	struct client *c;
+	int tmp;
+
+	if (!clients)
+		return clients;
+
+	qsort(clients->client, clients->num_clients, sizeof(*clients->client),
+	      cmp);
+
+	/* Trim excessive array space. */
+	active = 0;
+	for_each_client(clients, c, tmp) {
+		if (c->status != ALIVE)
+			break; /* Active clients are first in the array. */
+		active++;
+	}
+
+	clients->active_clients = active;
+
+	free = clients->num_clients - active;
+	if (free > clients->num_clients / 2) {
+		active = clients->num_clients - free / 2;
+		if (active != clients->num_clients) {
+			clients->num_clients = active;
+			clients->client = realloc(clients->client,
+						  clients->num_clients *
+						  sizeof(*c));
+		}
+	}
+
+	return clients;
+}
+
+static bool aggregate_pids = true;
+
+static struct clients *display_clients(struct clients *clients)
+{
+	struct client *ac, *c, *cp = NULL;
+	struct clients *aggregated;
+	int tmp, num = 0;
+
+	if (!aggregate_pids)
+		goto out;
+
+	/* Sort by pid first to make it easy to aggregate while walking. */
+	sort_clients(clients, client_pid_cmp);
+
+	aggregated = calloc(1, sizeof(*clients));
+	assert(aggregated);
+
+	ac = calloc(clients->num_clients, sizeof(*c));
+	assert(ac);
+
+	aggregated->num_classes = clients->num_classes;
+	aggregated->class = clients->class;
+	aggregated->client = ac;
+
+	for_each_client(clients, c, tmp) {
+		unsigned int i;
+
+		if (c->status == FREE)
+			break;
+
+		assert(c->status == ALIVE);
+
+		if ((cp && c->pid != cp->pid) || !cp) {
+			ac = &aggregated->client[num++];
+
+			/* New pid. */
+			ac->clients = aggregated;
+			ac->status = ALIVE;
+			ac->id = -c->pid;
+			ac->pid = c->pid;
+			strcpy(ac->name, c->name);
+			strcpy(ac->print_name, c->print_name);
+			ac->val = calloc(clients->num_classes,
+					 sizeof(ac->val[0]));
+			assert(ac->val);
+			ac->samples = 1;
+		}
+
+		cp = c;
+
+		if (c->samples < 2)
+			continue;
+
+		ac->samples = 2; /* All what matters for display. */
+		ac->total_runtime += c->total_runtime;
+		ac->last_runtime += c->last_runtime;
+
+		for (i = 0; i < clients->num_classes; i++)
+			ac->val[i] += c->val[i];
+	}
+
+	aggregated->num_clients = num;
+	aggregated->active_clients = num;
+
+	clients = aggregated;
+
+out:
+	return sort_clients(clients, client_cmp);
+}
+
+static void free_clients(struct clients *clients)
+{
+	struct client *c;
+	unsigned int tmp;
+
+	for_each_client(clients, c, tmp) {
+		free(c->val);
+		free(c->last);
+	}
+
+	free(clients->client);
+	free(clients);
+}
+
+static bool is_drm_fd(DIR *fd_dir, const char *name)
+{
+	struct stat stat;
+	int ret;
+
+	ret = fstatat(dirfd(fd_dir), name, &stat, 0);
+
+	return ret == 0 &&
+	       (stat.st_mode & S_IFMT) == S_IFCHR &&
+	       major(stat.st_rdev) == 226;
+}
+
+static bool get_task_name(const char *buffer, char *out, unsigned long sz)
+{
+	char *s = index(buffer, '(');
+	char *e = rindex(buffer, ')');
+	unsigned int len;
+
+	if (!s || !e)
+		return false;
+
+	len = --e - ++s + 1;
+	if(!len || (len + 1) >= sz)
+		return false;
+
+	strncpy(out, s, len);
+	out[len] = 0;
+
+	return true;
+}
+
+static DIR *opendirat(DIR *at, const char *name)
+{
+	DIR *dir;
+	int fd;
+
+	fd = openat(dirfd(at), name, O_DIRECTORY);
+	if (fd < 0)
+		return NULL;
+
+	dir = fdopendir(fd);
+	if (!dir)
+		close(fd);
+
+	return dir;
+}
+
+static FILE *fropenat(DIR *at, const char *name)
+{
+	FILE *f;
+	int fd;
+
+	fd = openat(dirfd(at), name, O_RDONLY);
+	if (fd < 0)
+		return NULL;
+
+	f = fdopen(fd, "r");
+	if (!f)
+		close(fd);
+
+	return f;
+}
+
+static size_t freadat2buf(char *buf, const size_t sz, DIR *at, const char *name)
+{
+	size_t count;
+	FILE *f;
+
+	f = fropenat(at, name);
+	if (!f)
+		return 0;
+
+	memset(buf, 0, sz);
+	count = fread(buf, 1, sz, f);
+	fclose(f);
+
+	return count;
+}
+
+static struct clients *scan_clients(struct clients *clients)
+{
+	struct dirent *proc_dent;
+	struct client *c;
+	DIR *proc_dir;
+	int tmp;
+
+	if (!clients)
+		return clients;
+
+	for_each_client(clients, c, tmp) {
+		assert(c->status != PROBE);
+		if (c->status == ALIVE)
+			c->status = PROBE;
+		else
+			break; /* Free block at the end of array. */
+	}
+
+	proc_dir = opendir("/proc");
+	if (!proc_dir)
+		return clients;
+
+	while ((proc_dent = readdir(proc_dir)) != NULL) {
+		DIR *pid_dir = NULL, *fd_dir = NULL, *fdinfo_dir = NULL;
+		struct dirent *fdinfo_dent;
+		char client_name[64] = { };
+		unsigned int client_pid;
+		char buf[4096];
+		size_t count;
+
+		if (proc_dent->d_type != DT_DIR)
+			continue;
+		if (!isdigit(proc_dent->d_name[0]))
+			continue;
+
+		pid_dir = opendirat(proc_dir, proc_dent->d_name);
+		if (!pid_dir)
+			continue;
+
+		count = freadat2buf(buf, sizeof(buf), pid_dir, "stat");
+		if (!count)
+			goto next;
+
+		client_pid = atoi(buf);
+		if (!client_pid)
+			goto next;
+
+		if (!get_task_name(buf, client_name, sizeof(client_name)))
+			goto next;
+
+		fd_dir = opendirat(pid_dir, "fd");
+		if (!fd_dir)
+			goto next;
+
+		fdinfo_dir = opendirat(pid_dir, "fdinfo");
+		if (!fdinfo_dir)
+			goto next;
+
+		while ((fdinfo_dent = readdir(fdinfo_dir)) != NULL) {
+			struct drm_client_fdinfo info = { };
+
+			if (fdinfo_dent->d_type != DT_REG)
+				continue;
+			if (!isdigit(fdinfo_dent->d_name[0]))
+				continue;
+
+			if (!is_drm_fd(fd_dir, fdinfo_dent->d_name))
+				continue;
+
+			if (!__igt_parse_drm_fdinfo(dirfd(fdinfo_dir),
+						    fdinfo_dent->d_name,
+						    &info))
+				continue;
+
+			if (strcmp(info.driver, "i915"))
+				continue;
+			if (strcmp(info.pdev, clients->pci_slot))
+				continue;
+			if (find_client(clients, ALIVE, info.id))
+				continue; /* Skip duplicate fds. */
+
+			c = find_client(clients, PROBE, info.id);
+			if (!c)
+				add_client(clients, info.id, client_pid,
+					   client_name, info.busy);
+			else
+				update_client(c, client_pid, client_name,
+					      info.busy);
+		}
+
+next:
+		if (fdinfo_dir)
+			closedir(fdinfo_dir);
+		if (fd_dir)
+			closedir(fd_dir);
+		if (pid_dir)
+			closedir(pid_dir);
+	}
+
+	closedir(proc_dir);
+
+	for_each_client(clients, c, tmp) {
+		if (c->status == PROBE)
+			free_client(c);
+		else if (c->status == FREE)
+			break;
+	}
+
+	return display_clients(clients);
+}
+
 static const char *bars[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" };
 
 static void n_spaces(const unsigned int n)
@@ -776,6 +1320,18 @@ json_close_struct(void)
 		fflush(stdout);
 }
 
+static void
+__json_add_member(const char *key, const char *val)
+{
+	assert(json_indent_level < ARRAY_SIZE(json_indent));
+
+	fprintf(out, "%s%s\"%s\": \"%s\"",
+		json_struct_members ? ",\n" : "",
+		json_indent[json_indent_level], key, val);
+
+	json_struct_members++;
+}
+
 static unsigned int
 json_add_member(const struct cnt_group *parent, struct cnt_item *item,
 		unsigned int headers)
@@ -1501,6 +2057,157 @@ print_engines(struct engines *engines, double t, int lines, int w, int h)
 	return lines;
 }
 
+static int
+print_clients_header(struct clients *clients, int lines,
+		     int con_w, int con_h, int *class_w)
+{
+	if (output_mode == INTERACTIVE) {
+		const char *pidname = "   PID              NAME ";
+		unsigned int num_active = 0;
+		int len = strlen(pidname);
+
+		if (lines++ >= con_h)
+			return lines;
+
+		printf("\033[7m");
+		printf("%s", pidname);
+
+		if (lines++ >= con_h || len >= con_w)
+			return lines;
+
+		if (clients->num_classes) {
+			unsigned int i;
+			int width;
+
+			for (i = 0; i < clients->num_classes; i++) {
+				if (clients->class[i].num_engines)
+					num_active++;
+			}
+
+			*class_w = width = (con_w - len) / num_active;
+
+			for (i = 0; i < clients->num_classes; i++) {
+				const char *name = clients->class[i].name;
+				int name_len = strlen(name);
+				int pad = (width - name_len) / 2;
+				int spaces = width - pad - name_len;
+
+				if (!clients->class[i].num_engines)
+					continue; /* Assert in the ideal world. */
+
+				if (pad < 0 || spaces < 0)
+					continue;
+
+				n_spaces(pad);
+				printf("%s", name);
+				n_spaces(spaces);
+				len += pad + name_len + spaces;
+			}
+		}
+
+		n_spaces(con_w - len);
+		printf("\033[0m\n");
+	} else {
+		if (clients->num_classes)
+			pops->open_struct("clients");
+	}
+
+	return lines;
+}
+
+static bool numeric_clients;
+static bool filter_idle;
+
+static int
+print_client(struct client *c, struct engines *engines, double t, int lines,
+	     int con_w, int con_h, unsigned int period_us, int *class_w)
+{
+	struct clients *clients = c->clients;
+	unsigned int i;
+
+	if (output_mode == INTERACTIVE) {
+		if (filter_idle && (!c->total_runtime || c->samples < 2))
+			return lines;
+
+		lines++;
+
+		printf("%6u %17s ", c->pid, c->print_name);
+
+		for (i = 0; c->samples > 1 && i < clients->num_classes; i++) {
+			double pct;
+
+			if (!clients->class[i].num_engines)
+				continue; /* Assert in the ideal world. */
+
+			pct = (double)c->val[i] / period_us / 1e3 * 100 /
+			      clients->class[i].num_engines;
+
+			/*
+			 * Guard against possible time-drift between sampling
+			 * client data and time we obtained our time-delta from
+			 * PMU.
+			 */
+			if (pct > 100.0)
+				pct = 100.0;
+
+			print_percentage_bar(pct, *class_w, numeric_clients);
+		}
+
+		putchar('\n');
+	} else if (output_mode == JSON) {
+		char buf[64];
+
+		snprintf(buf, sizeof(buf), "%u", c->id);
+		pops->open_struct(buf);
+
+		__json_add_member("name", c->print_name);
+
+		snprintf(buf, sizeof(buf), "%u", c->pid);
+		__json_add_member("pid", buf);
+
+		if (c->samples > 1) {
+			pops->open_struct("engine-classes");
+
+			for (i = 0; i < clients->num_classes; i++) {
+				double pct;
+
+				snprintf(buf, sizeof(buf), "%s",
+					clients->class[i].name);
+				pops->open_struct(buf);
+
+				pct = (double)c->val[i] / period_us / 1e3 * 100;
+				snprintf(buf, sizeof(buf), "%f", pct);
+				__json_add_member("busy", buf);
+
+				__json_add_member("unit", "%");
+
+				pops->close_struct();
+			}
+
+			pops->close_struct();
+		}
+
+		pops->close_struct();
+	}
+
+	return lines;
+}
+
+static int
+print_clients_footer(struct clients *clients, double t,
+		     int lines, int con_w, int con_h)
+{
+	if (output_mode == INTERACTIVE) {
+		if (lines++ < con_h)
+			printf("\n");
+	} else {
+		if (clients->num_classes)
+			pops->close_struct();
+	}
+
+	return lines;
+}
+
 static void restore_term(void)
 {
 	tcsetattr(STDIN_FILENO, TCSANOW, &termios_orig);
@@ -1565,6 +2272,31 @@ static void interactive_stdin(void)
 	assert(ret == 0);
 }
 
+static void select_client_sort(void)
+{
+	struct {
+		int (*cmp)(const void *, const void *);
+		const char *msg;
+	} cmp[] = {
+		{ client_last_cmp, "Sorting clients by current GPU usage." },
+		{ client_total_cmp, "Sorting clients by accummulated GPU usage." },
+		{ client_pid_cmp, "Sorting clients by pid." },
+		{ client_id_cmp, "Sorting clients by DRM id." },
+	};
+	static unsigned int client_sort;
+
+bump:
+	if (++client_sort >= ARRAY_SIZE(cmp))
+		client_sort = 0;
+
+	client_cmp = cmp[client_sort].cmp;
+	header_msg = cmp[client_sort].msg;
+
+	/* Sort by client id makes no sense with pid aggregation. */
+	if (aggregate_pids && client_cmp == client_id_cmp)
+		goto bump;
+}
+
 static bool in_help;
 
 static void process_help_stdin(void)
@@ -1607,9 +2339,29 @@ static void process_normal_stdin(void)
 			else
 				header_msg = "Showing physical engines.";
 			break;
+		case 'i':
+			filter_idle ^= true;
+			if (filter_idle)
+				header_msg = "Hiding inactive clients.";
+			else
+				header_msg = "Showing inactive clients.";
+			break;
+		case 'n':
+			numeric_clients ^= true;
+			break;
+		case 's':
+			select_client_sort();
+			break;
 		case 'h':
 			in_help = true;
 			break;
+		case 'H':
+			aggregate_pids ^= true;
+			if (aggregate_pids)
+				header_msg = "Aggregating clients.";
+			else
+				header_msg = "Showing individual clients.";
+			break;
 		};
 	}
 }
@@ -1637,6 +2389,10 @@ static void show_help_screen(void)
 	printf(
 "Help for interactive commands:\n\n"
 "    '1'    Toggle between aggregated engine class and physical engine mode.\n"
+"    'n'    Toggle display of numeric client busyness overlay.\n"
+"    's'    Toggle between sort modes (runtime, total runtime, pid, client id).\n"
+"    'i'    Toggle display of clients which used no GPU time.\n"
+"    'H'    Toggle between per PID aggregation and individual clients.\n"
 "\n"
 "    'h' or 'q'    Exit interactive help.\n"
 "\n");
@@ -1645,6 +2401,7 @@ static void show_help_screen(void)
 int main(int argc, char **argv)
 {
 	unsigned int period_us = DEFAULT_PERIOD_MS * 1000;
+	struct clients *clients = NULL;
 	int con_w = -1, con_h = -1;
 	char *output_path = NULL;
 	struct engines *engines;
@@ -1783,15 +2540,24 @@ int main(int argc, char **argv)
 
 	ret = EXIT_SUCCESS;
 
+	clients = init_clients(card.pci_slot_name[0] ?
+			       card.pci_slot_name : IGPU_PCI);
 	init_engine_classes(engines);
+	if (clients) {
+		clients->num_classes = engines->num_classes;
+		clients->class = engines->class;
+	}
 
 	pmu_sample(engines);
+	scan_clients(clients);
 	codename = igt_device_get_pretty_name(&card, false);
 
 	while (!stop_top) {
+		struct clients *disp_clients;
 		bool consumed = false;
+		int j, lines = 0;
 		struct winsize ws;
-		int lines = 0;
+		struct client *c;
 		double t;
 
 		/* Update terminal size. */
@@ -1810,6 +2576,8 @@ int main(int argc, char **argv)
 		pmu_sample(engines);
 		t = (double)(engines->ts.cur - engines->ts.prev) / 1e9;
 
+		disp_clients = scan_clients(clients);
+
 		if (stop_top)
 			break;
 
@@ -1829,12 +2597,41 @@ int main(int argc, char **argv)
 
 			lines = print_engines(engines, t, lines, con_w, con_h);
 
+			if (disp_clients) {
+				int class_w;
+
+				lines = print_clients_header(disp_clients, lines,
+							     con_w, con_h,
+							     &class_w);
+
+				for_each_client(disp_clients, c, j) {
+					assert(c->status != PROBE);
+					if (c->status != ALIVE)
+						break; /* Active clients are first in the array. */
+
+					if (lines >= con_h)
+						break;
+
+					lines = print_client(c, engines, t,
+							     lines, con_w,
+							     con_h, period_us,
+							     &class_w);
+				}
+
+				lines = print_clients_footer(disp_clients, t,
+							     lines, con_w,
+							     con_h);
+			}
+
 			pops->close_struct();
 		}
 
 		if (stop_top)
 			break;
 
+		if (disp_clients != clients)
+			free_clients(disp_clients);
+
 		if (output_mode == INTERACTIVE)
 			process_stdin(period_us);
 		else
diff --git a/tools/meson.build b/tools/meson.build
index b6b9753463a9..771d0b9e3d5d 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -91,7 +91,7 @@ install_subdir('registers', install_dir : datadir)
 executable('intel_gpu_top', 'intel_gpu_top.c',
 	   install : true,
 	   install_rpath : bindir_rpathdir,
-	   dependencies : [lib_igt_perf,lib_igt_device_scan,math])
+	   dependencies : [lib_igt_perf,lib_igt_device_scan,lib_igt_drm_fdinfo,math])
 
 executable('amd_hdmi_compliance', 'amd_hdmi_compliance.c',
 	   dependencies : [tool_deps],
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [igt-dev] [PATCH i-g-t 03/11] intel-gpu-top: Add support for per client stats
@ 2022-02-22 13:55   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Use the i915 exported data in /proc/<pid>/fdinfo to show GPU utilization
per DRM client.

Example of the output:

intel-gpu-top: Intel Tigerlake (Gen12) @ /dev/dri/card0 -  220/ 221 MHz
    70% RC6;  0.62/ 7.08 W;      760 irqs/s

         ENGINES     BUSY                                 MI_SEMA MI_WAIT
       Render/3D   23.06% |██████▊                      |      0%      0%
         Blitter    0.00% |                             |      0%      0%
           Video    5.40% |█▋                           |      0%      0%
    VideoEnhance   20.67% |██████                       |      0%      0%

   PID              NAME  Render/3D    Blitter      Video    VideoEnhance
  3082               mpv |          ||          ||▌         ||██        |
  3117         neverball |█▉        ||          ||          ||          |
     1           systemd |▍         ||          ||          ||          |
  2338       gnome-shell |          ||          ||          ||          |

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 man/intel_gpu_top.rst |   4 +
 tools/intel_gpu_top.c | 801 +++++++++++++++++++++++++++++++++++++++++-
 tools/meson.build     |   2 +-
 3 files changed, 804 insertions(+), 3 deletions(-)

diff --git a/man/intel_gpu_top.rst b/man/intel_gpu_top.rst
index b3b765b05feb..f4dbfc5b44d9 100644
--- a/man/intel_gpu_top.rst
+++ b/man/intel_gpu_top.rst
@@ -56,6 +56,10 @@ Supported keys:
     'q'    Exit from the tool.
     'h'    Show interactive help.
     '1'    Toggle between aggregated engine class and physical engine mode.
+    'n'    Toggle display of numeric client busyness overlay.
+    's'    Toggle between sort modes (runtime, total runtime, pid, client id).
+    'i'    Toggle display of clients which used no GPU time.
+    'H'    Toggle between per PID aggregation and individual clients.
 
 DEVICE SELECTION
 ================
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index bc11fce2bb1e..73815cdea8aa 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -43,8 +43,10 @@
 #include <sys/types.h>
 #include <unistd.h>
 #include <termios.h>
+#include <sys/sysmacros.h>
 
 #include "igt_perf.h"
+#include "igt_drm_fdinfo.h"
 
 #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
 
@@ -311,7 +313,8 @@ static int engine_cmp(const void *__a, const void *__b)
 		return a->instance - b->instance;
 }
 
-#define is_igpu_pci(x) (strcmp(x, "0000:00:02.0") == 0)
+#define IGPU_PCI "0000:00:02.0"
+#define is_igpu_pci(x) (strcmp(x, IGPU_PCI) == 0)
 #define is_igpu(x) (strcmp(x, "i915") == 0)
 
 static struct engines *discover_engines(char *device)
@@ -635,6 +638,547 @@ static void pmu_sample(struct engines *engines)
 	}
 }
 
+enum client_status {
+	FREE = 0, /* mbz */
+	ALIVE,
+	PROBE
+};
+
+struct clients;
+
+struct client {
+	struct clients *clients;
+
+	enum client_status status;
+	unsigned int id;
+	unsigned int pid;
+	char name[24];
+	char print_name[24];
+	unsigned int samples;
+	unsigned long total_runtime;
+	unsigned long last_runtime;
+	unsigned long *val;
+	uint64_t *last;
+};
+
+struct clients {
+	unsigned int num_clients;
+	unsigned int active_clients;
+
+	unsigned int num_classes;
+	struct engine_class *class;
+
+	char pci_slot[64];
+
+	struct client *client;
+};
+
+#define for_each_client(clients, c, tmp) \
+	for ((tmp) = (clients)->num_clients, c = (clients)->client; \
+	     (tmp > 0); (tmp)--, (c)++)
+
+static struct clients *init_clients(const char *pci_slot)
+{
+	struct clients *clients;
+
+	clients = malloc(sizeof(*clients));
+	if (!clients)
+		return NULL;
+
+	memset(clients, 0, sizeof(*clients));
+
+	strncpy(clients->pci_slot, pci_slot, sizeof(clients->pci_slot));
+
+	return clients;
+}
+
+static struct client *
+find_client(struct clients *clients, enum client_status status, unsigned int id)
+{
+	unsigned int start, num;
+	struct client *c;
+
+	start = status == FREE ? clients->active_clients : 0; /* Free block at the end. */
+	num = clients->num_clients - start;
+
+	for (c = &clients->client[start]; num; c++, num--) {
+		if (status != c->status)
+			continue;
+
+		if (status == FREE || c->id == id)
+			return c;
+	}
+
+	return NULL;
+}
+
+static void
+update_client(struct client *c, unsigned int pid, char *name, uint64_t val[16])
+{
+	unsigned int i;
+
+	if (c->pid != pid)
+		c->pid = pid;
+
+	if (strcmp(c->name, name)) {
+		char *p;
+
+		strncpy(c->name, name, sizeof(c->name) - 1);
+		strncpy(c->print_name, name, sizeof(c->print_name) - 1);
+
+		p = c->print_name;
+		while (*p) {
+			if (!isprint(*p))
+				*p = '*';
+			p++;
+		}
+	}
+
+	c->last_runtime = 0;
+	c->total_runtime = 0;
+
+	for (i = 0; i < c->clients->num_classes; i++) {
+		if (val[i] < c->last[i])
+			continue; /* It will catch up soon. */
+
+		c->total_runtime += val[i];
+		c->val[i] = val[i] - c->last[i];
+		c->last_runtime += c->val[i];
+		c->last[i] = val[i];
+	}
+
+	c->samples++;
+	c->status = ALIVE;
+}
+
+static void
+add_client(struct clients *clients, unsigned int id, unsigned int pid,
+	   char *name, uint64_t busy[16])
+{
+	struct client *c;
+
+	assert(!find_client(clients, ALIVE, id));
+
+	c = find_client(clients, FREE, 0);
+	if (!c) {
+		unsigned int idx = clients->num_clients;
+
+		clients->num_clients += (clients->num_clients + 2) / 2;
+		clients->client = realloc(clients->client,
+					  clients->num_clients * sizeof(*c));
+		assert(clients->client);
+
+		c = &clients->client[idx];
+		memset(c, 0, (clients->num_clients - idx) * sizeof(*c));
+	}
+
+	c->id = id;
+	c->clients = clients;
+	c->val = calloc(clients->num_classes, sizeof(c->val));
+	c->last = calloc(clients->num_classes, sizeof(c->last));
+	assert(c->val && c->last);
+
+	update_client(c, pid, name, busy);
+}
+
+static void free_client(struct client *c)
+{
+	free(c->val);
+	free(c->last);
+	memset(c, 0, sizeof(*c));
+}
+
+static int client_last_cmp(const void *_a, const void *_b)
+{
+	const struct client *a = _a;
+	const struct client *b = _b;
+	long tot_a, tot_b;
+
+	/*
+	 * Sort clients in descending order of runtime in the previous sampling
+	 * period for active ones, followed by inactive. Tie-breaker is client
+	 * id.
+	 */
+
+	tot_a = a->status == ALIVE ? a->last_runtime : -1;
+	tot_b = b->status == ALIVE ? b->last_runtime : -1;
+
+	tot_b -= tot_a;
+	if (tot_b > 0)
+		return 1;
+	if (tot_b < 0)
+		return -1;
+
+	return (int)b->id - a->id;
+}
+
+static int client_total_cmp(const void *_a, const void *_b)
+{
+	const struct client *a = _a;
+	const struct client *b = _b;
+	long tot_a, tot_b;
+
+	tot_a = a->status == ALIVE ? a->total_runtime : -1;
+	tot_b = b->status == ALIVE ? b->total_runtime : -1;
+
+	tot_b -= tot_a;
+	if (tot_b > 0)
+		return 1;
+	if (tot_b < 0)
+		return -1;
+
+	return (int)b->id - a->id;
+}
+
+static int client_id_cmp(const void *_a, const void *_b)
+{
+	const struct client *a = _a;
+	const struct client *b = _b;
+	int id_a, id_b;
+
+	id_a = a->status == ALIVE ? a->id : -1;
+	id_b = b->status == ALIVE ? b->id : -1;
+
+	id_b -= id_a;
+	if (id_b > 0)
+		return 1;
+	if (id_b < 0)
+		return -1;
+
+	return (int)b->id - a->id;
+}
+
+static int client_pid_cmp(const void *_a, const void *_b)
+{
+	const struct client *a = _a;
+	const struct client *b = _b;
+	int pid_a, pid_b;
+
+	pid_a = a->status == ALIVE ? a->pid : INT_MAX;
+	pid_b = b->status == ALIVE ? b->pid : INT_MAX;
+
+	pid_b -= pid_a;
+	if (pid_b > 0)
+		return -1;
+	if (pid_b < 0)
+		return 1;
+
+	return (int)a->id - b->id;
+}
+
+static int (*client_cmp)(const void *, const void *) = client_last_cmp;
+
+static struct clients *sort_clients(struct clients *clients,
+				    int (*cmp)(const void *, const void *))
+{
+	unsigned int active, free;
+	struct client *c;
+	int tmp;
+
+	if (!clients)
+		return clients;
+
+	qsort(clients->client, clients->num_clients, sizeof(*clients->client),
+	      cmp);
+
+	/* Trim excessive array space. */
+	active = 0;
+	for_each_client(clients, c, tmp) {
+		if (c->status != ALIVE)
+			break; /* Active clients are first in the array. */
+		active++;
+	}
+
+	clients->active_clients = active;
+
+	free = clients->num_clients - active;
+	if (free > clients->num_clients / 2) {
+		active = clients->num_clients - free / 2;
+		if (active != clients->num_clients) {
+			clients->num_clients = active;
+			clients->client = realloc(clients->client,
+						  clients->num_clients *
+						  sizeof(*c));
+		}
+	}
+
+	return clients;
+}
+
+static bool aggregate_pids = true;
+
+static struct clients *display_clients(struct clients *clients)
+{
+	struct client *ac, *c, *cp = NULL;
+	struct clients *aggregated;
+	int tmp, num = 0;
+
+	if (!aggregate_pids)
+		goto out;
+
+	/* Sort by pid first to make it easy to aggregate while walking. */
+	sort_clients(clients, client_pid_cmp);
+
+	aggregated = calloc(1, sizeof(*clients));
+	assert(aggregated);
+
+	ac = calloc(clients->num_clients, sizeof(*c));
+	assert(ac);
+
+	aggregated->num_classes = clients->num_classes;
+	aggregated->class = clients->class;
+	aggregated->client = ac;
+
+	for_each_client(clients, c, tmp) {
+		unsigned int i;
+
+		if (c->status == FREE)
+			break;
+
+		assert(c->status == ALIVE);
+
+		if ((cp && c->pid != cp->pid) || !cp) {
+			ac = &aggregated->client[num++];
+
+			/* New pid. */
+			ac->clients = aggregated;
+			ac->status = ALIVE;
+			ac->id = -c->pid;
+			ac->pid = c->pid;
+			strcpy(ac->name, c->name);
+			strcpy(ac->print_name, c->print_name);
+			ac->val = calloc(clients->num_classes,
+					 sizeof(ac->val[0]));
+			assert(ac->val);
+			ac->samples = 1;
+		}
+
+		cp = c;
+
+		if (c->samples < 2)
+			continue;
+
+		ac->samples = 2; /* All what matters for display. */
+		ac->total_runtime += c->total_runtime;
+		ac->last_runtime += c->last_runtime;
+
+		for (i = 0; i < clients->num_classes; i++)
+			ac->val[i] += c->val[i];
+	}
+
+	aggregated->num_clients = num;
+	aggregated->active_clients = num;
+
+	clients = aggregated;
+
+out:
+	return sort_clients(clients, client_cmp);
+}
+
+static void free_clients(struct clients *clients)
+{
+	struct client *c;
+	unsigned int tmp;
+
+	for_each_client(clients, c, tmp) {
+		free(c->val);
+		free(c->last);
+	}
+
+	free(clients->client);
+	free(clients);
+}
+
+static bool is_drm_fd(DIR *fd_dir, const char *name)
+{
+	struct stat stat;
+	int ret;
+
+	ret = fstatat(dirfd(fd_dir), name, &stat, 0);
+
+	return ret == 0 &&
+	       (stat.st_mode & S_IFMT) == S_IFCHR &&
+	       major(stat.st_rdev) == 226;
+}
+
+static bool get_task_name(const char *buffer, char *out, unsigned long sz)
+{
+	char *s = index(buffer, '(');
+	char *e = rindex(buffer, ')');
+	unsigned int len;
+
+	if (!s || !e)
+		return false;
+
+	len = --e - ++s + 1;
+	if(!len || (len + 1) >= sz)
+		return false;
+
+	strncpy(out, s, len);
+	out[len] = 0;
+
+	return true;
+}
+
+static DIR *opendirat(DIR *at, const char *name)
+{
+	DIR *dir;
+	int fd;
+
+	fd = openat(dirfd(at), name, O_DIRECTORY);
+	if (fd < 0)
+		return NULL;
+
+	dir = fdopendir(fd);
+	if (!dir)
+		close(fd);
+
+	return dir;
+}
+
+static FILE *fropenat(DIR *at, const char *name)
+{
+	FILE *f;
+	int fd;
+
+	fd = openat(dirfd(at), name, O_RDONLY);
+	if (fd < 0)
+		return NULL;
+
+	f = fdopen(fd, "r");
+	if (!f)
+		close(fd);
+
+	return f;
+}
+
+static size_t freadat2buf(char *buf, const size_t sz, DIR *at, const char *name)
+{
+	size_t count;
+	FILE *f;
+
+	f = fropenat(at, name);
+	if (!f)
+		return 0;
+
+	memset(buf, 0, sz);
+	count = fread(buf, 1, sz, f);
+	fclose(f);
+
+	return count;
+}
+
+static struct clients *scan_clients(struct clients *clients)
+{
+	struct dirent *proc_dent;
+	struct client *c;
+	DIR *proc_dir;
+	int tmp;
+
+	if (!clients)
+		return clients;
+
+	for_each_client(clients, c, tmp) {
+		assert(c->status != PROBE);
+		if (c->status == ALIVE)
+			c->status = PROBE;
+		else
+			break; /* Free block at the end of array. */
+	}
+
+	proc_dir = opendir("/proc");
+	if (!proc_dir)
+		return clients;
+
+	while ((proc_dent = readdir(proc_dir)) != NULL) {
+		DIR *pid_dir = NULL, *fd_dir = NULL, *fdinfo_dir = NULL;
+		struct dirent *fdinfo_dent;
+		char client_name[64] = { };
+		unsigned int client_pid;
+		char buf[4096];
+		size_t count;
+
+		if (proc_dent->d_type != DT_DIR)
+			continue;
+		if (!isdigit(proc_dent->d_name[0]))
+			continue;
+
+		pid_dir = opendirat(proc_dir, proc_dent->d_name);
+		if (!pid_dir)
+			continue;
+
+		count = freadat2buf(buf, sizeof(buf), pid_dir, "stat");
+		if (!count)
+			goto next;
+
+		client_pid = atoi(buf);
+		if (!client_pid)
+			goto next;
+
+		if (!get_task_name(buf, client_name, sizeof(client_name)))
+			goto next;
+
+		fd_dir = opendirat(pid_dir, "fd");
+		if (!fd_dir)
+			goto next;
+
+		fdinfo_dir = opendirat(pid_dir, "fdinfo");
+		if (!fdinfo_dir)
+			goto next;
+
+		while ((fdinfo_dent = readdir(fdinfo_dir)) != NULL) {
+			struct drm_client_fdinfo info = { };
+
+			if (fdinfo_dent->d_type != DT_REG)
+				continue;
+			if (!isdigit(fdinfo_dent->d_name[0]))
+				continue;
+
+			if (!is_drm_fd(fd_dir, fdinfo_dent->d_name))
+				continue;
+
+			if (!__igt_parse_drm_fdinfo(dirfd(fdinfo_dir),
+						    fdinfo_dent->d_name,
+						    &info))
+				continue;
+
+			if (strcmp(info.driver, "i915"))
+				continue;
+			if (strcmp(info.pdev, clients->pci_slot))
+				continue;
+			if (find_client(clients, ALIVE, info.id))
+				continue; /* Skip duplicate fds. */
+
+			c = find_client(clients, PROBE, info.id);
+			if (!c)
+				add_client(clients, info.id, client_pid,
+					   client_name, info.busy);
+			else
+				update_client(c, client_pid, client_name,
+					      info.busy);
+		}
+
+next:
+		if (fdinfo_dir)
+			closedir(fdinfo_dir);
+		if (fd_dir)
+			closedir(fd_dir);
+		if (pid_dir)
+			closedir(pid_dir);
+	}
+
+	closedir(proc_dir);
+
+	for_each_client(clients, c, tmp) {
+		if (c->status == PROBE)
+			free_client(c);
+		else if (c->status == FREE)
+			break;
+	}
+
+	return display_clients(clients);
+}
+
 static const char *bars[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" };
 
 static void n_spaces(const unsigned int n)
@@ -776,6 +1320,18 @@ json_close_struct(void)
 		fflush(stdout);
 }
 
+static void
+__json_add_member(const char *key, const char *val)
+{
+	assert(json_indent_level < ARRAY_SIZE(json_indent));
+
+	fprintf(out, "%s%s\"%s\": \"%s\"",
+		json_struct_members ? ",\n" : "",
+		json_indent[json_indent_level], key, val);
+
+	json_struct_members++;
+}
+
 static unsigned int
 json_add_member(const struct cnt_group *parent, struct cnt_item *item,
 		unsigned int headers)
@@ -1501,6 +2057,157 @@ print_engines(struct engines *engines, double t, int lines, int w, int h)
 	return lines;
 }
 
+static int
+print_clients_header(struct clients *clients, int lines,
+		     int con_w, int con_h, int *class_w)
+{
+	if (output_mode == INTERACTIVE) {
+		const char *pidname = "   PID              NAME ";
+		unsigned int num_active = 0;
+		int len = strlen(pidname);
+
+		if (lines++ >= con_h)
+			return lines;
+
+		printf("\033[7m");
+		printf("%s", pidname);
+
+		if (lines++ >= con_h || len >= con_w)
+			return lines;
+
+		if (clients->num_classes) {
+			unsigned int i;
+			int width;
+
+			for (i = 0; i < clients->num_classes; i++) {
+				if (clients->class[i].num_engines)
+					num_active++;
+			}
+
+			*class_w = width = (con_w - len) / num_active;
+
+			for (i = 0; i < clients->num_classes; i++) {
+				const char *name = clients->class[i].name;
+				int name_len = strlen(name);
+				int pad = (width - name_len) / 2;
+				int spaces = width - pad - name_len;
+
+				if (!clients->class[i].num_engines)
+					continue; /* Assert in the ideal world. */
+
+				if (pad < 0 || spaces < 0)
+					continue;
+
+				n_spaces(pad);
+				printf("%s", name);
+				n_spaces(spaces);
+				len += pad + name_len + spaces;
+			}
+		}
+
+		n_spaces(con_w - len);
+		printf("\033[0m\n");
+	} else {
+		if (clients->num_classes)
+			pops->open_struct("clients");
+	}
+
+	return lines;
+}
+
+static bool numeric_clients;
+static bool filter_idle;
+
+static int
+print_client(struct client *c, struct engines *engines, double t, int lines,
+	     int con_w, int con_h, unsigned int period_us, int *class_w)
+{
+	struct clients *clients = c->clients;
+	unsigned int i;
+
+	if (output_mode == INTERACTIVE) {
+		if (filter_idle && (!c->total_runtime || c->samples < 2))
+			return lines;
+
+		lines++;
+
+		printf("%6u %17s ", c->pid, c->print_name);
+
+		for (i = 0; c->samples > 1 && i < clients->num_classes; i++) {
+			double pct;
+
+			if (!clients->class[i].num_engines)
+				continue; /* Assert in the ideal world. */
+
+			pct = (double)c->val[i] / period_us / 1e3 * 100 /
+			      clients->class[i].num_engines;
+
+			/*
+			 * Guard against possible time-drift between sampling
+			 * client data and time we obtained our time-delta from
+			 * PMU.
+			 */
+			if (pct > 100.0)
+				pct = 100.0;
+
+			print_percentage_bar(pct, *class_w, numeric_clients);
+		}
+
+		putchar('\n');
+	} else if (output_mode == JSON) {
+		char buf[64];
+
+		snprintf(buf, sizeof(buf), "%u", c->id);
+		pops->open_struct(buf);
+
+		__json_add_member("name", c->print_name);
+
+		snprintf(buf, sizeof(buf), "%u", c->pid);
+		__json_add_member("pid", buf);
+
+		if (c->samples > 1) {
+			pops->open_struct("engine-classes");
+
+			for (i = 0; i < clients->num_classes; i++) {
+				double pct;
+
+				snprintf(buf, sizeof(buf), "%s",
+					clients->class[i].name);
+				pops->open_struct(buf);
+
+				pct = (double)c->val[i] / period_us / 1e3 * 100;
+				snprintf(buf, sizeof(buf), "%f", pct);
+				__json_add_member("busy", buf);
+
+				__json_add_member("unit", "%");
+
+				pops->close_struct();
+			}
+
+			pops->close_struct();
+		}
+
+		pops->close_struct();
+	}
+
+	return lines;
+}
+
+static int
+print_clients_footer(struct clients *clients, double t,
+		     int lines, int con_w, int con_h)
+{
+	if (output_mode == INTERACTIVE) {
+		if (lines++ < con_h)
+			printf("\n");
+	} else {
+		if (clients->num_classes)
+			pops->close_struct();
+	}
+
+	return lines;
+}
+
 static void restore_term(void)
 {
 	tcsetattr(STDIN_FILENO, TCSANOW, &termios_orig);
@@ -1565,6 +2272,31 @@ static void interactive_stdin(void)
 	assert(ret == 0);
 }
 
+static void select_client_sort(void)
+{
+	struct {
+		int (*cmp)(const void *, const void *);
+		const char *msg;
+	} cmp[] = {
+		{ client_last_cmp, "Sorting clients by current GPU usage." },
+		{ client_total_cmp, "Sorting clients by accummulated GPU usage." },
+		{ client_pid_cmp, "Sorting clients by pid." },
+		{ client_id_cmp, "Sorting clients by DRM id." },
+	};
+	static unsigned int client_sort;
+
+bump:
+	if (++client_sort >= ARRAY_SIZE(cmp))
+		client_sort = 0;
+
+	client_cmp = cmp[client_sort].cmp;
+	header_msg = cmp[client_sort].msg;
+
+	/* Sort by client id makes no sense with pid aggregation. */
+	if (aggregate_pids && client_cmp == client_id_cmp)
+		goto bump;
+}
+
 static bool in_help;
 
 static void process_help_stdin(void)
@@ -1607,9 +2339,29 @@ static void process_normal_stdin(void)
 			else
 				header_msg = "Showing physical engines.";
 			break;
+		case 'i':
+			filter_idle ^= true;
+			if (filter_idle)
+				header_msg = "Hiding inactive clients.";
+			else
+				header_msg = "Showing inactive clients.";
+			break;
+		case 'n':
+			numeric_clients ^= true;
+			break;
+		case 's':
+			select_client_sort();
+			break;
 		case 'h':
 			in_help = true;
 			break;
+		case 'H':
+			aggregate_pids ^= true;
+			if (aggregate_pids)
+				header_msg = "Aggregating clients.";
+			else
+				header_msg = "Showing individual clients.";
+			break;
 		};
 	}
 }
@@ -1637,6 +2389,10 @@ static void show_help_screen(void)
 	printf(
 "Help for interactive commands:\n\n"
 "    '1'    Toggle between aggregated engine class and physical engine mode.\n"
+"    'n'    Toggle display of numeric client busyness overlay.\n"
+"    's'    Toggle between sort modes (runtime, total runtime, pid, client id).\n"
+"    'i'    Toggle display of clients which used no GPU time.\n"
+"    'H'    Toggle between per PID aggregation and individual clients.\n"
 "\n"
 "    'h' or 'q'    Exit interactive help.\n"
 "\n");
@@ -1645,6 +2401,7 @@ static void show_help_screen(void)
 int main(int argc, char **argv)
 {
 	unsigned int period_us = DEFAULT_PERIOD_MS * 1000;
+	struct clients *clients = NULL;
 	int con_w = -1, con_h = -1;
 	char *output_path = NULL;
 	struct engines *engines;
@@ -1783,15 +2540,24 @@ int main(int argc, char **argv)
 
 	ret = EXIT_SUCCESS;
 
+	clients = init_clients(card.pci_slot_name[0] ?
+			       card.pci_slot_name : IGPU_PCI);
 	init_engine_classes(engines);
+	if (clients) {
+		clients->num_classes = engines->num_classes;
+		clients->class = engines->class;
+	}
 
 	pmu_sample(engines);
+	scan_clients(clients);
 	codename = igt_device_get_pretty_name(&card, false);
 
 	while (!stop_top) {
+		struct clients *disp_clients;
 		bool consumed = false;
+		int j, lines = 0;
 		struct winsize ws;
-		int lines = 0;
+		struct client *c;
 		double t;
 
 		/* Update terminal size. */
@@ -1810,6 +2576,8 @@ int main(int argc, char **argv)
 		pmu_sample(engines);
 		t = (double)(engines->ts.cur - engines->ts.prev) / 1e9;
 
+		disp_clients = scan_clients(clients);
+
 		if (stop_top)
 			break;
 
@@ -1829,12 +2597,41 @@ int main(int argc, char **argv)
 
 			lines = print_engines(engines, t, lines, con_w, con_h);
 
+			if (disp_clients) {
+				int class_w;
+
+				lines = print_clients_header(disp_clients, lines,
+							     con_w, con_h,
+							     &class_w);
+
+				for_each_client(disp_clients, c, j) {
+					assert(c->status != PROBE);
+					if (c->status != ALIVE)
+						break; /* Active clients are first in the array. */
+
+					if (lines >= con_h)
+						break;
+
+					lines = print_client(c, engines, t,
+							     lines, con_w,
+							     con_h, period_us,
+							     &class_w);
+				}
+
+				lines = print_clients_footer(disp_clients, t,
+							     lines, con_w,
+							     con_h);
+			}
+
 			pops->close_struct();
 		}
 
 		if (stop_top)
 			break;
 
+		if (disp_clients != clients)
+			free_clients(disp_clients);
+
 		if (output_mode == INTERACTIVE)
 			process_stdin(period_us);
 		else
diff --git a/tools/meson.build b/tools/meson.build
index b6b9753463a9..771d0b9e3d5d 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -91,7 +91,7 @@ install_subdir('registers', install_dir : datadir)
 executable('intel_gpu_top', 'intel_gpu_top.c',
 	   install : true,
 	   install_rpath : bindir_rpathdir,
-	   dependencies : [lib_igt_perf,lib_igt_device_scan,math])
+	   dependencies : [lib_igt_perf,lib_igt_device_scan,lib_igt_drm_fdinfo,math])
 
 executable('amd_hdmi_compliance', 'amd_hdmi_compliance.c',
 	   dependencies : [tool_deps],
-- 
2.32.0

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH i-g-t 04/11] lib: Extract igt_drm_clients from intel_gpu_top
  2022-02-22 13:55 ` [Intel-gfx] " Tvrtko Ursulin
  (?)
@ 2022-02-22 13:55   ` Tvrtko Ursulin
  -1 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Code movement with some improvements to prepare for further work in
making a vendor agnostic gputop tool possible.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c | 386 +++++++++++++++++++++++++++++++
 lib/igt_drm_clients.h | 102 +++++++++
 lib/meson.build       |   8 +
 tools/intel_gpu_top.c | 516 ++++++------------------------------------
 tools/meson.build     |   2 +-
 5 files changed, 570 insertions(+), 444 deletions(-)
 create mode 100644 lib/igt_drm_clients.c
 create mode 100644 lib/igt_drm_clients.h

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
new file mode 100644
index 000000000000..58d82648d821
--- /dev/null
+++ b/lib/igt_drm_clients.c
@@ -0,0 +1,386 @@
+/*
+ * 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 <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "igt_drm_clients.h"
+#include "igt_drm_fdinfo.h"
+
+struct igt_drm_clients *igt_drm_clients_init(void *private_data)
+{
+	struct igt_drm_clients *clients;
+
+	clients = malloc(sizeof(*clients));
+	if (!clients)
+		return NULL;
+
+	memset(clients, 0, sizeof(*clients));
+
+	clients->private_data = private_data;
+
+	return clients;
+}
+
+struct igt_drm_client *
+igt_drm_clients_find(struct igt_drm_clients *clients,
+		     enum igt_drm_client_status status,
+		     unsigned int id)
+{
+	unsigned int start, num;
+	struct igt_drm_client *c;
+
+	start = status == IGT_DRM_CLIENT_FREE ? clients->active_clients : 0; /* Free block at the end. */
+	num = clients->num_clients - start;
+
+	for (c = &clients->client[start]; num; c++, num--) {
+		if (status != c->status)
+			continue;
+
+		if (status == IGT_DRM_CLIENT_FREE || c->id == id)
+			return c;
+	}
+
+	return NULL;
+}
+
+void
+igt_drm_client_update(struct igt_drm_client *c, unsigned int pid, char *name,
+		      uint64_t val[16])
+{
+	unsigned int i;
+
+	if (c->pid != pid)
+		c->pid = pid;
+
+	if (strcmp(c->name, name)) {
+		char *p;
+
+		strncpy(c->name, name, sizeof(c->name) - 1);
+		strncpy(c->print_name, name, sizeof(c->print_name) - 1);
+
+		p = c->print_name;
+		while (*p) {
+			if (!isprint(*p))
+				*p = '*';
+			p++;
+		}
+	}
+
+	c->last_runtime = 0;
+	c->total_runtime = 0;
+
+	for (i = 0; i < c->clients->num_classes; i++) {
+		if (val[i] < c->last[i])
+			continue; /* It will catch up soon. */
+
+		c->total_runtime += val[i];
+		c->val[i] = val[i] - c->last[i];
+		c->last_runtime += c->val[i];
+		c->last[i] = val[i];
+	}
+
+	c->samples++;
+	c->status = IGT_DRM_CLIENT_ALIVE;
+}
+
+void
+igt_drm_client_add(struct igt_drm_clients *clients, unsigned int id,
+		   unsigned int pid, char *name, uint64_t busy[16])
+{
+	struct igt_drm_client *c;
+
+	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE, id));
+
+	c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_FREE, 0);
+	if (!c) {
+		unsigned int idx = clients->num_clients;
+
+		clients->num_clients += (clients->num_clients + 2) / 2;
+		clients->client = realloc(clients->client,
+					  clients->num_clients * sizeof(*c));
+		assert(clients->client);
+
+		c = &clients->client[idx];
+		memset(c, 0, (clients->num_clients - idx) * sizeof(*c));
+	}
+
+	c->id = id;
+	c->clients = clients;
+	c->val = calloc(clients->num_classes, sizeof(c->val));
+	c->last = calloc(clients->num_classes, sizeof(c->last));
+	assert(c->val && c->last);
+
+	igt_drm_client_update(c, pid, name, busy);
+}
+
+void igt_drm_client_free(struct igt_drm_client *c)
+{
+	free(c->val);
+	free(c->last);
+	memset(c, 0, sizeof(*c));
+}
+
+struct igt_drm_clients *
+igt_drm_clients_sort(struct igt_drm_clients *clients,
+		     int (*cmp)(const void *, const void *))
+{
+	unsigned int active, free;
+	struct igt_drm_client *c;
+	int tmp;
+
+	if (!clients)
+		return clients;
+
+	qsort(clients->client, clients->num_clients, sizeof(*clients->client),
+	      cmp);
+
+	/* Trim excessive array space. */
+	active = 0;
+	igt_for_each_drm_client(clients, c, tmp) {
+		if (c->status != IGT_DRM_CLIENT_ALIVE)
+			break; /* Active clients are first in the array. */
+		active++;
+	}
+
+	clients->active_clients = active;
+
+	free = clients->num_clients - active;
+	if (free > clients->num_clients / 2) {
+		active = clients->num_clients - free / 2;
+		if (active != clients->num_clients) {
+			clients->num_clients = active;
+			clients->client = realloc(clients->client,
+						  clients->num_clients *
+						  sizeof(*c));
+		}
+	}
+
+	return clients;
+}
+
+void igt_drm_clients_free(struct igt_drm_clients *clients)
+{
+	struct igt_drm_client *c;
+	unsigned int tmp;
+
+	igt_for_each_drm_client(clients, c, tmp)
+		igt_drm_client_free(c);
+
+	free(clients->client);
+	free(clients);
+}
+
+static DIR *opendirat(int at, const char *name)
+{
+	DIR *dir;
+	int fd;
+
+	fd = openat(at, name, O_DIRECTORY);
+	if (fd < 0)
+		return NULL;
+
+	dir = fdopendir(fd);
+	if (!dir)
+		close(fd);
+
+	return dir;
+}
+
+static size_t readat2buf(char *buf, const size_t sz, int at, const char *name)
+{
+	size_t count;
+	int fd;
+
+	fd = openat(at, name, O_RDONLY);
+	if (fd < 0)
+		return 0;
+
+	buf[sz - 1] = 0;
+	count = read(fd, buf, sz);
+	close(fd);
+
+	return count;
+}
+
+static bool get_task_name(const char *buffer, char *out, unsigned long sz)
+{
+	char *s = index(buffer, '(');
+	char *e = rindex(buffer, ')');
+	unsigned int len;
+
+	if (!s || !e)
+		return false;
+
+	len = --e - ++s + 1;
+	if(!len || (len + 1) >= sz)
+		return false;
+
+	strncpy(out, s, len);
+	out[len] = 0;
+
+	return true;
+}
+
+static bool is_drm_fd(int fd_dir, const char *name)
+{
+	struct stat stat;
+	int ret;
+
+	ret = fstatat(fd_dir, name, &stat, 0);
+
+	return ret == 0 &&
+	       (stat.st_mode & S_IFMT) == S_IFCHR &&
+	       major(stat.st_rdev) == 226;
+}
+
+struct igt_drm_clients *
+igt_drm_clients_scan(struct igt_drm_clients *clients,
+		     bool (*filter_client)(const struct igt_drm_clients *,
+					   const struct drm_client_fdinfo *))
+{
+	struct dirent *proc_dent;
+	struct igt_drm_client *c;
+	DIR *proc_dir;
+	int tmp;
+
+	if (!clients)
+		return clients;
+
+	igt_for_each_drm_client(clients, c, tmp) {
+		assert(c->status != IGT_DRM_CLIENT_PROBE);
+		if (c->status == IGT_DRM_CLIENT_ALIVE)
+			c->status = IGT_DRM_CLIENT_PROBE;
+		else
+			break; /* Free block at the end of array. */
+	}
+
+	proc_dir = opendir("/proc");
+	if (!proc_dir)
+		return clients;
+
+	while ((proc_dent = readdir(proc_dir)) != NULL) {
+		int pid_dir = -1, fd_dir = -1;
+		struct dirent *fdinfo_dent;
+		char client_name[64] = { };
+		unsigned int client_pid;
+		DIR *fdinfo_dir = NULL;
+		char buf[4096];
+		size_t count;
+
+		if (proc_dent->d_type != DT_DIR)
+			continue;
+		if (!isdigit(proc_dent->d_name[0]))
+			continue;
+
+		pid_dir = openat(dirfd(proc_dir), proc_dent->d_name,
+				 O_DIRECTORY | O_RDONLY);
+		if (pid_dir < 0)
+			continue;
+
+		count = readat2buf(buf, sizeof(buf), pid_dir, "stat");
+		if (!count)
+			goto next;
+
+		client_pid = atoi(buf);
+		if (!client_pid)
+			goto next;
+
+		if (!get_task_name(buf, client_name, sizeof(client_name)))
+			goto next;
+
+		fd_dir = openat(pid_dir, "fd", O_DIRECTORY | O_RDONLY);
+		if (fd_dir < 0)
+			goto next;
+
+		fdinfo_dir = opendirat(pid_dir, "fdinfo");
+		if (!fdinfo_dir)
+			goto next;
+
+		while ((fdinfo_dent = readdir(fdinfo_dir)) != NULL) {
+			struct drm_client_fdinfo info;
+
+			if (fdinfo_dent->d_type != DT_REG)
+				continue;
+			if (!isdigit(fdinfo_dent->d_name[0]))
+				continue;
+
+			if (!is_drm_fd(fd_dir, fdinfo_dent->d_name))
+				continue;
+
+			memset(&info, 0, sizeof(info));
+
+			if (!__igt_parse_drm_fdinfo(dirfd(fdinfo_dir),
+						    fdinfo_dent->d_name,
+						    &info))
+				continue;
+
+			if (filter_client && !filter_client(clients, &info))
+				continue;
+
+			if (igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE,
+						info.id))
+				continue; /* Skip duplicate fds. */
+
+			c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_PROBE,
+						info.id);
+			if (!c)
+				igt_drm_client_add(clients, info.id, client_pid,
+						   client_name, info.busy);
+			else
+				igt_drm_client_update(c, client_pid,
+						      client_name, info.busy);
+		}
+
+next:
+		if (fdinfo_dir)
+			closedir(fdinfo_dir);
+		if (fd_dir >= 0)
+			close(fd_dir);
+		if (pid_dir >= 0)
+			close(pid_dir);
+	}
+
+	closedir(proc_dir);
+
+	igt_for_each_drm_client(clients, c, tmp) {
+		if (c->status == IGT_DRM_CLIENT_PROBE)
+			igt_drm_client_free(c);
+		else if (c->status == IGT_DRM_CLIENT_FREE)
+			break;
+	}
+
+	return clients;
+}
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
new file mode 100644
index 000000000000..f52080847291
--- /dev/null
+++ b/lib/igt_drm_clients.h
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef IGT_DRM_CLIENTS_H
+#define IGT_DRM_CLIENTS_H
+
+#include <stdint.h>
+
+struct drm_client_fdinfo;
+
+enum igt_drm_client_status {
+	IGT_DRM_CLIENT_FREE = 0, /* mbz */
+	IGT_DRM_CLIENT_ALIVE,
+	IGT_DRM_CLIENT_PROBE
+};
+
+struct igt_drm_client_engine_class {
+	unsigned int engine_class;
+	const char *name;
+	unsigned int num_engines;
+};
+
+struct igt_drm_clients;
+
+struct igt_drm_client {
+	struct igt_drm_clients *clients;
+
+	enum igt_drm_client_status status;
+	unsigned int id;
+	unsigned int pid;
+	char name[24];
+	char print_name[24];
+	unsigned int samples;
+	unsigned long total_runtime;
+	unsigned long last_runtime;
+	unsigned long *val;
+	uint64_t *last;
+};
+
+struct igt_drm_clients {
+	unsigned int num_clients;
+	unsigned int active_clients;
+
+	unsigned int num_classes;
+	struct igt_drm_client_engine_class *engine_class;
+
+	void *private_data;
+
+	struct igt_drm_client *client; /* Must be last. */
+};
+
+#define igt_for_each_drm_client(clients, c, tmp) \
+	for ((tmp) = (clients)->num_clients, c = (clients)->client; \
+	     (tmp > 0); (tmp)--, (c)++)
+
+struct igt_drm_clients *igt_drm_clients_init(void *private_data);
+void igt_drm_clients_free(struct igt_drm_clients *clients);
+
+struct igt_drm_clients *
+igt_drm_clients_scan(struct igt_drm_clients *clients,
+		     bool (*filter_client)(const struct igt_drm_clients *,
+					   const struct drm_client_fdinfo *));
+
+struct igt_drm_client *
+igt_drm_clients_find(struct igt_drm_clients *clients,
+		     enum igt_drm_client_status status,
+		     unsigned int id);
+
+struct igt_drm_clients *
+igt_drm_clients_sort(struct igt_drm_clients *clients,
+		     int (*cmp)(const void *, const void *));
+
+void igt_drm_client_update(struct igt_drm_client *c,
+			   unsigned int pid, char *name, uint64_t val[16]);
+
+void igt_drm_client_add(struct igt_drm_clients *clients, unsigned int id,
+			unsigned int pid, char *name, uint64_t busy[16]);
+
+void igt_drm_client_free(struct igt_drm_client *c);
+
+#endif /* IGT_DRM_CLIENTS_H */
diff --git a/lib/meson.build b/lib/meson.build
index d00817dc58bc..131bb749ed85 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -16,6 +16,7 @@ lib_sources = [
 	'igt_debugfs.c',
 	'igt_device.c',
 	'igt_device_scan.c',
+	'igt_drm_clients.h',
 	'igt_drm_fdinfo.c',
 	'igt_aux.c',
 	'igt_gt.c',
@@ -217,6 +218,13 @@ lib_igt_device_scan_build = static_library('igt_device_scan',
 lib_igt_device_scan = declare_dependency(link_with : lib_igt_device_scan_build,
 				  include_directories : inc)
 
+lib_igt_drm_clients_build = static_library('igt_drm_clients',
+        ['igt_drm_clients.c'],
+        include_directories : inc)
+
+lib_igt_drm_clients = declare_dependency(link_with : lib_igt_drm_clients_build,
+				         include_directories : inc)
+
 lib_igt_drm_fdinfo_build = static_library('igt_drm_fdinfo',
 	['igt_drm_fdinfo.c'],
 	include_directories : inc)
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index 73815cdea8aa..c4f990b2a7d0 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -46,6 +46,7 @@
 #include <sys/sysmacros.h>
 
 #include "igt_perf.h"
+#include "igt_drm_clients.h"
 #include "igt_drm_fdinfo.h"
 
 #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
@@ -80,16 +81,10 @@ struct engine {
 	struct pmu_counter sema;
 };
 
-struct engine_class {
-	unsigned int class;
-	const char *name;
-	unsigned int num_engines;
-};
-
 struct engines {
 	unsigned int num_engines;
 	unsigned int num_classes;
-	struct engine_class *class;
+	struct igt_drm_client_engine_class *class;
 	unsigned int num_counters;
 	DIR *root;
 	int fd;
@@ -638,160 +633,10 @@ static void pmu_sample(struct engines *engines)
 	}
 }
 
-enum client_status {
-	FREE = 0, /* mbz */
-	ALIVE,
-	PROBE
-};
-
-struct clients;
-
-struct client {
-	struct clients *clients;
-
-	enum client_status status;
-	unsigned int id;
-	unsigned int pid;
-	char name[24];
-	char print_name[24];
-	unsigned int samples;
-	unsigned long total_runtime;
-	unsigned long last_runtime;
-	unsigned long *val;
-	uint64_t *last;
-};
-
-struct clients {
-	unsigned int num_clients;
-	unsigned int active_clients;
-
-	unsigned int num_classes;
-	struct engine_class *class;
-
-	char pci_slot[64];
-
-	struct client *client;
-};
-
-#define for_each_client(clients, c, tmp) \
-	for ((tmp) = (clients)->num_clients, c = (clients)->client; \
-	     (tmp > 0); (tmp)--, (c)++)
-
-static struct clients *init_clients(const char *pci_slot)
-{
-	struct clients *clients;
-
-	clients = malloc(sizeof(*clients));
-	if (!clients)
-		return NULL;
-
-	memset(clients, 0, sizeof(*clients));
-
-	strncpy(clients->pci_slot, pci_slot, sizeof(clients->pci_slot));
-
-	return clients;
-}
-
-static struct client *
-find_client(struct clients *clients, enum client_status status, unsigned int id)
-{
-	unsigned int start, num;
-	struct client *c;
-
-	start = status == FREE ? clients->active_clients : 0; /* Free block at the end. */
-	num = clients->num_clients - start;
-
-	for (c = &clients->client[start]; num; c++, num--) {
-		if (status != c->status)
-			continue;
-
-		if (status == FREE || c->id == id)
-			return c;
-	}
-
-	return NULL;
-}
-
-static void
-update_client(struct client *c, unsigned int pid, char *name, uint64_t val[16])
-{
-	unsigned int i;
-
-	if (c->pid != pid)
-		c->pid = pid;
-
-	if (strcmp(c->name, name)) {
-		char *p;
-
-		strncpy(c->name, name, sizeof(c->name) - 1);
-		strncpy(c->print_name, name, sizeof(c->print_name) - 1);
-
-		p = c->print_name;
-		while (*p) {
-			if (!isprint(*p))
-				*p = '*';
-			p++;
-		}
-	}
-
-	c->last_runtime = 0;
-	c->total_runtime = 0;
-
-	for (i = 0; i < c->clients->num_classes; i++) {
-		if (val[i] < c->last[i])
-			continue; /* It will catch up soon. */
-
-		c->total_runtime += val[i];
-		c->val[i] = val[i] - c->last[i];
-		c->last_runtime += c->val[i];
-		c->last[i] = val[i];
-	}
-
-	c->samples++;
-	c->status = ALIVE;
-}
-
-static void
-add_client(struct clients *clients, unsigned int id, unsigned int pid,
-	   char *name, uint64_t busy[16])
-{
-	struct client *c;
-
-	assert(!find_client(clients, ALIVE, id));
-
-	c = find_client(clients, FREE, 0);
-	if (!c) {
-		unsigned int idx = clients->num_clients;
-
-		clients->num_clients += (clients->num_clients + 2) / 2;
-		clients->client = realloc(clients->client,
-					  clients->num_clients * sizeof(*c));
-		assert(clients->client);
-
-		c = &clients->client[idx];
-		memset(c, 0, (clients->num_clients - idx) * sizeof(*c));
-	}
-
-	c->id = id;
-	c->clients = clients;
-	c->val = calloc(clients->num_classes, sizeof(c->val));
-	c->last = calloc(clients->num_classes, sizeof(c->last));
-	assert(c->val && c->last);
-
-	update_client(c, pid, name, busy);
-}
-
-static void free_client(struct client *c)
-{
-	free(c->val);
-	free(c->last);
-	memset(c, 0, sizeof(*c));
-}
-
 static int client_last_cmp(const void *_a, const void *_b)
 {
-	const struct client *a = _a;
-	const struct client *b = _b;
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
 	long tot_a, tot_b;
 
 	/*
@@ -800,8 +645,8 @@ static int client_last_cmp(const void *_a, const void *_b)
 	 * id.
 	 */
 
-	tot_a = a->status == ALIVE ? a->last_runtime : -1;
-	tot_b = b->status == ALIVE ? b->last_runtime : -1;
+	tot_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->last_runtime : -1;
+	tot_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->last_runtime : -1;
 
 	tot_b -= tot_a;
 	if (tot_b > 0)
@@ -814,12 +659,12 @@ static int client_last_cmp(const void *_a, const void *_b)
 
 static int client_total_cmp(const void *_a, const void *_b)
 {
-	const struct client *a = _a;
-	const struct client *b = _b;
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
 	long tot_a, tot_b;
 
-	tot_a = a->status == ALIVE ? a->total_runtime : -1;
-	tot_b = b->status == ALIVE ? b->total_runtime : -1;
+	tot_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->total_runtime : -1;
+	tot_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->total_runtime : -1;
 
 	tot_b -= tot_a;
 	if (tot_b > 0)
@@ -832,12 +677,12 @@ static int client_total_cmp(const void *_a, const void *_b)
 
 static int client_id_cmp(const void *_a, const void *_b)
 {
-	const struct client *a = _a;
-	const struct client *b = _b;
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
 	int id_a, id_b;
 
-	id_a = a->status == ALIVE ? a->id : -1;
-	id_b = b->status == ALIVE ? b->id : -1;
+	id_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->id : -1;
+	id_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->id : -1;
 
 	id_b -= id_a;
 	if (id_b > 0)
@@ -850,12 +695,12 @@ static int client_id_cmp(const void *_a, const void *_b)
 
 static int client_pid_cmp(const void *_a, const void *_b)
 {
-	const struct client *a = _a;
-	const struct client *b = _b;
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
 	int pid_a, pid_b;
 
-	pid_a = a->status == ALIVE ? a->pid : INT_MAX;
-	pid_b = b->status == ALIVE ? b->pid : INT_MAX;
+	pid_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->pid : INT_MAX;
+	pid_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->pid : INT_MAX;
 
 	pid_b -= pid_a;
 	if (pid_b > 0)
@@ -868,56 +713,19 @@ static int client_pid_cmp(const void *_a, const void *_b)
 
 static int (*client_cmp)(const void *, const void *) = client_last_cmp;
 
-static struct clients *sort_clients(struct clients *clients,
-				    int (*cmp)(const void *, const void *))
-{
-	unsigned int active, free;
-	struct client *c;
-	int tmp;
-
-	if (!clients)
-		return clients;
-
-	qsort(clients->client, clients->num_clients, sizeof(*clients->client),
-	      cmp);
-
-	/* Trim excessive array space. */
-	active = 0;
-	for_each_client(clients, c, tmp) {
-		if (c->status != ALIVE)
-			break; /* Active clients are first in the array. */
-		active++;
-	}
-
-	clients->active_clients = active;
-
-	free = clients->num_clients - active;
-	if (free > clients->num_clients / 2) {
-		active = clients->num_clients - free / 2;
-		if (active != clients->num_clients) {
-			clients->num_clients = active;
-			clients->client = realloc(clients->client,
-						  clients->num_clients *
-						  sizeof(*c));
-		}
-	}
-
-	return clients;
-}
-
 static bool aggregate_pids = true;
 
-static struct clients *display_clients(struct clients *clients)
+static struct igt_drm_clients *display_clients(struct igt_drm_clients *clients)
 {
-	struct client *ac, *c, *cp = NULL;
-	struct clients *aggregated;
+	struct igt_drm_client *ac, *c, *cp = NULL;
+	struct igt_drm_clients *aggregated;
 	int tmp, num = 0;
 
 	if (!aggregate_pids)
 		goto out;
 
 	/* Sort by pid first to make it easy to aggregate while walking. */
-	sort_clients(clients, client_pid_cmp);
+	igt_drm_clients_sort(clients, client_pid_cmp);
 
 	aggregated = calloc(1, sizeof(*clients));
 	assert(aggregated);
@@ -926,23 +734,24 @@ static struct clients *display_clients(struct clients *clients)
 	assert(ac);
 
 	aggregated->num_classes = clients->num_classes;
-	aggregated->class = clients->class;
+	aggregated->engine_class = clients->engine_class;
+	aggregated->private_data = clients->private_data;
 	aggregated->client = ac;
 
-	for_each_client(clients, c, tmp) {
+	igt_for_each_drm_client(clients, c, tmp) {
 		unsigned int i;
 
-		if (c->status == FREE)
+		if (c->status == IGT_DRM_CLIENT_FREE)
 			break;
 
-		assert(c->status == ALIVE);
+		assert(c->status == IGT_DRM_CLIENT_ALIVE);
 
 		if ((cp && c->pid != cp->pid) || !cp) {
 			ac = &aggregated->client[num++];
 
 			/* New pid. */
 			ac->clients = aggregated;
-			ac->status = ALIVE;
+			ac->status = IGT_DRM_CLIENT_ALIVE;
 			ac->id = -c->pid;
 			ac->pid = c->pid;
 			strcpy(ac->name, c->name);
@@ -972,213 +781,21 @@ static struct clients *display_clients(struct clients *clients)
 	clients = aggregated;
 
 out:
-	return sort_clients(clients, client_cmp);
+	return igt_drm_clients_sort(clients, client_cmp);
 }
 
-static void free_clients(struct clients *clients)
+static void free_display_clients(struct igt_drm_clients *clients)
 {
-	struct client *c;
-	unsigned int tmp;
+	struct igt_drm_client *c;
+	int tmp;
 
-	for_each_client(clients, c, tmp) {
+	igt_for_each_drm_client(clients, c, tmp)
 		free(c->val);
-		free(c->last);
-	}
 
 	free(clients->client);
 	free(clients);
 }
 
-static bool is_drm_fd(DIR *fd_dir, const char *name)
-{
-	struct stat stat;
-	int ret;
-
-	ret = fstatat(dirfd(fd_dir), name, &stat, 0);
-
-	return ret == 0 &&
-	       (stat.st_mode & S_IFMT) == S_IFCHR &&
-	       major(stat.st_rdev) == 226;
-}
-
-static bool get_task_name(const char *buffer, char *out, unsigned long sz)
-{
-	char *s = index(buffer, '(');
-	char *e = rindex(buffer, ')');
-	unsigned int len;
-
-	if (!s || !e)
-		return false;
-
-	len = --e - ++s + 1;
-	if(!len || (len + 1) >= sz)
-		return false;
-
-	strncpy(out, s, len);
-	out[len] = 0;
-
-	return true;
-}
-
-static DIR *opendirat(DIR *at, const char *name)
-{
-	DIR *dir;
-	int fd;
-
-	fd = openat(dirfd(at), name, O_DIRECTORY);
-	if (fd < 0)
-		return NULL;
-
-	dir = fdopendir(fd);
-	if (!dir)
-		close(fd);
-
-	return dir;
-}
-
-static FILE *fropenat(DIR *at, const char *name)
-{
-	FILE *f;
-	int fd;
-
-	fd = openat(dirfd(at), name, O_RDONLY);
-	if (fd < 0)
-		return NULL;
-
-	f = fdopen(fd, "r");
-	if (!f)
-		close(fd);
-
-	return f;
-}
-
-static size_t freadat2buf(char *buf, const size_t sz, DIR *at, const char *name)
-{
-	size_t count;
-	FILE *f;
-
-	f = fropenat(at, name);
-	if (!f)
-		return 0;
-
-	memset(buf, 0, sz);
-	count = fread(buf, 1, sz, f);
-	fclose(f);
-
-	return count;
-}
-
-static struct clients *scan_clients(struct clients *clients)
-{
-	struct dirent *proc_dent;
-	struct client *c;
-	DIR *proc_dir;
-	int tmp;
-
-	if (!clients)
-		return clients;
-
-	for_each_client(clients, c, tmp) {
-		assert(c->status != PROBE);
-		if (c->status == ALIVE)
-			c->status = PROBE;
-		else
-			break; /* Free block at the end of array. */
-	}
-
-	proc_dir = opendir("/proc");
-	if (!proc_dir)
-		return clients;
-
-	while ((proc_dent = readdir(proc_dir)) != NULL) {
-		DIR *pid_dir = NULL, *fd_dir = NULL, *fdinfo_dir = NULL;
-		struct dirent *fdinfo_dent;
-		char client_name[64] = { };
-		unsigned int client_pid;
-		char buf[4096];
-		size_t count;
-
-		if (proc_dent->d_type != DT_DIR)
-			continue;
-		if (!isdigit(proc_dent->d_name[0]))
-			continue;
-
-		pid_dir = opendirat(proc_dir, proc_dent->d_name);
-		if (!pid_dir)
-			continue;
-
-		count = freadat2buf(buf, sizeof(buf), pid_dir, "stat");
-		if (!count)
-			goto next;
-
-		client_pid = atoi(buf);
-		if (!client_pid)
-			goto next;
-
-		if (!get_task_name(buf, client_name, sizeof(client_name)))
-			goto next;
-
-		fd_dir = opendirat(pid_dir, "fd");
-		if (!fd_dir)
-			goto next;
-
-		fdinfo_dir = opendirat(pid_dir, "fdinfo");
-		if (!fdinfo_dir)
-			goto next;
-
-		while ((fdinfo_dent = readdir(fdinfo_dir)) != NULL) {
-			struct drm_client_fdinfo info = { };
-
-			if (fdinfo_dent->d_type != DT_REG)
-				continue;
-			if (!isdigit(fdinfo_dent->d_name[0]))
-				continue;
-
-			if (!is_drm_fd(fd_dir, fdinfo_dent->d_name))
-				continue;
-
-			if (!__igt_parse_drm_fdinfo(dirfd(fdinfo_dir),
-						    fdinfo_dent->d_name,
-						    &info))
-				continue;
-
-			if (strcmp(info.driver, "i915"))
-				continue;
-			if (strcmp(info.pdev, clients->pci_slot))
-				continue;
-			if (find_client(clients, ALIVE, info.id))
-				continue; /* Skip duplicate fds. */
-
-			c = find_client(clients, PROBE, info.id);
-			if (!c)
-				add_client(clients, info.id, client_pid,
-					   client_name, info.busy);
-			else
-				update_client(c, client_pid, client_name,
-					      info.busy);
-		}
-
-next:
-		if (fdinfo_dir)
-			closedir(fdinfo_dir);
-		if (fd_dir)
-			closedir(fd_dir);
-		if (pid_dir)
-			closedir(pid_dir);
-	}
-
-	closedir(proc_dir);
-
-	for_each_client(clients, c, tmp) {
-		if (c->status == PROBE)
-			free_client(c);
-		else if (c->status == FREE)
-			break;
-	}
-
-	return display_clients(clients);
-}
-
 static const char *bars[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" };
 
 static void n_spaces(const unsigned int n)
@@ -1881,15 +1498,15 @@ print_engines_footer(struct engines *engines, double t,
 
 static int class_cmp(const void *_a, const void *_b)
 {
-	const struct engine_class *a = _a;
-	const struct engine_class *b = _b;
+	const struct igt_drm_client_engine_class *a = _a;
+	const struct igt_drm_client_engine_class *b = _b;
 
-	return a->class - b->class;
+	return a->engine_class - b->engine_class;
 }
 
 static void init_engine_classes(struct engines *engines)
 {
-	struct engine_class *classes;
+	struct igt_drm_client_engine_class *classes;
 	unsigned int i, num;
 	int max = -1;
 
@@ -1916,7 +1533,7 @@ static void init_engine_classes(struct engines *engines)
 	}
 
 	for (i = 0; i < num; i++) {
-		classes[i].class = i;
+		classes[i].engine_class = i;
 		classes[i].name = class_display_name(i);
 	}
 
@@ -2058,7 +1675,7 @@ print_engines(struct engines *engines, double t, int lines, int w, int h)
 }
 
 static int
-print_clients_header(struct clients *clients, int lines,
+print_clients_header(struct igt_drm_clients *clients, int lines,
 		     int con_w, int con_h, int *class_w)
 {
 	if (output_mode == INTERACTIVE) {
@@ -2080,19 +1697,19 @@ print_clients_header(struct clients *clients, int lines,
 			int width;
 
 			for (i = 0; i < clients->num_classes; i++) {
-				if (clients->class[i].num_engines)
+				if (clients->engine_class[i].num_engines)
 					num_active++;
 			}
 
 			*class_w = width = (con_w - len) / num_active;
 
 			for (i = 0; i < clients->num_classes; i++) {
-				const char *name = clients->class[i].name;
+				const char *name = clients->engine_class[i].name;
 				int name_len = strlen(name);
 				int pad = (width - name_len) / 2;
 				int spaces = width - pad - name_len;
 
-				if (!clients->class[i].num_engines)
+				if (!clients->engine_class[i].num_engines)
 					continue; /* Assert in the ideal world. */
 
 				if (pad < 0 || spaces < 0)
@@ -2119,10 +1736,10 @@ static bool numeric_clients;
 static bool filter_idle;
 
 static int
-print_client(struct client *c, struct engines *engines, double t, int lines,
+print_client(struct igt_drm_client *c, struct engines *engines, double t, int lines,
 	     int con_w, int con_h, unsigned int period_us, int *class_w)
 {
-	struct clients *clients = c->clients;
+	struct igt_drm_clients *clients = c->clients;
 	unsigned int i;
 
 	if (output_mode == INTERACTIVE) {
@@ -2136,11 +1753,11 @@ print_client(struct client *c, struct engines *engines, double t, int lines,
 		for (i = 0; c->samples > 1 && i < clients->num_classes; i++) {
 			double pct;
 
-			if (!clients->class[i].num_engines)
+			if (!clients->engine_class[i].num_engines)
 				continue; /* Assert in the ideal world. */
 
 			pct = (double)c->val[i] / period_us / 1e3 * 100 /
-			      clients->class[i].num_engines;
+			      clients->engine_class[i].num_engines;
 
 			/*
 			 * Guard against possible time-drift between sampling
@@ -2172,7 +1789,7 @@ print_client(struct client *c, struct engines *engines, double t, int lines,
 				double pct;
 
 				snprintf(buf, sizeof(buf), "%s",
-					clients->class[i].name);
+					clients->engine_class[i].name);
 				pops->open_struct(buf);
 
 				pct = (double)c->val[i] / period_us / 1e3 * 100;
@@ -2194,7 +1811,7 @@ print_client(struct client *c, struct engines *engines, double t, int lines,
 }
 
 static int
-print_clients_footer(struct clients *clients, double t,
+print_clients_footer(struct igt_drm_clients *clients, double t,
 		     int lines, int con_w, int con_h)
 {
 	if (output_mode == INTERACTIVE) {
@@ -2398,10 +2015,21 @@ static void show_help_screen(void)
 "\n");
 }
 
+static bool client_match(const struct igt_drm_clients *clients,
+			 const struct drm_client_fdinfo *info)
+{
+	if (strcmp(info->driver, "i915"))
+		return false;
+	if (strcmp(info->pdev, clients->private_data))
+		return false;
+
+	return true;
+}
+
 int main(int argc, char **argv)
 {
 	unsigned int period_us = DEFAULT_PERIOD_MS * 1000;
-	struct clients *clients = NULL;
+	struct igt_drm_clients *clients = NULL;
 	int con_w = -1, con_h = -1;
 	char *output_path = NULL;
 	struct engines *engines;
@@ -2540,24 +2168,24 @@ int main(int argc, char **argv)
 
 	ret = EXIT_SUCCESS;
 
-	clients = init_clients(card.pci_slot_name[0] ?
-			       card.pci_slot_name : IGPU_PCI);
+	clients = igt_drm_clients_init(strdup(card.pci_slot_name[0] ?
+					      card.pci_slot_name : IGPU_PCI));
 	init_engine_classes(engines);
 	if (clients) {
 		clients->num_classes = engines->num_classes;
-		clients->class = engines->class;
+		clients->engine_class = engines->class;
 	}
 
 	pmu_sample(engines);
-	scan_clients(clients);
+	igt_drm_clients_scan(clients, client_match);
 	codename = igt_device_get_pretty_name(&card, false);
 
 	while (!stop_top) {
-		struct clients *disp_clients;
+		struct igt_drm_clients *disp_clients;
 		bool consumed = false;
 		int j, lines = 0;
 		struct winsize ws;
-		struct client *c;
+		struct igt_drm_client *c;
 		double t;
 
 		/* Update terminal size. */
@@ -2576,7 +2204,9 @@ int main(int argc, char **argv)
 		pmu_sample(engines);
 		t = (double)(engines->ts.cur - engines->ts.prev) / 1e9;
 
-		disp_clients = scan_clients(clients);
+		disp_clients =
+			display_clients(igt_drm_clients_scan(clients,
+							     client_match));
 
 		if (stop_top)
 			break;
@@ -2604,9 +2234,9 @@ int main(int argc, char **argv)
 							     con_w, con_h,
 							     &class_w);
 
-				for_each_client(disp_clients, c, j) {
-					assert(c->status != PROBE);
-					if (c->status != ALIVE)
+				igt_for_each_drm_client(disp_clients, c, j) {
+					assert(c->status != IGT_DRM_CLIENT_PROBE);
+					if (c->status != IGT_DRM_CLIENT_ALIVE)
 						break; /* Active clients are first in the array. */
 
 					if (lines >= con_h)
@@ -2630,7 +2260,7 @@ int main(int argc, char **argv)
 			break;
 
 		if (disp_clients != clients)
-			free_clients(disp_clients);
+			free_display_clients(disp_clients);
 
 		if (output_mode == INTERACTIVE)
 			process_stdin(period_us);
diff --git a/tools/meson.build b/tools/meson.build
index 771d0b9e3d5d..e65aceeef9fa 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -91,7 +91,7 @@ install_subdir('registers', install_dir : datadir)
 executable('intel_gpu_top', 'intel_gpu_top.c',
 	   install : true,
 	   install_rpath : bindir_rpathdir,
-	   dependencies : [lib_igt_perf,lib_igt_device_scan,lib_igt_drm_fdinfo,math])
+	   dependencies : [lib_igt_perf,lib_igt_device_scan,lib_igt_drm_clients,lib_igt_drm_fdinfo,math])
 
 executable('amd_hdmi_compliance', 'amd_hdmi_compliance.c',
 	   dependencies : [tool_deps],
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [Intel-gfx] [PATCH i-g-t 04/11] lib: Extract igt_drm_clients from intel_gpu_top
@ 2022-02-22 13:55   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Code movement with some improvements to prepare for further work in
making a vendor agnostic gputop tool possible.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c | 386 +++++++++++++++++++++++++++++++
 lib/igt_drm_clients.h | 102 +++++++++
 lib/meson.build       |   8 +
 tools/intel_gpu_top.c | 516 ++++++------------------------------------
 tools/meson.build     |   2 +-
 5 files changed, 570 insertions(+), 444 deletions(-)
 create mode 100644 lib/igt_drm_clients.c
 create mode 100644 lib/igt_drm_clients.h

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
new file mode 100644
index 000000000000..58d82648d821
--- /dev/null
+++ b/lib/igt_drm_clients.c
@@ -0,0 +1,386 @@
+/*
+ * 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 <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "igt_drm_clients.h"
+#include "igt_drm_fdinfo.h"
+
+struct igt_drm_clients *igt_drm_clients_init(void *private_data)
+{
+	struct igt_drm_clients *clients;
+
+	clients = malloc(sizeof(*clients));
+	if (!clients)
+		return NULL;
+
+	memset(clients, 0, sizeof(*clients));
+
+	clients->private_data = private_data;
+
+	return clients;
+}
+
+struct igt_drm_client *
+igt_drm_clients_find(struct igt_drm_clients *clients,
+		     enum igt_drm_client_status status,
+		     unsigned int id)
+{
+	unsigned int start, num;
+	struct igt_drm_client *c;
+
+	start = status == IGT_DRM_CLIENT_FREE ? clients->active_clients : 0; /* Free block at the end. */
+	num = clients->num_clients - start;
+
+	for (c = &clients->client[start]; num; c++, num--) {
+		if (status != c->status)
+			continue;
+
+		if (status == IGT_DRM_CLIENT_FREE || c->id == id)
+			return c;
+	}
+
+	return NULL;
+}
+
+void
+igt_drm_client_update(struct igt_drm_client *c, unsigned int pid, char *name,
+		      uint64_t val[16])
+{
+	unsigned int i;
+
+	if (c->pid != pid)
+		c->pid = pid;
+
+	if (strcmp(c->name, name)) {
+		char *p;
+
+		strncpy(c->name, name, sizeof(c->name) - 1);
+		strncpy(c->print_name, name, sizeof(c->print_name) - 1);
+
+		p = c->print_name;
+		while (*p) {
+			if (!isprint(*p))
+				*p = '*';
+			p++;
+		}
+	}
+
+	c->last_runtime = 0;
+	c->total_runtime = 0;
+
+	for (i = 0; i < c->clients->num_classes; i++) {
+		if (val[i] < c->last[i])
+			continue; /* It will catch up soon. */
+
+		c->total_runtime += val[i];
+		c->val[i] = val[i] - c->last[i];
+		c->last_runtime += c->val[i];
+		c->last[i] = val[i];
+	}
+
+	c->samples++;
+	c->status = IGT_DRM_CLIENT_ALIVE;
+}
+
+void
+igt_drm_client_add(struct igt_drm_clients *clients, unsigned int id,
+		   unsigned int pid, char *name, uint64_t busy[16])
+{
+	struct igt_drm_client *c;
+
+	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE, id));
+
+	c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_FREE, 0);
+	if (!c) {
+		unsigned int idx = clients->num_clients;
+
+		clients->num_clients += (clients->num_clients + 2) / 2;
+		clients->client = realloc(clients->client,
+					  clients->num_clients * sizeof(*c));
+		assert(clients->client);
+
+		c = &clients->client[idx];
+		memset(c, 0, (clients->num_clients - idx) * sizeof(*c));
+	}
+
+	c->id = id;
+	c->clients = clients;
+	c->val = calloc(clients->num_classes, sizeof(c->val));
+	c->last = calloc(clients->num_classes, sizeof(c->last));
+	assert(c->val && c->last);
+
+	igt_drm_client_update(c, pid, name, busy);
+}
+
+void igt_drm_client_free(struct igt_drm_client *c)
+{
+	free(c->val);
+	free(c->last);
+	memset(c, 0, sizeof(*c));
+}
+
+struct igt_drm_clients *
+igt_drm_clients_sort(struct igt_drm_clients *clients,
+		     int (*cmp)(const void *, const void *))
+{
+	unsigned int active, free;
+	struct igt_drm_client *c;
+	int tmp;
+
+	if (!clients)
+		return clients;
+
+	qsort(clients->client, clients->num_clients, sizeof(*clients->client),
+	      cmp);
+
+	/* Trim excessive array space. */
+	active = 0;
+	igt_for_each_drm_client(clients, c, tmp) {
+		if (c->status != IGT_DRM_CLIENT_ALIVE)
+			break; /* Active clients are first in the array. */
+		active++;
+	}
+
+	clients->active_clients = active;
+
+	free = clients->num_clients - active;
+	if (free > clients->num_clients / 2) {
+		active = clients->num_clients - free / 2;
+		if (active != clients->num_clients) {
+			clients->num_clients = active;
+			clients->client = realloc(clients->client,
+						  clients->num_clients *
+						  sizeof(*c));
+		}
+	}
+
+	return clients;
+}
+
+void igt_drm_clients_free(struct igt_drm_clients *clients)
+{
+	struct igt_drm_client *c;
+	unsigned int tmp;
+
+	igt_for_each_drm_client(clients, c, tmp)
+		igt_drm_client_free(c);
+
+	free(clients->client);
+	free(clients);
+}
+
+static DIR *opendirat(int at, const char *name)
+{
+	DIR *dir;
+	int fd;
+
+	fd = openat(at, name, O_DIRECTORY);
+	if (fd < 0)
+		return NULL;
+
+	dir = fdopendir(fd);
+	if (!dir)
+		close(fd);
+
+	return dir;
+}
+
+static size_t readat2buf(char *buf, const size_t sz, int at, const char *name)
+{
+	size_t count;
+	int fd;
+
+	fd = openat(at, name, O_RDONLY);
+	if (fd < 0)
+		return 0;
+
+	buf[sz - 1] = 0;
+	count = read(fd, buf, sz);
+	close(fd);
+
+	return count;
+}
+
+static bool get_task_name(const char *buffer, char *out, unsigned long sz)
+{
+	char *s = index(buffer, '(');
+	char *e = rindex(buffer, ')');
+	unsigned int len;
+
+	if (!s || !e)
+		return false;
+
+	len = --e - ++s + 1;
+	if(!len || (len + 1) >= sz)
+		return false;
+
+	strncpy(out, s, len);
+	out[len] = 0;
+
+	return true;
+}
+
+static bool is_drm_fd(int fd_dir, const char *name)
+{
+	struct stat stat;
+	int ret;
+
+	ret = fstatat(fd_dir, name, &stat, 0);
+
+	return ret == 0 &&
+	       (stat.st_mode & S_IFMT) == S_IFCHR &&
+	       major(stat.st_rdev) == 226;
+}
+
+struct igt_drm_clients *
+igt_drm_clients_scan(struct igt_drm_clients *clients,
+		     bool (*filter_client)(const struct igt_drm_clients *,
+					   const struct drm_client_fdinfo *))
+{
+	struct dirent *proc_dent;
+	struct igt_drm_client *c;
+	DIR *proc_dir;
+	int tmp;
+
+	if (!clients)
+		return clients;
+
+	igt_for_each_drm_client(clients, c, tmp) {
+		assert(c->status != IGT_DRM_CLIENT_PROBE);
+		if (c->status == IGT_DRM_CLIENT_ALIVE)
+			c->status = IGT_DRM_CLIENT_PROBE;
+		else
+			break; /* Free block at the end of array. */
+	}
+
+	proc_dir = opendir("/proc");
+	if (!proc_dir)
+		return clients;
+
+	while ((proc_dent = readdir(proc_dir)) != NULL) {
+		int pid_dir = -1, fd_dir = -1;
+		struct dirent *fdinfo_dent;
+		char client_name[64] = { };
+		unsigned int client_pid;
+		DIR *fdinfo_dir = NULL;
+		char buf[4096];
+		size_t count;
+
+		if (proc_dent->d_type != DT_DIR)
+			continue;
+		if (!isdigit(proc_dent->d_name[0]))
+			continue;
+
+		pid_dir = openat(dirfd(proc_dir), proc_dent->d_name,
+				 O_DIRECTORY | O_RDONLY);
+		if (pid_dir < 0)
+			continue;
+
+		count = readat2buf(buf, sizeof(buf), pid_dir, "stat");
+		if (!count)
+			goto next;
+
+		client_pid = atoi(buf);
+		if (!client_pid)
+			goto next;
+
+		if (!get_task_name(buf, client_name, sizeof(client_name)))
+			goto next;
+
+		fd_dir = openat(pid_dir, "fd", O_DIRECTORY | O_RDONLY);
+		if (fd_dir < 0)
+			goto next;
+
+		fdinfo_dir = opendirat(pid_dir, "fdinfo");
+		if (!fdinfo_dir)
+			goto next;
+
+		while ((fdinfo_dent = readdir(fdinfo_dir)) != NULL) {
+			struct drm_client_fdinfo info;
+
+			if (fdinfo_dent->d_type != DT_REG)
+				continue;
+			if (!isdigit(fdinfo_dent->d_name[0]))
+				continue;
+
+			if (!is_drm_fd(fd_dir, fdinfo_dent->d_name))
+				continue;
+
+			memset(&info, 0, sizeof(info));
+
+			if (!__igt_parse_drm_fdinfo(dirfd(fdinfo_dir),
+						    fdinfo_dent->d_name,
+						    &info))
+				continue;
+
+			if (filter_client && !filter_client(clients, &info))
+				continue;
+
+			if (igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE,
+						info.id))
+				continue; /* Skip duplicate fds. */
+
+			c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_PROBE,
+						info.id);
+			if (!c)
+				igt_drm_client_add(clients, info.id, client_pid,
+						   client_name, info.busy);
+			else
+				igt_drm_client_update(c, client_pid,
+						      client_name, info.busy);
+		}
+
+next:
+		if (fdinfo_dir)
+			closedir(fdinfo_dir);
+		if (fd_dir >= 0)
+			close(fd_dir);
+		if (pid_dir >= 0)
+			close(pid_dir);
+	}
+
+	closedir(proc_dir);
+
+	igt_for_each_drm_client(clients, c, tmp) {
+		if (c->status == IGT_DRM_CLIENT_PROBE)
+			igt_drm_client_free(c);
+		else if (c->status == IGT_DRM_CLIENT_FREE)
+			break;
+	}
+
+	return clients;
+}
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
new file mode 100644
index 000000000000..f52080847291
--- /dev/null
+++ b/lib/igt_drm_clients.h
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef IGT_DRM_CLIENTS_H
+#define IGT_DRM_CLIENTS_H
+
+#include <stdint.h>
+
+struct drm_client_fdinfo;
+
+enum igt_drm_client_status {
+	IGT_DRM_CLIENT_FREE = 0, /* mbz */
+	IGT_DRM_CLIENT_ALIVE,
+	IGT_DRM_CLIENT_PROBE
+};
+
+struct igt_drm_client_engine_class {
+	unsigned int engine_class;
+	const char *name;
+	unsigned int num_engines;
+};
+
+struct igt_drm_clients;
+
+struct igt_drm_client {
+	struct igt_drm_clients *clients;
+
+	enum igt_drm_client_status status;
+	unsigned int id;
+	unsigned int pid;
+	char name[24];
+	char print_name[24];
+	unsigned int samples;
+	unsigned long total_runtime;
+	unsigned long last_runtime;
+	unsigned long *val;
+	uint64_t *last;
+};
+
+struct igt_drm_clients {
+	unsigned int num_clients;
+	unsigned int active_clients;
+
+	unsigned int num_classes;
+	struct igt_drm_client_engine_class *engine_class;
+
+	void *private_data;
+
+	struct igt_drm_client *client; /* Must be last. */
+};
+
+#define igt_for_each_drm_client(clients, c, tmp) \
+	for ((tmp) = (clients)->num_clients, c = (clients)->client; \
+	     (tmp > 0); (tmp)--, (c)++)
+
+struct igt_drm_clients *igt_drm_clients_init(void *private_data);
+void igt_drm_clients_free(struct igt_drm_clients *clients);
+
+struct igt_drm_clients *
+igt_drm_clients_scan(struct igt_drm_clients *clients,
+		     bool (*filter_client)(const struct igt_drm_clients *,
+					   const struct drm_client_fdinfo *));
+
+struct igt_drm_client *
+igt_drm_clients_find(struct igt_drm_clients *clients,
+		     enum igt_drm_client_status status,
+		     unsigned int id);
+
+struct igt_drm_clients *
+igt_drm_clients_sort(struct igt_drm_clients *clients,
+		     int (*cmp)(const void *, const void *));
+
+void igt_drm_client_update(struct igt_drm_client *c,
+			   unsigned int pid, char *name, uint64_t val[16]);
+
+void igt_drm_client_add(struct igt_drm_clients *clients, unsigned int id,
+			unsigned int pid, char *name, uint64_t busy[16]);
+
+void igt_drm_client_free(struct igt_drm_client *c);
+
+#endif /* IGT_DRM_CLIENTS_H */
diff --git a/lib/meson.build b/lib/meson.build
index d00817dc58bc..131bb749ed85 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -16,6 +16,7 @@ lib_sources = [
 	'igt_debugfs.c',
 	'igt_device.c',
 	'igt_device_scan.c',
+	'igt_drm_clients.h',
 	'igt_drm_fdinfo.c',
 	'igt_aux.c',
 	'igt_gt.c',
@@ -217,6 +218,13 @@ lib_igt_device_scan_build = static_library('igt_device_scan',
 lib_igt_device_scan = declare_dependency(link_with : lib_igt_device_scan_build,
 				  include_directories : inc)
 
+lib_igt_drm_clients_build = static_library('igt_drm_clients',
+        ['igt_drm_clients.c'],
+        include_directories : inc)
+
+lib_igt_drm_clients = declare_dependency(link_with : lib_igt_drm_clients_build,
+				         include_directories : inc)
+
 lib_igt_drm_fdinfo_build = static_library('igt_drm_fdinfo',
 	['igt_drm_fdinfo.c'],
 	include_directories : inc)
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index 73815cdea8aa..c4f990b2a7d0 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -46,6 +46,7 @@
 #include <sys/sysmacros.h>
 
 #include "igt_perf.h"
+#include "igt_drm_clients.h"
 #include "igt_drm_fdinfo.h"
 
 #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
@@ -80,16 +81,10 @@ struct engine {
 	struct pmu_counter sema;
 };
 
-struct engine_class {
-	unsigned int class;
-	const char *name;
-	unsigned int num_engines;
-};
-
 struct engines {
 	unsigned int num_engines;
 	unsigned int num_classes;
-	struct engine_class *class;
+	struct igt_drm_client_engine_class *class;
 	unsigned int num_counters;
 	DIR *root;
 	int fd;
@@ -638,160 +633,10 @@ static void pmu_sample(struct engines *engines)
 	}
 }
 
-enum client_status {
-	FREE = 0, /* mbz */
-	ALIVE,
-	PROBE
-};
-
-struct clients;
-
-struct client {
-	struct clients *clients;
-
-	enum client_status status;
-	unsigned int id;
-	unsigned int pid;
-	char name[24];
-	char print_name[24];
-	unsigned int samples;
-	unsigned long total_runtime;
-	unsigned long last_runtime;
-	unsigned long *val;
-	uint64_t *last;
-};
-
-struct clients {
-	unsigned int num_clients;
-	unsigned int active_clients;
-
-	unsigned int num_classes;
-	struct engine_class *class;
-
-	char pci_slot[64];
-
-	struct client *client;
-};
-
-#define for_each_client(clients, c, tmp) \
-	for ((tmp) = (clients)->num_clients, c = (clients)->client; \
-	     (tmp > 0); (tmp)--, (c)++)
-
-static struct clients *init_clients(const char *pci_slot)
-{
-	struct clients *clients;
-
-	clients = malloc(sizeof(*clients));
-	if (!clients)
-		return NULL;
-
-	memset(clients, 0, sizeof(*clients));
-
-	strncpy(clients->pci_slot, pci_slot, sizeof(clients->pci_slot));
-
-	return clients;
-}
-
-static struct client *
-find_client(struct clients *clients, enum client_status status, unsigned int id)
-{
-	unsigned int start, num;
-	struct client *c;
-
-	start = status == FREE ? clients->active_clients : 0; /* Free block at the end. */
-	num = clients->num_clients - start;
-
-	for (c = &clients->client[start]; num; c++, num--) {
-		if (status != c->status)
-			continue;
-
-		if (status == FREE || c->id == id)
-			return c;
-	}
-
-	return NULL;
-}
-
-static void
-update_client(struct client *c, unsigned int pid, char *name, uint64_t val[16])
-{
-	unsigned int i;
-
-	if (c->pid != pid)
-		c->pid = pid;
-
-	if (strcmp(c->name, name)) {
-		char *p;
-
-		strncpy(c->name, name, sizeof(c->name) - 1);
-		strncpy(c->print_name, name, sizeof(c->print_name) - 1);
-
-		p = c->print_name;
-		while (*p) {
-			if (!isprint(*p))
-				*p = '*';
-			p++;
-		}
-	}
-
-	c->last_runtime = 0;
-	c->total_runtime = 0;
-
-	for (i = 0; i < c->clients->num_classes; i++) {
-		if (val[i] < c->last[i])
-			continue; /* It will catch up soon. */
-
-		c->total_runtime += val[i];
-		c->val[i] = val[i] - c->last[i];
-		c->last_runtime += c->val[i];
-		c->last[i] = val[i];
-	}
-
-	c->samples++;
-	c->status = ALIVE;
-}
-
-static void
-add_client(struct clients *clients, unsigned int id, unsigned int pid,
-	   char *name, uint64_t busy[16])
-{
-	struct client *c;
-
-	assert(!find_client(clients, ALIVE, id));
-
-	c = find_client(clients, FREE, 0);
-	if (!c) {
-		unsigned int idx = clients->num_clients;
-
-		clients->num_clients += (clients->num_clients + 2) / 2;
-		clients->client = realloc(clients->client,
-					  clients->num_clients * sizeof(*c));
-		assert(clients->client);
-
-		c = &clients->client[idx];
-		memset(c, 0, (clients->num_clients - idx) * sizeof(*c));
-	}
-
-	c->id = id;
-	c->clients = clients;
-	c->val = calloc(clients->num_classes, sizeof(c->val));
-	c->last = calloc(clients->num_classes, sizeof(c->last));
-	assert(c->val && c->last);
-
-	update_client(c, pid, name, busy);
-}
-
-static void free_client(struct client *c)
-{
-	free(c->val);
-	free(c->last);
-	memset(c, 0, sizeof(*c));
-}
-
 static int client_last_cmp(const void *_a, const void *_b)
 {
-	const struct client *a = _a;
-	const struct client *b = _b;
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
 	long tot_a, tot_b;
 
 	/*
@@ -800,8 +645,8 @@ static int client_last_cmp(const void *_a, const void *_b)
 	 * id.
 	 */
 
-	tot_a = a->status == ALIVE ? a->last_runtime : -1;
-	tot_b = b->status == ALIVE ? b->last_runtime : -1;
+	tot_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->last_runtime : -1;
+	tot_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->last_runtime : -1;
 
 	tot_b -= tot_a;
 	if (tot_b > 0)
@@ -814,12 +659,12 @@ static int client_last_cmp(const void *_a, const void *_b)
 
 static int client_total_cmp(const void *_a, const void *_b)
 {
-	const struct client *a = _a;
-	const struct client *b = _b;
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
 	long tot_a, tot_b;
 
-	tot_a = a->status == ALIVE ? a->total_runtime : -1;
-	tot_b = b->status == ALIVE ? b->total_runtime : -1;
+	tot_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->total_runtime : -1;
+	tot_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->total_runtime : -1;
 
 	tot_b -= tot_a;
 	if (tot_b > 0)
@@ -832,12 +677,12 @@ static int client_total_cmp(const void *_a, const void *_b)
 
 static int client_id_cmp(const void *_a, const void *_b)
 {
-	const struct client *a = _a;
-	const struct client *b = _b;
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
 	int id_a, id_b;
 
-	id_a = a->status == ALIVE ? a->id : -1;
-	id_b = b->status == ALIVE ? b->id : -1;
+	id_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->id : -1;
+	id_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->id : -1;
 
 	id_b -= id_a;
 	if (id_b > 0)
@@ -850,12 +695,12 @@ static int client_id_cmp(const void *_a, const void *_b)
 
 static int client_pid_cmp(const void *_a, const void *_b)
 {
-	const struct client *a = _a;
-	const struct client *b = _b;
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
 	int pid_a, pid_b;
 
-	pid_a = a->status == ALIVE ? a->pid : INT_MAX;
-	pid_b = b->status == ALIVE ? b->pid : INT_MAX;
+	pid_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->pid : INT_MAX;
+	pid_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->pid : INT_MAX;
 
 	pid_b -= pid_a;
 	if (pid_b > 0)
@@ -868,56 +713,19 @@ static int client_pid_cmp(const void *_a, const void *_b)
 
 static int (*client_cmp)(const void *, const void *) = client_last_cmp;
 
-static struct clients *sort_clients(struct clients *clients,
-				    int (*cmp)(const void *, const void *))
-{
-	unsigned int active, free;
-	struct client *c;
-	int tmp;
-
-	if (!clients)
-		return clients;
-
-	qsort(clients->client, clients->num_clients, sizeof(*clients->client),
-	      cmp);
-
-	/* Trim excessive array space. */
-	active = 0;
-	for_each_client(clients, c, tmp) {
-		if (c->status != ALIVE)
-			break; /* Active clients are first in the array. */
-		active++;
-	}
-
-	clients->active_clients = active;
-
-	free = clients->num_clients - active;
-	if (free > clients->num_clients / 2) {
-		active = clients->num_clients - free / 2;
-		if (active != clients->num_clients) {
-			clients->num_clients = active;
-			clients->client = realloc(clients->client,
-						  clients->num_clients *
-						  sizeof(*c));
-		}
-	}
-
-	return clients;
-}
-
 static bool aggregate_pids = true;
 
-static struct clients *display_clients(struct clients *clients)
+static struct igt_drm_clients *display_clients(struct igt_drm_clients *clients)
 {
-	struct client *ac, *c, *cp = NULL;
-	struct clients *aggregated;
+	struct igt_drm_client *ac, *c, *cp = NULL;
+	struct igt_drm_clients *aggregated;
 	int tmp, num = 0;
 
 	if (!aggregate_pids)
 		goto out;
 
 	/* Sort by pid first to make it easy to aggregate while walking. */
-	sort_clients(clients, client_pid_cmp);
+	igt_drm_clients_sort(clients, client_pid_cmp);
 
 	aggregated = calloc(1, sizeof(*clients));
 	assert(aggregated);
@@ -926,23 +734,24 @@ static struct clients *display_clients(struct clients *clients)
 	assert(ac);
 
 	aggregated->num_classes = clients->num_classes;
-	aggregated->class = clients->class;
+	aggregated->engine_class = clients->engine_class;
+	aggregated->private_data = clients->private_data;
 	aggregated->client = ac;
 
-	for_each_client(clients, c, tmp) {
+	igt_for_each_drm_client(clients, c, tmp) {
 		unsigned int i;
 
-		if (c->status == FREE)
+		if (c->status == IGT_DRM_CLIENT_FREE)
 			break;
 
-		assert(c->status == ALIVE);
+		assert(c->status == IGT_DRM_CLIENT_ALIVE);
 
 		if ((cp && c->pid != cp->pid) || !cp) {
 			ac = &aggregated->client[num++];
 
 			/* New pid. */
 			ac->clients = aggregated;
-			ac->status = ALIVE;
+			ac->status = IGT_DRM_CLIENT_ALIVE;
 			ac->id = -c->pid;
 			ac->pid = c->pid;
 			strcpy(ac->name, c->name);
@@ -972,213 +781,21 @@ static struct clients *display_clients(struct clients *clients)
 	clients = aggregated;
 
 out:
-	return sort_clients(clients, client_cmp);
+	return igt_drm_clients_sort(clients, client_cmp);
 }
 
-static void free_clients(struct clients *clients)
+static void free_display_clients(struct igt_drm_clients *clients)
 {
-	struct client *c;
-	unsigned int tmp;
+	struct igt_drm_client *c;
+	int tmp;
 
-	for_each_client(clients, c, tmp) {
+	igt_for_each_drm_client(clients, c, tmp)
 		free(c->val);
-		free(c->last);
-	}
 
 	free(clients->client);
 	free(clients);
 }
 
-static bool is_drm_fd(DIR *fd_dir, const char *name)
-{
-	struct stat stat;
-	int ret;
-
-	ret = fstatat(dirfd(fd_dir), name, &stat, 0);
-
-	return ret == 0 &&
-	       (stat.st_mode & S_IFMT) == S_IFCHR &&
-	       major(stat.st_rdev) == 226;
-}
-
-static bool get_task_name(const char *buffer, char *out, unsigned long sz)
-{
-	char *s = index(buffer, '(');
-	char *e = rindex(buffer, ')');
-	unsigned int len;
-
-	if (!s || !e)
-		return false;
-
-	len = --e - ++s + 1;
-	if(!len || (len + 1) >= sz)
-		return false;
-
-	strncpy(out, s, len);
-	out[len] = 0;
-
-	return true;
-}
-
-static DIR *opendirat(DIR *at, const char *name)
-{
-	DIR *dir;
-	int fd;
-
-	fd = openat(dirfd(at), name, O_DIRECTORY);
-	if (fd < 0)
-		return NULL;
-
-	dir = fdopendir(fd);
-	if (!dir)
-		close(fd);
-
-	return dir;
-}
-
-static FILE *fropenat(DIR *at, const char *name)
-{
-	FILE *f;
-	int fd;
-
-	fd = openat(dirfd(at), name, O_RDONLY);
-	if (fd < 0)
-		return NULL;
-
-	f = fdopen(fd, "r");
-	if (!f)
-		close(fd);
-
-	return f;
-}
-
-static size_t freadat2buf(char *buf, const size_t sz, DIR *at, const char *name)
-{
-	size_t count;
-	FILE *f;
-
-	f = fropenat(at, name);
-	if (!f)
-		return 0;
-
-	memset(buf, 0, sz);
-	count = fread(buf, 1, sz, f);
-	fclose(f);
-
-	return count;
-}
-
-static struct clients *scan_clients(struct clients *clients)
-{
-	struct dirent *proc_dent;
-	struct client *c;
-	DIR *proc_dir;
-	int tmp;
-
-	if (!clients)
-		return clients;
-
-	for_each_client(clients, c, tmp) {
-		assert(c->status != PROBE);
-		if (c->status == ALIVE)
-			c->status = PROBE;
-		else
-			break; /* Free block at the end of array. */
-	}
-
-	proc_dir = opendir("/proc");
-	if (!proc_dir)
-		return clients;
-
-	while ((proc_dent = readdir(proc_dir)) != NULL) {
-		DIR *pid_dir = NULL, *fd_dir = NULL, *fdinfo_dir = NULL;
-		struct dirent *fdinfo_dent;
-		char client_name[64] = { };
-		unsigned int client_pid;
-		char buf[4096];
-		size_t count;
-
-		if (proc_dent->d_type != DT_DIR)
-			continue;
-		if (!isdigit(proc_dent->d_name[0]))
-			continue;
-
-		pid_dir = opendirat(proc_dir, proc_dent->d_name);
-		if (!pid_dir)
-			continue;
-
-		count = freadat2buf(buf, sizeof(buf), pid_dir, "stat");
-		if (!count)
-			goto next;
-
-		client_pid = atoi(buf);
-		if (!client_pid)
-			goto next;
-
-		if (!get_task_name(buf, client_name, sizeof(client_name)))
-			goto next;
-
-		fd_dir = opendirat(pid_dir, "fd");
-		if (!fd_dir)
-			goto next;
-
-		fdinfo_dir = opendirat(pid_dir, "fdinfo");
-		if (!fdinfo_dir)
-			goto next;
-
-		while ((fdinfo_dent = readdir(fdinfo_dir)) != NULL) {
-			struct drm_client_fdinfo info = { };
-
-			if (fdinfo_dent->d_type != DT_REG)
-				continue;
-			if (!isdigit(fdinfo_dent->d_name[0]))
-				continue;
-
-			if (!is_drm_fd(fd_dir, fdinfo_dent->d_name))
-				continue;
-
-			if (!__igt_parse_drm_fdinfo(dirfd(fdinfo_dir),
-						    fdinfo_dent->d_name,
-						    &info))
-				continue;
-
-			if (strcmp(info.driver, "i915"))
-				continue;
-			if (strcmp(info.pdev, clients->pci_slot))
-				continue;
-			if (find_client(clients, ALIVE, info.id))
-				continue; /* Skip duplicate fds. */
-
-			c = find_client(clients, PROBE, info.id);
-			if (!c)
-				add_client(clients, info.id, client_pid,
-					   client_name, info.busy);
-			else
-				update_client(c, client_pid, client_name,
-					      info.busy);
-		}
-
-next:
-		if (fdinfo_dir)
-			closedir(fdinfo_dir);
-		if (fd_dir)
-			closedir(fd_dir);
-		if (pid_dir)
-			closedir(pid_dir);
-	}
-
-	closedir(proc_dir);
-
-	for_each_client(clients, c, tmp) {
-		if (c->status == PROBE)
-			free_client(c);
-		else if (c->status == FREE)
-			break;
-	}
-
-	return display_clients(clients);
-}
-
 static const char *bars[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" };
 
 static void n_spaces(const unsigned int n)
@@ -1881,15 +1498,15 @@ print_engines_footer(struct engines *engines, double t,
 
 static int class_cmp(const void *_a, const void *_b)
 {
-	const struct engine_class *a = _a;
-	const struct engine_class *b = _b;
+	const struct igt_drm_client_engine_class *a = _a;
+	const struct igt_drm_client_engine_class *b = _b;
 
-	return a->class - b->class;
+	return a->engine_class - b->engine_class;
 }
 
 static void init_engine_classes(struct engines *engines)
 {
-	struct engine_class *classes;
+	struct igt_drm_client_engine_class *classes;
 	unsigned int i, num;
 	int max = -1;
 
@@ -1916,7 +1533,7 @@ static void init_engine_classes(struct engines *engines)
 	}
 
 	for (i = 0; i < num; i++) {
-		classes[i].class = i;
+		classes[i].engine_class = i;
 		classes[i].name = class_display_name(i);
 	}
 
@@ -2058,7 +1675,7 @@ print_engines(struct engines *engines, double t, int lines, int w, int h)
 }
 
 static int
-print_clients_header(struct clients *clients, int lines,
+print_clients_header(struct igt_drm_clients *clients, int lines,
 		     int con_w, int con_h, int *class_w)
 {
 	if (output_mode == INTERACTIVE) {
@@ -2080,19 +1697,19 @@ print_clients_header(struct clients *clients, int lines,
 			int width;
 
 			for (i = 0; i < clients->num_classes; i++) {
-				if (clients->class[i].num_engines)
+				if (clients->engine_class[i].num_engines)
 					num_active++;
 			}
 
 			*class_w = width = (con_w - len) / num_active;
 
 			for (i = 0; i < clients->num_classes; i++) {
-				const char *name = clients->class[i].name;
+				const char *name = clients->engine_class[i].name;
 				int name_len = strlen(name);
 				int pad = (width - name_len) / 2;
 				int spaces = width - pad - name_len;
 
-				if (!clients->class[i].num_engines)
+				if (!clients->engine_class[i].num_engines)
 					continue; /* Assert in the ideal world. */
 
 				if (pad < 0 || spaces < 0)
@@ -2119,10 +1736,10 @@ static bool numeric_clients;
 static bool filter_idle;
 
 static int
-print_client(struct client *c, struct engines *engines, double t, int lines,
+print_client(struct igt_drm_client *c, struct engines *engines, double t, int lines,
 	     int con_w, int con_h, unsigned int period_us, int *class_w)
 {
-	struct clients *clients = c->clients;
+	struct igt_drm_clients *clients = c->clients;
 	unsigned int i;
 
 	if (output_mode == INTERACTIVE) {
@@ -2136,11 +1753,11 @@ print_client(struct client *c, struct engines *engines, double t, int lines,
 		for (i = 0; c->samples > 1 && i < clients->num_classes; i++) {
 			double pct;
 
-			if (!clients->class[i].num_engines)
+			if (!clients->engine_class[i].num_engines)
 				continue; /* Assert in the ideal world. */
 
 			pct = (double)c->val[i] / period_us / 1e3 * 100 /
-			      clients->class[i].num_engines;
+			      clients->engine_class[i].num_engines;
 
 			/*
 			 * Guard against possible time-drift between sampling
@@ -2172,7 +1789,7 @@ print_client(struct client *c, struct engines *engines, double t, int lines,
 				double pct;
 
 				snprintf(buf, sizeof(buf), "%s",
-					clients->class[i].name);
+					clients->engine_class[i].name);
 				pops->open_struct(buf);
 
 				pct = (double)c->val[i] / period_us / 1e3 * 100;
@@ -2194,7 +1811,7 @@ print_client(struct client *c, struct engines *engines, double t, int lines,
 }
 
 static int
-print_clients_footer(struct clients *clients, double t,
+print_clients_footer(struct igt_drm_clients *clients, double t,
 		     int lines, int con_w, int con_h)
 {
 	if (output_mode == INTERACTIVE) {
@@ -2398,10 +2015,21 @@ static void show_help_screen(void)
 "\n");
 }
 
+static bool client_match(const struct igt_drm_clients *clients,
+			 const struct drm_client_fdinfo *info)
+{
+	if (strcmp(info->driver, "i915"))
+		return false;
+	if (strcmp(info->pdev, clients->private_data))
+		return false;
+
+	return true;
+}
+
 int main(int argc, char **argv)
 {
 	unsigned int period_us = DEFAULT_PERIOD_MS * 1000;
-	struct clients *clients = NULL;
+	struct igt_drm_clients *clients = NULL;
 	int con_w = -1, con_h = -1;
 	char *output_path = NULL;
 	struct engines *engines;
@@ -2540,24 +2168,24 @@ int main(int argc, char **argv)
 
 	ret = EXIT_SUCCESS;
 
-	clients = init_clients(card.pci_slot_name[0] ?
-			       card.pci_slot_name : IGPU_PCI);
+	clients = igt_drm_clients_init(strdup(card.pci_slot_name[0] ?
+					      card.pci_slot_name : IGPU_PCI));
 	init_engine_classes(engines);
 	if (clients) {
 		clients->num_classes = engines->num_classes;
-		clients->class = engines->class;
+		clients->engine_class = engines->class;
 	}
 
 	pmu_sample(engines);
-	scan_clients(clients);
+	igt_drm_clients_scan(clients, client_match);
 	codename = igt_device_get_pretty_name(&card, false);
 
 	while (!stop_top) {
-		struct clients *disp_clients;
+		struct igt_drm_clients *disp_clients;
 		bool consumed = false;
 		int j, lines = 0;
 		struct winsize ws;
-		struct client *c;
+		struct igt_drm_client *c;
 		double t;
 
 		/* Update terminal size. */
@@ -2576,7 +2204,9 @@ int main(int argc, char **argv)
 		pmu_sample(engines);
 		t = (double)(engines->ts.cur - engines->ts.prev) / 1e9;
 
-		disp_clients = scan_clients(clients);
+		disp_clients =
+			display_clients(igt_drm_clients_scan(clients,
+							     client_match));
 
 		if (stop_top)
 			break;
@@ -2604,9 +2234,9 @@ int main(int argc, char **argv)
 							     con_w, con_h,
 							     &class_w);
 
-				for_each_client(disp_clients, c, j) {
-					assert(c->status != PROBE);
-					if (c->status != ALIVE)
+				igt_for_each_drm_client(disp_clients, c, j) {
+					assert(c->status != IGT_DRM_CLIENT_PROBE);
+					if (c->status != IGT_DRM_CLIENT_ALIVE)
 						break; /* Active clients are first in the array. */
 
 					if (lines >= con_h)
@@ -2630,7 +2260,7 @@ int main(int argc, char **argv)
 			break;
 
 		if (disp_clients != clients)
-			free_clients(disp_clients);
+			free_display_clients(disp_clients);
 
 		if (output_mode == INTERACTIVE)
 			process_stdin(period_us);
diff --git a/tools/meson.build b/tools/meson.build
index 771d0b9e3d5d..e65aceeef9fa 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -91,7 +91,7 @@ install_subdir('registers', install_dir : datadir)
 executable('intel_gpu_top', 'intel_gpu_top.c',
 	   install : true,
 	   install_rpath : bindir_rpathdir,
-	   dependencies : [lib_igt_perf,lib_igt_device_scan,lib_igt_drm_fdinfo,math])
+	   dependencies : [lib_igt_perf,lib_igt_device_scan,lib_igt_drm_clients,lib_igt_drm_fdinfo,math])
 
 executable('amd_hdmi_compliance', 'amd_hdmi_compliance.c',
 	   dependencies : [tool_deps],
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [igt-dev] [PATCH i-g-t 04/11] lib: Extract igt_drm_clients from intel_gpu_top
@ 2022-02-22 13:55   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Code movement with some improvements to prepare for further work in
making a vendor agnostic gputop tool possible.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c | 386 +++++++++++++++++++++++++++++++
 lib/igt_drm_clients.h | 102 +++++++++
 lib/meson.build       |   8 +
 tools/intel_gpu_top.c | 516 ++++++------------------------------------
 tools/meson.build     |   2 +-
 5 files changed, 570 insertions(+), 444 deletions(-)
 create mode 100644 lib/igt_drm_clients.c
 create mode 100644 lib/igt_drm_clients.h

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
new file mode 100644
index 000000000000..58d82648d821
--- /dev/null
+++ b/lib/igt_drm_clients.c
@@ -0,0 +1,386 @@
+/*
+ * 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 <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "igt_drm_clients.h"
+#include "igt_drm_fdinfo.h"
+
+struct igt_drm_clients *igt_drm_clients_init(void *private_data)
+{
+	struct igt_drm_clients *clients;
+
+	clients = malloc(sizeof(*clients));
+	if (!clients)
+		return NULL;
+
+	memset(clients, 0, sizeof(*clients));
+
+	clients->private_data = private_data;
+
+	return clients;
+}
+
+struct igt_drm_client *
+igt_drm_clients_find(struct igt_drm_clients *clients,
+		     enum igt_drm_client_status status,
+		     unsigned int id)
+{
+	unsigned int start, num;
+	struct igt_drm_client *c;
+
+	start = status == IGT_DRM_CLIENT_FREE ? clients->active_clients : 0; /* Free block at the end. */
+	num = clients->num_clients - start;
+
+	for (c = &clients->client[start]; num; c++, num--) {
+		if (status != c->status)
+			continue;
+
+		if (status == IGT_DRM_CLIENT_FREE || c->id == id)
+			return c;
+	}
+
+	return NULL;
+}
+
+void
+igt_drm_client_update(struct igt_drm_client *c, unsigned int pid, char *name,
+		      uint64_t val[16])
+{
+	unsigned int i;
+
+	if (c->pid != pid)
+		c->pid = pid;
+
+	if (strcmp(c->name, name)) {
+		char *p;
+
+		strncpy(c->name, name, sizeof(c->name) - 1);
+		strncpy(c->print_name, name, sizeof(c->print_name) - 1);
+
+		p = c->print_name;
+		while (*p) {
+			if (!isprint(*p))
+				*p = '*';
+			p++;
+		}
+	}
+
+	c->last_runtime = 0;
+	c->total_runtime = 0;
+
+	for (i = 0; i < c->clients->num_classes; i++) {
+		if (val[i] < c->last[i])
+			continue; /* It will catch up soon. */
+
+		c->total_runtime += val[i];
+		c->val[i] = val[i] - c->last[i];
+		c->last_runtime += c->val[i];
+		c->last[i] = val[i];
+	}
+
+	c->samples++;
+	c->status = IGT_DRM_CLIENT_ALIVE;
+}
+
+void
+igt_drm_client_add(struct igt_drm_clients *clients, unsigned int id,
+		   unsigned int pid, char *name, uint64_t busy[16])
+{
+	struct igt_drm_client *c;
+
+	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE, id));
+
+	c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_FREE, 0);
+	if (!c) {
+		unsigned int idx = clients->num_clients;
+
+		clients->num_clients += (clients->num_clients + 2) / 2;
+		clients->client = realloc(clients->client,
+					  clients->num_clients * sizeof(*c));
+		assert(clients->client);
+
+		c = &clients->client[idx];
+		memset(c, 0, (clients->num_clients - idx) * sizeof(*c));
+	}
+
+	c->id = id;
+	c->clients = clients;
+	c->val = calloc(clients->num_classes, sizeof(c->val));
+	c->last = calloc(clients->num_classes, sizeof(c->last));
+	assert(c->val && c->last);
+
+	igt_drm_client_update(c, pid, name, busy);
+}
+
+void igt_drm_client_free(struct igt_drm_client *c)
+{
+	free(c->val);
+	free(c->last);
+	memset(c, 0, sizeof(*c));
+}
+
+struct igt_drm_clients *
+igt_drm_clients_sort(struct igt_drm_clients *clients,
+		     int (*cmp)(const void *, const void *))
+{
+	unsigned int active, free;
+	struct igt_drm_client *c;
+	int tmp;
+
+	if (!clients)
+		return clients;
+
+	qsort(clients->client, clients->num_clients, sizeof(*clients->client),
+	      cmp);
+
+	/* Trim excessive array space. */
+	active = 0;
+	igt_for_each_drm_client(clients, c, tmp) {
+		if (c->status != IGT_DRM_CLIENT_ALIVE)
+			break; /* Active clients are first in the array. */
+		active++;
+	}
+
+	clients->active_clients = active;
+
+	free = clients->num_clients - active;
+	if (free > clients->num_clients / 2) {
+		active = clients->num_clients - free / 2;
+		if (active != clients->num_clients) {
+			clients->num_clients = active;
+			clients->client = realloc(clients->client,
+						  clients->num_clients *
+						  sizeof(*c));
+		}
+	}
+
+	return clients;
+}
+
+void igt_drm_clients_free(struct igt_drm_clients *clients)
+{
+	struct igt_drm_client *c;
+	unsigned int tmp;
+
+	igt_for_each_drm_client(clients, c, tmp)
+		igt_drm_client_free(c);
+
+	free(clients->client);
+	free(clients);
+}
+
+static DIR *opendirat(int at, const char *name)
+{
+	DIR *dir;
+	int fd;
+
+	fd = openat(at, name, O_DIRECTORY);
+	if (fd < 0)
+		return NULL;
+
+	dir = fdopendir(fd);
+	if (!dir)
+		close(fd);
+
+	return dir;
+}
+
+static size_t readat2buf(char *buf, const size_t sz, int at, const char *name)
+{
+	size_t count;
+	int fd;
+
+	fd = openat(at, name, O_RDONLY);
+	if (fd < 0)
+		return 0;
+
+	buf[sz - 1] = 0;
+	count = read(fd, buf, sz);
+	close(fd);
+
+	return count;
+}
+
+static bool get_task_name(const char *buffer, char *out, unsigned long sz)
+{
+	char *s = index(buffer, '(');
+	char *e = rindex(buffer, ')');
+	unsigned int len;
+
+	if (!s || !e)
+		return false;
+
+	len = --e - ++s + 1;
+	if(!len || (len + 1) >= sz)
+		return false;
+
+	strncpy(out, s, len);
+	out[len] = 0;
+
+	return true;
+}
+
+static bool is_drm_fd(int fd_dir, const char *name)
+{
+	struct stat stat;
+	int ret;
+
+	ret = fstatat(fd_dir, name, &stat, 0);
+
+	return ret == 0 &&
+	       (stat.st_mode & S_IFMT) == S_IFCHR &&
+	       major(stat.st_rdev) == 226;
+}
+
+struct igt_drm_clients *
+igt_drm_clients_scan(struct igt_drm_clients *clients,
+		     bool (*filter_client)(const struct igt_drm_clients *,
+					   const struct drm_client_fdinfo *))
+{
+	struct dirent *proc_dent;
+	struct igt_drm_client *c;
+	DIR *proc_dir;
+	int tmp;
+
+	if (!clients)
+		return clients;
+
+	igt_for_each_drm_client(clients, c, tmp) {
+		assert(c->status != IGT_DRM_CLIENT_PROBE);
+		if (c->status == IGT_DRM_CLIENT_ALIVE)
+			c->status = IGT_DRM_CLIENT_PROBE;
+		else
+			break; /* Free block at the end of array. */
+	}
+
+	proc_dir = opendir("/proc");
+	if (!proc_dir)
+		return clients;
+
+	while ((proc_dent = readdir(proc_dir)) != NULL) {
+		int pid_dir = -1, fd_dir = -1;
+		struct dirent *fdinfo_dent;
+		char client_name[64] = { };
+		unsigned int client_pid;
+		DIR *fdinfo_dir = NULL;
+		char buf[4096];
+		size_t count;
+
+		if (proc_dent->d_type != DT_DIR)
+			continue;
+		if (!isdigit(proc_dent->d_name[0]))
+			continue;
+
+		pid_dir = openat(dirfd(proc_dir), proc_dent->d_name,
+				 O_DIRECTORY | O_RDONLY);
+		if (pid_dir < 0)
+			continue;
+
+		count = readat2buf(buf, sizeof(buf), pid_dir, "stat");
+		if (!count)
+			goto next;
+
+		client_pid = atoi(buf);
+		if (!client_pid)
+			goto next;
+
+		if (!get_task_name(buf, client_name, sizeof(client_name)))
+			goto next;
+
+		fd_dir = openat(pid_dir, "fd", O_DIRECTORY | O_RDONLY);
+		if (fd_dir < 0)
+			goto next;
+
+		fdinfo_dir = opendirat(pid_dir, "fdinfo");
+		if (!fdinfo_dir)
+			goto next;
+
+		while ((fdinfo_dent = readdir(fdinfo_dir)) != NULL) {
+			struct drm_client_fdinfo info;
+
+			if (fdinfo_dent->d_type != DT_REG)
+				continue;
+			if (!isdigit(fdinfo_dent->d_name[0]))
+				continue;
+
+			if (!is_drm_fd(fd_dir, fdinfo_dent->d_name))
+				continue;
+
+			memset(&info, 0, sizeof(info));
+
+			if (!__igt_parse_drm_fdinfo(dirfd(fdinfo_dir),
+						    fdinfo_dent->d_name,
+						    &info))
+				continue;
+
+			if (filter_client && !filter_client(clients, &info))
+				continue;
+
+			if (igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE,
+						info.id))
+				continue; /* Skip duplicate fds. */
+
+			c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_PROBE,
+						info.id);
+			if (!c)
+				igt_drm_client_add(clients, info.id, client_pid,
+						   client_name, info.busy);
+			else
+				igt_drm_client_update(c, client_pid,
+						      client_name, info.busy);
+		}
+
+next:
+		if (fdinfo_dir)
+			closedir(fdinfo_dir);
+		if (fd_dir >= 0)
+			close(fd_dir);
+		if (pid_dir >= 0)
+			close(pid_dir);
+	}
+
+	closedir(proc_dir);
+
+	igt_for_each_drm_client(clients, c, tmp) {
+		if (c->status == IGT_DRM_CLIENT_PROBE)
+			igt_drm_client_free(c);
+		else if (c->status == IGT_DRM_CLIENT_FREE)
+			break;
+	}
+
+	return clients;
+}
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
new file mode 100644
index 000000000000..f52080847291
--- /dev/null
+++ b/lib/igt_drm_clients.h
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef IGT_DRM_CLIENTS_H
+#define IGT_DRM_CLIENTS_H
+
+#include <stdint.h>
+
+struct drm_client_fdinfo;
+
+enum igt_drm_client_status {
+	IGT_DRM_CLIENT_FREE = 0, /* mbz */
+	IGT_DRM_CLIENT_ALIVE,
+	IGT_DRM_CLIENT_PROBE
+};
+
+struct igt_drm_client_engine_class {
+	unsigned int engine_class;
+	const char *name;
+	unsigned int num_engines;
+};
+
+struct igt_drm_clients;
+
+struct igt_drm_client {
+	struct igt_drm_clients *clients;
+
+	enum igt_drm_client_status status;
+	unsigned int id;
+	unsigned int pid;
+	char name[24];
+	char print_name[24];
+	unsigned int samples;
+	unsigned long total_runtime;
+	unsigned long last_runtime;
+	unsigned long *val;
+	uint64_t *last;
+};
+
+struct igt_drm_clients {
+	unsigned int num_clients;
+	unsigned int active_clients;
+
+	unsigned int num_classes;
+	struct igt_drm_client_engine_class *engine_class;
+
+	void *private_data;
+
+	struct igt_drm_client *client; /* Must be last. */
+};
+
+#define igt_for_each_drm_client(clients, c, tmp) \
+	for ((tmp) = (clients)->num_clients, c = (clients)->client; \
+	     (tmp > 0); (tmp)--, (c)++)
+
+struct igt_drm_clients *igt_drm_clients_init(void *private_data);
+void igt_drm_clients_free(struct igt_drm_clients *clients);
+
+struct igt_drm_clients *
+igt_drm_clients_scan(struct igt_drm_clients *clients,
+		     bool (*filter_client)(const struct igt_drm_clients *,
+					   const struct drm_client_fdinfo *));
+
+struct igt_drm_client *
+igt_drm_clients_find(struct igt_drm_clients *clients,
+		     enum igt_drm_client_status status,
+		     unsigned int id);
+
+struct igt_drm_clients *
+igt_drm_clients_sort(struct igt_drm_clients *clients,
+		     int (*cmp)(const void *, const void *));
+
+void igt_drm_client_update(struct igt_drm_client *c,
+			   unsigned int pid, char *name, uint64_t val[16]);
+
+void igt_drm_client_add(struct igt_drm_clients *clients, unsigned int id,
+			unsigned int pid, char *name, uint64_t busy[16]);
+
+void igt_drm_client_free(struct igt_drm_client *c);
+
+#endif /* IGT_DRM_CLIENTS_H */
diff --git a/lib/meson.build b/lib/meson.build
index d00817dc58bc..131bb749ed85 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -16,6 +16,7 @@ lib_sources = [
 	'igt_debugfs.c',
 	'igt_device.c',
 	'igt_device_scan.c',
+	'igt_drm_clients.h',
 	'igt_drm_fdinfo.c',
 	'igt_aux.c',
 	'igt_gt.c',
@@ -217,6 +218,13 @@ lib_igt_device_scan_build = static_library('igt_device_scan',
 lib_igt_device_scan = declare_dependency(link_with : lib_igt_device_scan_build,
 				  include_directories : inc)
 
+lib_igt_drm_clients_build = static_library('igt_drm_clients',
+        ['igt_drm_clients.c'],
+        include_directories : inc)
+
+lib_igt_drm_clients = declare_dependency(link_with : lib_igt_drm_clients_build,
+				         include_directories : inc)
+
 lib_igt_drm_fdinfo_build = static_library('igt_drm_fdinfo',
 	['igt_drm_fdinfo.c'],
 	include_directories : inc)
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index 73815cdea8aa..c4f990b2a7d0 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -46,6 +46,7 @@
 #include <sys/sysmacros.h>
 
 #include "igt_perf.h"
+#include "igt_drm_clients.h"
 #include "igt_drm_fdinfo.h"
 
 #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
@@ -80,16 +81,10 @@ struct engine {
 	struct pmu_counter sema;
 };
 
-struct engine_class {
-	unsigned int class;
-	const char *name;
-	unsigned int num_engines;
-};
-
 struct engines {
 	unsigned int num_engines;
 	unsigned int num_classes;
-	struct engine_class *class;
+	struct igt_drm_client_engine_class *class;
 	unsigned int num_counters;
 	DIR *root;
 	int fd;
@@ -638,160 +633,10 @@ static void pmu_sample(struct engines *engines)
 	}
 }
 
-enum client_status {
-	FREE = 0, /* mbz */
-	ALIVE,
-	PROBE
-};
-
-struct clients;
-
-struct client {
-	struct clients *clients;
-
-	enum client_status status;
-	unsigned int id;
-	unsigned int pid;
-	char name[24];
-	char print_name[24];
-	unsigned int samples;
-	unsigned long total_runtime;
-	unsigned long last_runtime;
-	unsigned long *val;
-	uint64_t *last;
-};
-
-struct clients {
-	unsigned int num_clients;
-	unsigned int active_clients;
-
-	unsigned int num_classes;
-	struct engine_class *class;
-
-	char pci_slot[64];
-
-	struct client *client;
-};
-
-#define for_each_client(clients, c, tmp) \
-	for ((tmp) = (clients)->num_clients, c = (clients)->client; \
-	     (tmp > 0); (tmp)--, (c)++)
-
-static struct clients *init_clients(const char *pci_slot)
-{
-	struct clients *clients;
-
-	clients = malloc(sizeof(*clients));
-	if (!clients)
-		return NULL;
-
-	memset(clients, 0, sizeof(*clients));
-
-	strncpy(clients->pci_slot, pci_slot, sizeof(clients->pci_slot));
-
-	return clients;
-}
-
-static struct client *
-find_client(struct clients *clients, enum client_status status, unsigned int id)
-{
-	unsigned int start, num;
-	struct client *c;
-
-	start = status == FREE ? clients->active_clients : 0; /* Free block at the end. */
-	num = clients->num_clients - start;
-
-	for (c = &clients->client[start]; num; c++, num--) {
-		if (status != c->status)
-			continue;
-
-		if (status == FREE || c->id == id)
-			return c;
-	}
-
-	return NULL;
-}
-
-static void
-update_client(struct client *c, unsigned int pid, char *name, uint64_t val[16])
-{
-	unsigned int i;
-
-	if (c->pid != pid)
-		c->pid = pid;
-
-	if (strcmp(c->name, name)) {
-		char *p;
-
-		strncpy(c->name, name, sizeof(c->name) - 1);
-		strncpy(c->print_name, name, sizeof(c->print_name) - 1);
-
-		p = c->print_name;
-		while (*p) {
-			if (!isprint(*p))
-				*p = '*';
-			p++;
-		}
-	}
-
-	c->last_runtime = 0;
-	c->total_runtime = 0;
-
-	for (i = 0; i < c->clients->num_classes; i++) {
-		if (val[i] < c->last[i])
-			continue; /* It will catch up soon. */
-
-		c->total_runtime += val[i];
-		c->val[i] = val[i] - c->last[i];
-		c->last_runtime += c->val[i];
-		c->last[i] = val[i];
-	}
-
-	c->samples++;
-	c->status = ALIVE;
-}
-
-static void
-add_client(struct clients *clients, unsigned int id, unsigned int pid,
-	   char *name, uint64_t busy[16])
-{
-	struct client *c;
-
-	assert(!find_client(clients, ALIVE, id));
-
-	c = find_client(clients, FREE, 0);
-	if (!c) {
-		unsigned int idx = clients->num_clients;
-
-		clients->num_clients += (clients->num_clients + 2) / 2;
-		clients->client = realloc(clients->client,
-					  clients->num_clients * sizeof(*c));
-		assert(clients->client);
-
-		c = &clients->client[idx];
-		memset(c, 0, (clients->num_clients - idx) * sizeof(*c));
-	}
-
-	c->id = id;
-	c->clients = clients;
-	c->val = calloc(clients->num_classes, sizeof(c->val));
-	c->last = calloc(clients->num_classes, sizeof(c->last));
-	assert(c->val && c->last);
-
-	update_client(c, pid, name, busy);
-}
-
-static void free_client(struct client *c)
-{
-	free(c->val);
-	free(c->last);
-	memset(c, 0, sizeof(*c));
-}
-
 static int client_last_cmp(const void *_a, const void *_b)
 {
-	const struct client *a = _a;
-	const struct client *b = _b;
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
 	long tot_a, tot_b;
 
 	/*
@@ -800,8 +645,8 @@ static int client_last_cmp(const void *_a, const void *_b)
 	 * id.
 	 */
 
-	tot_a = a->status == ALIVE ? a->last_runtime : -1;
-	tot_b = b->status == ALIVE ? b->last_runtime : -1;
+	tot_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->last_runtime : -1;
+	tot_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->last_runtime : -1;
 
 	tot_b -= tot_a;
 	if (tot_b > 0)
@@ -814,12 +659,12 @@ static int client_last_cmp(const void *_a, const void *_b)
 
 static int client_total_cmp(const void *_a, const void *_b)
 {
-	const struct client *a = _a;
-	const struct client *b = _b;
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
 	long tot_a, tot_b;
 
-	tot_a = a->status == ALIVE ? a->total_runtime : -1;
-	tot_b = b->status == ALIVE ? b->total_runtime : -1;
+	tot_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->total_runtime : -1;
+	tot_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->total_runtime : -1;
 
 	tot_b -= tot_a;
 	if (tot_b > 0)
@@ -832,12 +677,12 @@ static int client_total_cmp(const void *_a, const void *_b)
 
 static int client_id_cmp(const void *_a, const void *_b)
 {
-	const struct client *a = _a;
-	const struct client *b = _b;
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
 	int id_a, id_b;
 
-	id_a = a->status == ALIVE ? a->id : -1;
-	id_b = b->status == ALIVE ? b->id : -1;
+	id_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->id : -1;
+	id_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->id : -1;
 
 	id_b -= id_a;
 	if (id_b > 0)
@@ -850,12 +695,12 @@ static int client_id_cmp(const void *_a, const void *_b)
 
 static int client_pid_cmp(const void *_a, const void *_b)
 {
-	const struct client *a = _a;
-	const struct client *b = _b;
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
 	int pid_a, pid_b;
 
-	pid_a = a->status == ALIVE ? a->pid : INT_MAX;
-	pid_b = b->status == ALIVE ? b->pid : INT_MAX;
+	pid_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->pid : INT_MAX;
+	pid_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->pid : INT_MAX;
 
 	pid_b -= pid_a;
 	if (pid_b > 0)
@@ -868,56 +713,19 @@ static int client_pid_cmp(const void *_a, const void *_b)
 
 static int (*client_cmp)(const void *, const void *) = client_last_cmp;
 
-static struct clients *sort_clients(struct clients *clients,
-				    int (*cmp)(const void *, const void *))
-{
-	unsigned int active, free;
-	struct client *c;
-	int tmp;
-
-	if (!clients)
-		return clients;
-
-	qsort(clients->client, clients->num_clients, sizeof(*clients->client),
-	      cmp);
-
-	/* Trim excessive array space. */
-	active = 0;
-	for_each_client(clients, c, tmp) {
-		if (c->status != ALIVE)
-			break; /* Active clients are first in the array. */
-		active++;
-	}
-
-	clients->active_clients = active;
-
-	free = clients->num_clients - active;
-	if (free > clients->num_clients / 2) {
-		active = clients->num_clients - free / 2;
-		if (active != clients->num_clients) {
-			clients->num_clients = active;
-			clients->client = realloc(clients->client,
-						  clients->num_clients *
-						  sizeof(*c));
-		}
-	}
-
-	return clients;
-}
-
 static bool aggregate_pids = true;
 
-static struct clients *display_clients(struct clients *clients)
+static struct igt_drm_clients *display_clients(struct igt_drm_clients *clients)
 {
-	struct client *ac, *c, *cp = NULL;
-	struct clients *aggregated;
+	struct igt_drm_client *ac, *c, *cp = NULL;
+	struct igt_drm_clients *aggregated;
 	int tmp, num = 0;
 
 	if (!aggregate_pids)
 		goto out;
 
 	/* Sort by pid first to make it easy to aggregate while walking. */
-	sort_clients(clients, client_pid_cmp);
+	igt_drm_clients_sort(clients, client_pid_cmp);
 
 	aggregated = calloc(1, sizeof(*clients));
 	assert(aggregated);
@@ -926,23 +734,24 @@ static struct clients *display_clients(struct clients *clients)
 	assert(ac);
 
 	aggregated->num_classes = clients->num_classes;
-	aggregated->class = clients->class;
+	aggregated->engine_class = clients->engine_class;
+	aggregated->private_data = clients->private_data;
 	aggregated->client = ac;
 
-	for_each_client(clients, c, tmp) {
+	igt_for_each_drm_client(clients, c, tmp) {
 		unsigned int i;
 
-		if (c->status == FREE)
+		if (c->status == IGT_DRM_CLIENT_FREE)
 			break;
 
-		assert(c->status == ALIVE);
+		assert(c->status == IGT_DRM_CLIENT_ALIVE);
 
 		if ((cp && c->pid != cp->pid) || !cp) {
 			ac = &aggregated->client[num++];
 
 			/* New pid. */
 			ac->clients = aggregated;
-			ac->status = ALIVE;
+			ac->status = IGT_DRM_CLIENT_ALIVE;
 			ac->id = -c->pid;
 			ac->pid = c->pid;
 			strcpy(ac->name, c->name);
@@ -972,213 +781,21 @@ static struct clients *display_clients(struct clients *clients)
 	clients = aggregated;
 
 out:
-	return sort_clients(clients, client_cmp);
+	return igt_drm_clients_sort(clients, client_cmp);
 }
 
-static void free_clients(struct clients *clients)
+static void free_display_clients(struct igt_drm_clients *clients)
 {
-	struct client *c;
-	unsigned int tmp;
+	struct igt_drm_client *c;
+	int tmp;
 
-	for_each_client(clients, c, tmp) {
+	igt_for_each_drm_client(clients, c, tmp)
 		free(c->val);
-		free(c->last);
-	}
 
 	free(clients->client);
 	free(clients);
 }
 
-static bool is_drm_fd(DIR *fd_dir, const char *name)
-{
-	struct stat stat;
-	int ret;
-
-	ret = fstatat(dirfd(fd_dir), name, &stat, 0);
-
-	return ret == 0 &&
-	       (stat.st_mode & S_IFMT) == S_IFCHR &&
-	       major(stat.st_rdev) == 226;
-}
-
-static bool get_task_name(const char *buffer, char *out, unsigned long sz)
-{
-	char *s = index(buffer, '(');
-	char *e = rindex(buffer, ')');
-	unsigned int len;
-
-	if (!s || !e)
-		return false;
-
-	len = --e - ++s + 1;
-	if(!len || (len + 1) >= sz)
-		return false;
-
-	strncpy(out, s, len);
-	out[len] = 0;
-
-	return true;
-}
-
-static DIR *opendirat(DIR *at, const char *name)
-{
-	DIR *dir;
-	int fd;
-
-	fd = openat(dirfd(at), name, O_DIRECTORY);
-	if (fd < 0)
-		return NULL;
-
-	dir = fdopendir(fd);
-	if (!dir)
-		close(fd);
-
-	return dir;
-}
-
-static FILE *fropenat(DIR *at, const char *name)
-{
-	FILE *f;
-	int fd;
-
-	fd = openat(dirfd(at), name, O_RDONLY);
-	if (fd < 0)
-		return NULL;
-
-	f = fdopen(fd, "r");
-	if (!f)
-		close(fd);
-
-	return f;
-}
-
-static size_t freadat2buf(char *buf, const size_t sz, DIR *at, const char *name)
-{
-	size_t count;
-	FILE *f;
-
-	f = fropenat(at, name);
-	if (!f)
-		return 0;
-
-	memset(buf, 0, sz);
-	count = fread(buf, 1, sz, f);
-	fclose(f);
-
-	return count;
-}
-
-static struct clients *scan_clients(struct clients *clients)
-{
-	struct dirent *proc_dent;
-	struct client *c;
-	DIR *proc_dir;
-	int tmp;
-
-	if (!clients)
-		return clients;
-
-	for_each_client(clients, c, tmp) {
-		assert(c->status != PROBE);
-		if (c->status == ALIVE)
-			c->status = PROBE;
-		else
-			break; /* Free block at the end of array. */
-	}
-
-	proc_dir = opendir("/proc");
-	if (!proc_dir)
-		return clients;
-
-	while ((proc_dent = readdir(proc_dir)) != NULL) {
-		DIR *pid_dir = NULL, *fd_dir = NULL, *fdinfo_dir = NULL;
-		struct dirent *fdinfo_dent;
-		char client_name[64] = { };
-		unsigned int client_pid;
-		char buf[4096];
-		size_t count;
-
-		if (proc_dent->d_type != DT_DIR)
-			continue;
-		if (!isdigit(proc_dent->d_name[0]))
-			continue;
-
-		pid_dir = opendirat(proc_dir, proc_dent->d_name);
-		if (!pid_dir)
-			continue;
-
-		count = freadat2buf(buf, sizeof(buf), pid_dir, "stat");
-		if (!count)
-			goto next;
-
-		client_pid = atoi(buf);
-		if (!client_pid)
-			goto next;
-
-		if (!get_task_name(buf, client_name, sizeof(client_name)))
-			goto next;
-
-		fd_dir = opendirat(pid_dir, "fd");
-		if (!fd_dir)
-			goto next;
-
-		fdinfo_dir = opendirat(pid_dir, "fdinfo");
-		if (!fdinfo_dir)
-			goto next;
-
-		while ((fdinfo_dent = readdir(fdinfo_dir)) != NULL) {
-			struct drm_client_fdinfo info = { };
-
-			if (fdinfo_dent->d_type != DT_REG)
-				continue;
-			if (!isdigit(fdinfo_dent->d_name[0]))
-				continue;
-
-			if (!is_drm_fd(fd_dir, fdinfo_dent->d_name))
-				continue;
-
-			if (!__igt_parse_drm_fdinfo(dirfd(fdinfo_dir),
-						    fdinfo_dent->d_name,
-						    &info))
-				continue;
-
-			if (strcmp(info.driver, "i915"))
-				continue;
-			if (strcmp(info.pdev, clients->pci_slot))
-				continue;
-			if (find_client(clients, ALIVE, info.id))
-				continue; /* Skip duplicate fds. */
-
-			c = find_client(clients, PROBE, info.id);
-			if (!c)
-				add_client(clients, info.id, client_pid,
-					   client_name, info.busy);
-			else
-				update_client(c, client_pid, client_name,
-					      info.busy);
-		}
-
-next:
-		if (fdinfo_dir)
-			closedir(fdinfo_dir);
-		if (fd_dir)
-			closedir(fd_dir);
-		if (pid_dir)
-			closedir(pid_dir);
-	}
-
-	closedir(proc_dir);
-
-	for_each_client(clients, c, tmp) {
-		if (c->status == PROBE)
-			free_client(c);
-		else if (c->status == FREE)
-			break;
-	}
-
-	return display_clients(clients);
-}
-
 static const char *bars[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" };
 
 static void n_spaces(const unsigned int n)
@@ -1881,15 +1498,15 @@ print_engines_footer(struct engines *engines, double t,
 
 static int class_cmp(const void *_a, const void *_b)
 {
-	const struct engine_class *a = _a;
-	const struct engine_class *b = _b;
+	const struct igt_drm_client_engine_class *a = _a;
+	const struct igt_drm_client_engine_class *b = _b;
 
-	return a->class - b->class;
+	return a->engine_class - b->engine_class;
 }
 
 static void init_engine_classes(struct engines *engines)
 {
-	struct engine_class *classes;
+	struct igt_drm_client_engine_class *classes;
 	unsigned int i, num;
 	int max = -1;
 
@@ -1916,7 +1533,7 @@ static void init_engine_classes(struct engines *engines)
 	}
 
 	for (i = 0; i < num; i++) {
-		classes[i].class = i;
+		classes[i].engine_class = i;
 		classes[i].name = class_display_name(i);
 	}
 
@@ -2058,7 +1675,7 @@ print_engines(struct engines *engines, double t, int lines, int w, int h)
 }
 
 static int
-print_clients_header(struct clients *clients, int lines,
+print_clients_header(struct igt_drm_clients *clients, int lines,
 		     int con_w, int con_h, int *class_w)
 {
 	if (output_mode == INTERACTIVE) {
@@ -2080,19 +1697,19 @@ print_clients_header(struct clients *clients, int lines,
 			int width;
 
 			for (i = 0; i < clients->num_classes; i++) {
-				if (clients->class[i].num_engines)
+				if (clients->engine_class[i].num_engines)
 					num_active++;
 			}
 
 			*class_w = width = (con_w - len) / num_active;
 
 			for (i = 0; i < clients->num_classes; i++) {
-				const char *name = clients->class[i].name;
+				const char *name = clients->engine_class[i].name;
 				int name_len = strlen(name);
 				int pad = (width - name_len) / 2;
 				int spaces = width - pad - name_len;
 
-				if (!clients->class[i].num_engines)
+				if (!clients->engine_class[i].num_engines)
 					continue; /* Assert in the ideal world. */
 
 				if (pad < 0 || spaces < 0)
@@ -2119,10 +1736,10 @@ static bool numeric_clients;
 static bool filter_idle;
 
 static int
-print_client(struct client *c, struct engines *engines, double t, int lines,
+print_client(struct igt_drm_client *c, struct engines *engines, double t, int lines,
 	     int con_w, int con_h, unsigned int period_us, int *class_w)
 {
-	struct clients *clients = c->clients;
+	struct igt_drm_clients *clients = c->clients;
 	unsigned int i;
 
 	if (output_mode == INTERACTIVE) {
@@ -2136,11 +1753,11 @@ print_client(struct client *c, struct engines *engines, double t, int lines,
 		for (i = 0; c->samples > 1 && i < clients->num_classes; i++) {
 			double pct;
 
-			if (!clients->class[i].num_engines)
+			if (!clients->engine_class[i].num_engines)
 				continue; /* Assert in the ideal world. */
 
 			pct = (double)c->val[i] / period_us / 1e3 * 100 /
-			      clients->class[i].num_engines;
+			      clients->engine_class[i].num_engines;
 
 			/*
 			 * Guard against possible time-drift between sampling
@@ -2172,7 +1789,7 @@ print_client(struct client *c, struct engines *engines, double t, int lines,
 				double pct;
 
 				snprintf(buf, sizeof(buf), "%s",
-					clients->class[i].name);
+					clients->engine_class[i].name);
 				pops->open_struct(buf);
 
 				pct = (double)c->val[i] / period_us / 1e3 * 100;
@@ -2194,7 +1811,7 @@ print_client(struct client *c, struct engines *engines, double t, int lines,
 }
 
 static int
-print_clients_footer(struct clients *clients, double t,
+print_clients_footer(struct igt_drm_clients *clients, double t,
 		     int lines, int con_w, int con_h)
 {
 	if (output_mode == INTERACTIVE) {
@@ -2398,10 +2015,21 @@ static void show_help_screen(void)
 "\n");
 }
 
+static bool client_match(const struct igt_drm_clients *clients,
+			 const struct drm_client_fdinfo *info)
+{
+	if (strcmp(info->driver, "i915"))
+		return false;
+	if (strcmp(info->pdev, clients->private_data))
+		return false;
+
+	return true;
+}
+
 int main(int argc, char **argv)
 {
 	unsigned int period_us = DEFAULT_PERIOD_MS * 1000;
-	struct clients *clients = NULL;
+	struct igt_drm_clients *clients = NULL;
 	int con_w = -1, con_h = -1;
 	char *output_path = NULL;
 	struct engines *engines;
@@ -2540,24 +2168,24 @@ int main(int argc, char **argv)
 
 	ret = EXIT_SUCCESS;
 
-	clients = init_clients(card.pci_slot_name[0] ?
-			       card.pci_slot_name : IGPU_PCI);
+	clients = igt_drm_clients_init(strdup(card.pci_slot_name[0] ?
+					      card.pci_slot_name : IGPU_PCI));
 	init_engine_classes(engines);
 	if (clients) {
 		clients->num_classes = engines->num_classes;
-		clients->class = engines->class;
+		clients->engine_class = engines->class;
 	}
 
 	pmu_sample(engines);
-	scan_clients(clients);
+	igt_drm_clients_scan(clients, client_match);
 	codename = igt_device_get_pretty_name(&card, false);
 
 	while (!stop_top) {
-		struct clients *disp_clients;
+		struct igt_drm_clients *disp_clients;
 		bool consumed = false;
 		int j, lines = 0;
 		struct winsize ws;
-		struct client *c;
+		struct igt_drm_client *c;
 		double t;
 
 		/* Update terminal size. */
@@ -2576,7 +2204,9 @@ int main(int argc, char **argv)
 		pmu_sample(engines);
 		t = (double)(engines->ts.cur - engines->ts.prev) / 1e9;
 
-		disp_clients = scan_clients(clients);
+		disp_clients =
+			display_clients(igt_drm_clients_scan(clients,
+							     client_match));
 
 		if (stop_top)
 			break;
@@ -2604,9 +2234,9 @@ int main(int argc, char **argv)
 							     con_w, con_h,
 							     &class_w);
 
-				for_each_client(disp_clients, c, j) {
-					assert(c->status != PROBE);
-					if (c->status != ALIVE)
+				igt_for_each_drm_client(disp_clients, c, j) {
+					assert(c->status != IGT_DRM_CLIENT_PROBE);
+					if (c->status != IGT_DRM_CLIENT_ALIVE)
 						break; /* Active clients are first in the array. */
 
 					if (lines >= con_h)
@@ -2630,7 +2260,7 @@ int main(int argc, char **argv)
 			break;
 
 		if (disp_clients != clients)
-			free_clients(disp_clients);
+			free_display_clients(disp_clients);
 
 		if (output_mode == INTERACTIVE)
 			process_stdin(period_us);
diff --git a/tools/meson.build b/tools/meson.build
index 771d0b9e3d5d..e65aceeef9fa 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -91,7 +91,7 @@ install_subdir('registers', install_dir : datadir)
 executable('intel_gpu_top', 'intel_gpu_top.c',
 	   install : true,
 	   install_rpath : bindir_rpathdir,
-	   dependencies : [lib_igt_perf,lib_igt_device_scan,lib_igt_drm_fdinfo,math])
+	   dependencies : [lib_igt_perf,lib_igt_device_scan,lib_igt_drm_clients,lib_igt_drm_fdinfo,math])
 
 executable('amd_hdmi_compliance', 'amd_hdmi_compliance.c',
 	   dependencies : [tool_deps],
-- 
2.32.0

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH i-g-t 05/11] libdrmfdinfo: Allow specifying custom engine map
  2022-02-22 13:55 ` [Intel-gfx] " Tvrtko Ursulin
  (?)
@ 2022-02-22 13:55   ` Tvrtko Ursulin
  -1 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Instead of hard coding the engine names, allow a map of names to indices
to either be passed in or it gets auto-detected (less efficient) while
parsing.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c   |  7 +++---
 lib/igt_drm_clients.h   |  3 ++-
 lib/igt_drm_fdinfo.c    | 47 +++++++++++++++++++++++++++--------------
 lib/igt_drm_fdinfo.h    |  7 ++++--
 tests/i915/drm_fdinfo.c | 16 +++++++++++---
 tools/intel_gpu_top.c   | 13 ++++++++++--
 6 files changed, 66 insertions(+), 27 deletions(-)

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
index 58d82648d821..591602f4c9f5 100644
--- a/lib/igt_drm_clients.c
+++ b/lib/igt_drm_clients.c
@@ -269,7 +269,8 @@ static bool is_drm_fd(int fd_dir, const char *name)
 struct igt_drm_clients *
 igt_drm_clients_scan(struct igt_drm_clients *clients,
 		     bool (*filter_client)(const struct igt_drm_clients *,
-					   const struct drm_client_fdinfo *))
+					   const struct drm_client_fdinfo *),
+		     const char **name_map, unsigned int map_entries)
 {
 	struct dirent *proc_dent;
 	struct igt_drm_client *c;
@@ -343,8 +344,8 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 			memset(&info, 0, sizeof(info));
 
 			if (!__igt_parse_drm_fdinfo(dirfd(fdinfo_dir),
-						    fdinfo_dent->d_name,
-						    &info))
+						    fdinfo_dent->d_name, &info,
+						    name_map, map_entries))
 				continue;
 
 			if (filter_client && !filter_client(clients, &info))
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
index f52080847291..91e9da4c0733 100644
--- a/lib/igt_drm_clients.h
+++ b/lib/igt_drm_clients.h
@@ -80,7 +80,8 @@ void igt_drm_clients_free(struct igt_drm_clients *clients);
 struct igt_drm_clients *
 igt_drm_clients_scan(struct igt_drm_clients *clients,
 		     bool (*filter_client)(const struct igt_drm_clients *,
-					   const struct drm_client_fdinfo *));
+					   const struct drm_client_fdinfo *),
+		     const char **name_map, unsigned int map_entries);
 
 struct igt_drm_client *
 igt_drm_clients_find(struct igt_drm_clients *clients,
diff --git a/lib/igt_drm_fdinfo.c b/lib/igt_drm_fdinfo.c
index 28c1bdbda08e..96a8b768a4b1 100644
--- a/lib/igt_drm_fdinfo.c
+++ b/lib/igt_drm_fdinfo.c
@@ -22,6 +22,7 @@
  *
  */
 
+#include <assert.h>
 #include <ctype.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -52,14 +53,10 @@ static size_t read_fdinfo(char *buf, const size_t sz, int at, const char *name)
 }
 
 static int parse_engine(char *line, struct drm_client_fdinfo *info,
-			size_t prefix_len, uint64_t *val)
+			size_t prefix_len,
+			const char **name_map, unsigned int map_entries,
+			uint64_t *val)
 {
-	static const char *e2class[] = {
-		"render",
-		"copy",
-		"video",
-		"video-enhance",
-	};
 	ssize_t name_len;
 	char *name, *p;
 	int found = -1;
@@ -75,10 +72,26 @@ static int parse_engine(char *line, struct drm_client_fdinfo *info,
 
 	name = line + prefix_len;
 
-	for (i = 0; i < ARRAY_SIZE(e2class); i++) {
-		if (!strncmp(name, e2class[i], name_len)) {
-			found = i;
-			break;
+	if (name_map) {
+		for (i = 0; i < map_entries; i++) {
+			if (!strncmp(name, name_map[i], name_len)) {
+				found = i;
+				break;
+			}
+		}
+	} else {
+		for (i = 0; i < info->num_engines; i++) {
+			if (!strncmp(name, info->names[i], name_len)) {
+				found = i;
+				break;
+			}
+		}
+
+		if (found < 0) {
+			assert((info->num_engines + 1) < ARRAY_SIZE(info->names));
+			assert((strlen(name) + 1) < sizeof(info->names[0]));
+			strncpy(info->names[info->num_engines], name, name_len);
+			found = info->num_engines;
 		}
 	}
 
@@ -109,7 +122,8 @@ static const char *find_kv(const char *buf, const char *key, size_t keylen)
 }
 
 bool
-__igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
+__igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info,
+		       const char **name_map, unsigned int map_entries)
 {
 	char buf[4096], *_buf = buf;
 	char *l, *ctx = NULL;
@@ -139,7 +153,7 @@ __igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
 		} else if (!strncmp(l, "drm-engine-", 11) &&
 			   strncmp(l, "drm-engine-capacity-", 20)) {
 			idx = parse_engine(l, info, strlen("drm-engine-"),
-					   &val);
+					   name_map, map_entries, &val);
 			if (idx >= 0) {
 				if (!info->capacity[idx])
 					info->capacity[idx] = 1;
@@ -149,7 +163,7 @@ __igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
 		} else if (!strncmp(l, "drm-engine-capacity-", 20)) {
 			idx = parse_engine(l, info,
 					   strlen("drm-engine-capacity-"),
-					   &val);
+					   name_map, map_entries, &val);
 			if (idx >= 0)
 				info->capacity[idx] = val;
 		}
@@ -161,7 +175,8 @@ __igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
 	return true;
 }
 
-bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info)
+bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info,
+			  const char **name_map, unsigned int map_entries)
 {
 	char fd[64];
 	int dir, ret;
@@ -175,7 +190,7 @@ bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info)
 	if (dir < 0)
 		return false;
 
-	res = __igt_parse_drm_fdinfo(dir, fd, info);
+	res = __igt_parse_drm_fdinfo(dir, fd, info, name_map, map_entries);
 
 	close(dir);
 
diff --git a/lib/igt_drm_fdinfo.h b/lib/igt_drm_fdinfo.h
index c527bab9a204..bea4a6304734 100644
--- a/lib/igt_drm_fdinfo.h
+++ b/lib/igt_drm_fdinfo.h
@@ -37,12 +37,15 @@ struct drm_client_fdinfo {
 
 	unsigned int num_engines;
 	unsigned int capacity[16];
+	char names[16][256];
 	uint64_t busy[16];
 };
 
-bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info);
+bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info,
+			  const char **name_map, unsigned int map_entries);
 
 bool __igt_parse_drm_fdinfo(int dir, const char *fd,
-			    struct drm_client_fdinfo *info);
+			    struct drm_client_fdinfo *info,
+			    const char **name_map, unsigned int map_entries);
 
 #endif /* IGT_DRM_FDINFO_H */
diff --git a/tests/i915/drm_fdinfo.c b/tests/i915/drm_fdinfo.c
index e3b1ebb0f454..d1053a53ae2c 100644
--- a/tests/i915/drm_fdinfo.c
+++ b/tests/i915/drm_fdinfo.c
@@ -34,6 +34,13 @@ IGT_TEST_DESCRIPTION("Test the i915 drm fdinfo data");
 const double tolerance = 0.05f;
 const unsigned long batch_duration_ns = 500e6;
 
+static const char *engine_map[] = {
+	"render",
+	"copy",
+	"video",
+	"video-enhance",
+};
+
 #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), \
@@ -50,7 +57,8 @@ static void basics(int i915, unsigned int num_classes)
 	struct drm_client_fdinfo info = { };
 	bool ret;
 
-	ret = igt_parse_drm_fdinfo(i915, &info);
+	ret = igt_parse_drm_fdinfo(i915, &info, engine_map,
+				   ARRAY_SIZE(engine_map));
 	igt_assert(ret);
 
 	igt_assert(!strcmp(info.driver, "i915"));
@@ -181,7 +189,8 @@ static uint64_t read_busy(int i915, unsigned int class)
 {
 	struct drm_client_fdinfo info = { };
 
-	igt_assert(igt_parse_drm_fdinfo(i915, &info));
+	igt_assert(igt_parse_drm_fdinfo(i915, &info, engine_map,
+					ARRAY_SIZE(engine_map)));
 
 	return info.busy[class];
 }
@@ -267,7 +276,8 @@ static void read_busy_all(int i915, uint64_t *val)
 {
 	struct drm_client_fdinfo info = { };
 
-	igt_assert(igt_parse_drm_fdinfo(i915, &info));
+	igt_assert(igt_parse_drm_fdinfo(i915, &info, engine_map,
+					ARRAY_SIZE(engine_map)));
 
 	memcpy(val, info.busy, sizeof(info.busy));
 }
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index c4f990b2a7d0..99e8e1d8ffd4 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -2030,6 +2030,12 @@ int main(int argc, char **argv)
 {
 	unsigned int period_us = DEFAULT_PERIOD_MS * 1000;
 	struct igt_drm_clients *clients = NULL;
+	static const char *engine_map[] = {
+		"render",
+		"copy",
+		"video",
+		"video-enhance",
+	};
 	int con_w = -1, con_h = -1;
 	char *output_path = NULL;
 	struct engines *engines;
@@ -2177,7 +2183,8 @@ int main(int argc, char **argv)
 	}
 
 	pmu_sample(engines);
-	igt_drm_clients_scan(clients, client_match);
+	igt_drm_clients_scan(clients, client_match, engine_map,
+			     ARRAY_SIZE(engine_map));
 	codename = igt_device_get_pretty_name(&card, false);
 
 	while (!stop_top) {
@@ -2206,7 +2213,9 @@ int main(int argc, char **argv)
 
 		disp_clients =
 			display_clients(igt_drm_clients_scan(clients,
-							     client_match));
+							     client_match,
+							     engine_map,
+							     ARRAY_SIZE(engine_map)));
 
 		if (stop_top)
 			break;
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [Intel-gfx] [PATCH i-g-t 05/11] libdrmfdinfo: Allow specifying custom engine map
@ 2022-02-22 13:55   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Instead of hard coding the engine names, allow a map of names to indices
to either be passed in or it gets auto-detected (less efficient) while
parsing.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c   |  7 +++---
 lib/igt_drm_clients.h   |  3 ++-
 lib/igt_drm_fdinfo.c    | 47 +++++++++++++++++++++++++++--------------
 lib/igt_drm_fdinfo.h    |  7 ++++--
 tests/i915/drm_fdinfo.c | 16 +++++++++++---
 tools/intel_gpu_top.c   | 13 ++++++++++--
 6 files changed, 66 insertions(+), 27 deletions(-)

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
index 58d82648d821..591602f4c9f5 100644
--- a/lib/igt_drm_clients.c
+++ b/lib/igt_drm_clients.c
@@ -269,7 +269,8 @@ static bool is_drm_fd(int fd_dir, const char *name)
 struct igt_drm_clients *
 igt_drm_clients_scan(struct igt_drm_clients *clients,
 		     bool (*filter_client)(const struct igt_drm_clients *,
-					   const struct drm_client_fdinfo *))
+					   const struct drm_client_fdinfo *),
+		     const char **name_map, unsigned int map_entries)
 {
 	struct dirent *proc_dent;
 	struct igt_drm_client *c;
@@ -343,8 +344,8 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 			memset(&info, 0, sizeof(info));
 
 			if (!__igt_parse_drm_fdinfo(dirfd(fdinfo_dir),
-						    fdinfo_dent->d_name,
-						    &info))
+						    fdinfo_dent->d_name, &info,
+						    name_map, map_entries))
 				continue;
 
 			if (filter_client && !filter_client(clients, &info))
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
index f52080847291..91e9da4c0733 100644
--- a/lib/igt_drm_clients.h
+++ b/lib/igt_drm_clients.h
@@ -80,7 +80,8 @@ void igt_drm_clients_free(struct igt_drm_clients *clients);
 struct igt_drm_clients *
 igt_drm_clients_scan(struct igt_drm_clients *clients,
 		     bool (*filter_client)(const struct igt_drm_clients *,
-					   const struct drm_client_fdinfo *));
+					   const struct drm_client_fdinfo *),
+		     const char **name_map, unsigned int map_entries);
 
 struct igt_drm_client *
 igt_drm_clients_find(struct igt_drm_clients *clients,
diff --git a/lib/igt_drm_fdinfo.c b/lib/igt_drm_fdinfo.c
index 28c1bdbda08e..96a8b768a4b1 100644
--- a/lib/igt_drm_fdinfo.c
+++ b/lib/igt_drm_fdinfo.c
@@ -22,6 +22,7 @@
  *
  */
 
+#include <assert.h>
 #include <ctype.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -52,14 +53,10 @@ static size_t read_fdinfo(char *buf, const size_t sz, int at, const char *name)
 }
 
 static int parse_engine(char *line, struct drm_client_fdinfo *info,
-			size_t prefix_len, uint64_t *val)
+			size_t prefix_len,
+			const char **name_map, unsigned int map_entries,
+			uint64_t *val)
 {
-	static const char *e2class[] = {
-		"render",
-		"copy",
-		"video",
-		"video-enhance",
-	};
 	ssize_t name_len;
 	char *name, *p;
 	int found = -1;
@@ -75,10 +72,26 @@ static int parse_engine(char *line, struct drm_client_fdinfo *info,
 
 	name = line + prefix_len;
 
-	for (i = 0; i < ARRAY_SIZE(e2class); i++) {
-		if (!strncmp(name, e2class[i], name_len)) {
-			found = i;
-			break;
+	if (name_map) {
+		for (i = 0; i < map_entries; i++) {
+			if (!strncmp(name, name_map[i], name_len)) {
+				found = i;
+				break;
+			}
+		}
+	} else {
+		for (i = 0; i < info->num_engines; i++) {
+			if (!strncmp(name, info->names[i], name_len)) {
+				found = i;
+				break;
+			}
+		}
+
+		if (found < 0) {
+			assert((info->num_engines + 1) < ARRAY_SIZE(info->names));
+			assert((strlen(name) + 1) < sizeof(info->names[0]));
+			strncpy(info->names[info->num_engines], name, name_len);
+			found = info->num_engines;
 		}
 	}
 
@@ -109,7 +122,8 @@ static const char *find_kv(const char *buf, const char *key, size_t keylen)
 }
 
 bool
-__igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
+__igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info,
+		       const char **name_map, unsigned int map_entries)
 {
 	char buf[4096], *_buf = buf;
 	char *l, *ctx = NULL;
@@ -139,7 +153,7 @@ __igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
 		} else if (!strncmp(l, "drm-engine-", 11) &&
 			   strncmp(l, "drm-engine-capacity-", 20)) {
 			idx = parse_engine(l, info, strlen("drm-engine-"),
-					   &val);
+					   name_map, map_entries, &val);
 			if (idx >= 0) {
 				if (!info->capacity[idx])
 					info->capacity[idx] = 1;
@@ -149,7 +163,7 @@ __igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
 		} else if (!strncmp(l, "drm-engine-capacity-", 20)) {
 			idx = parse_engine(l, info,
 					   strlen("drm-engine-capacity-"),
-					   &val);
+					   name_map, map_entries, &val);
 			if (idx >= 0)
 				info->capacity[idx] = val;
 		}
@@ -161,7 +175,8 @@ __igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
 	return true;
 }
 
-bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info)
+bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info,
+			  const char **name_map, unsigned int map_entries)
 {
 	char fd[64];
 	int dir, ret;
@@ -175,7 +190,7 @@ bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info)
 	if (dir < 0)
 		return false;
 
-	res = __igt_parse_drm_fdinfo(dir, fd, info);
+	res = __igt_parse_drm_fdinfo(dir, fd, info, name_map, map_entries);
 
 	close(dir);
 
diff --git a/lib/igt_drm_fdinfo.h b/lib/igt_drm_fdinfo.h
index c527bab9a204..bea4a6304734 100644
--- a/lib/igt_drm_fdinfo.h
+++ b/lib/igt_drm_fdinfo.h
@@ -37,12 +37,15 @@ struct drm_client_fdinfo {
 
 	unsigned int num_engines;
 	unsigned int capacity[16];
+	char names[16][256];
 	uint64_t busy[16];
 };
 
-bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info);
+bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info,
+			  const char **name_map, unsigned int map_entries);
 
 bool __igt_parse_drm_fdinfo(int dir, const char *fd,
-			    struct drm_client_fdinfo *info);
+			    struct drm_client_fdinfo *info,
+			    const char **name_map, unsigned int map_entries);
 
 #endif /* IGT_DRM_FDINFO_H */
diff --git a/tests/i915/drm_fdinfo.c b/tests/i915/drm_fdinfo.c
index e3b1ebb0f454..d1053a53ae2c 100644
--- a/tests/i915/drm_fdinfo.c
+++ b/tests/i915/drm_fdinfo.c
@@ -34,6 +34,13 @@ IGT_TEST_DESCRIPTION("Test the i915 drm fdinfo data");
 const double tolerance = 0.05f;
 const unsigned long batch_duration_ns = 500e6;
 
+static const char *engine_map[] = {
+	"render",
+	"copy",
+	"video",
+	"video-enhance",
+};
+
 #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), \
@@ -50,7 +57,8 @@ static void basics(int i915, unsigned int num_classes)
 	struct drm_client_fdinfo info = { };
 	bool ret;
 
-	ret = igt_parse_drm_fdinfo(i915, &info);
+	ret = igt_parse_drm_fdinfo(i915, &info, engine_map,
+				   ARRAY_SIZE(engine_map));
 	igt_assert(ret);
 
 	igt_assert(!strcmp(info.driver, "i915"));
@@ -181,7 +189,8 @@ static uint64_t read_busy(int i915, unsigned int class)
 {
 	struct drm_client_fdinfo info = { };
 
-	igt_assert(igt_parse_drm_fdinfo(i915, &info));
+	igt_assert(igt_parse_drm_fdinfo(i915, &info, engine_map,
+					ARRAY_SIZE(engine_map)));
 
 	return info.busy[class];
 }
@@ -267,7 +276,8 @@ static void read_busy_all(int i915, uint64_t *val)
 {
 	struct drm_client_fdinfo info = { };
 
-	igt_assert(igt_parse_drm_fdinfo(i915, &info));
+	igt_assert(igt_parse_drm_fdinfo(i915, &info, engine_map,
+					ARRAY_SIZE(engine_map)));
 
 	memcpy(val, info.busy, sizeof(info.busy));
 }
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index c4f990b2a7d0..99e8e1d8ffd4 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -2030,6 +2030,12 @@ int main(int argc, char **argv)
 {
 	unsigned int period_us = DEFAULT_PERIOD_MS * 1000;
 	struct igt_drm_clients *clients = NULL;
+	static const char *engine_map[] = {
+		"render",
+		"copy",
+		"video",
+		"video-enhance",
+	};
 	int con_w = -1, con_h = -1;
 	char *output_path = NULL;
 	struct engines *engines;
@@ -2177,7 +2183,8 @@ int main(int argc, char **argv)
 	}
 
 	pmu_sample(engines);
-	igt_drm_clients_scan(clients, client_match);
+	igt_drm_clients_scan(clients, client_match, engine_map,
+			     ARRAY_SIZE(engine_map));
 	codename = igt_device_get_pretty_name(&card, false);
 
 	while (!stop_top) {
@@ -2206,7 +2213,9 @@ int main(int argc, char **argv)
 
 		disp_clients =
 			display_clients(igt_drm_clients_scan(clients,
-							     client_match));
+							     client_match,
+							     engine_map,
+							     ARRAY_SIZE(engine_map)));
 
 		if (stop_top)
 			break;
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [igt-dev] [PATCH i-g-t 05/11] libdrmfdinfo: Allow specifying custom engine map
@ 2022-02-22 13:55   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:55 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Instead of hard coding the engine names, allow a map of names to indices
to either be passed in or it gets auto-detected (less efficient) while
parsing.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c   |  7 +++---
 lib/igt_drm_clients.h   |  3 ++-
 lib/igt_drm_fdinfo.c    | 47 +++++++++++++++++++++++++++--------------
 lib/igt_drm_fdinfo.h    |  7 ++++--
 tests/i915/drm_fdinfo.c | 16 +++++++++++---
 tools/intel_gpu_top.c   | 13 ++++++++++--
 6 files changed, 66 insertions(+), 27 deletions(-)

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
index 58d82648d821..591602f4c9f5 100644
--- a/lib/igt_drm_clients.c
+++ b/lib/igt_drm_clients.c
@@ -269,7 +269,8 @@ static bool is_drm_fd(int fd_dir, const char *name)
 struct igt_drm_clients *
 igt_drm_clients_scan(struct igt_drm_clients *clients,
 		     bool (*filter_client)(const struct igt_drm_clients *,
-					   const struct drm_client_fdinfo *))
+					   const struct drm_client_fdinfo *),
+		     const char **name_map, unsigned int map_entries)
 {
 	struct dirent *proc_dent;
 	struct igt_drm_client *c;
@@ -343,8 +344,8 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 			memset(&info, 0, sizeof(info));
 
 			if (!__igt_parse_drm_fdinfo(dirfd(fdinfo_dir),
-						    fdinfo_dent->d_name,
-						    &info))
+						    fdinfo_dent->d_name, &info,
+						    name_map, map_entries))
 				continue;
 
 			if (filter_client && !filter_client(clients, &info))
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
index f52080847291..91e9da4c0733 100644
--- a/lib/igt_drm_clients.h
+++ b/lib/igt_drm_clients.h
@@ -80,7 +80,8 @@ void igt_drm_clients_free(struct igt_drm_clients *clients);
 struct igt_drm_clients *
 igt_drm_clients_scan(struct igt_drm_clients *clients,
 		     bool (*filter_client)(const struct igt_drm_clients *,
-					   const struct drm_client_fdinfo *));
+					   const struct drm_client_fdinfo *),
+		     const char **name_map, unsigned int map_entries);
 
 struct igt_drm_client *
 igt_drm_clients_find(struct igt_drm_clients *clients,
diff --git a/lib/igt_drm_fdinfo.c b/lib/igt_drm_fdinfo.c
index 28c1bdbda08e..96a8b768a4b1 100644
--- a/lib/igt_drm_fdinfo.c
+++ b/lib/igt_drm_fdinfo.c
@@ -22,6 +22,7 @@
  *
  */
 
+#include <assert.h>
 #include <ctype.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -52,14 +53,10 @@ static size_t read_fdinfo(char *buf, const size_t sz, int at, const char *name)
 }
 
 static int parse_engine(char *line, struct drm_client_fdinfo *info,
-			size_t prefix_len, uint64_t *val)
+			size_t prefix_len,
+			const char **name_map, unsigned int map_entries,
+			uint64_t *val)
 {
-	static const char *e2class[] = {
-		"render",
-		"copy",
-		"video",
-		"video-enhance",
-	};
 	ssize_t name_len;
 	char *name, *p;
 	int found = -1;
@@ -75,10 +72,26 @@ static int parse_engine(char *line, struct drm_client_fdinfo *info,
 
 	name = line + prefix_len;
 
-	for (i = 0; i < ARRAY_SIZE(e2class); i++) {
-		if (!strncmp(name, e2class[i], name_len)) {
-			found = i;
-			break;
+	if (name_map) {
+		for (i = 0; i < map_entries; i++) {
+			if (!strncmp(name, name_map[i], name_len)) {
+				found = i;
+				break;
+			}
+		}
+	} else {
+		for (i = 0; i < info->num_engines; i++) {
+			if (!strncmp(name, info->names[i], name_len)) {
+				found = i;
+				break;
+			}
+		}
+
+		if (found < 0) {
+			assert((info->num_engines + 1) < ARRAY_SIZE(info->names));
+			assert((strlen(name) + 1) < sizeof(info->names[0]));
+			strncpy(info->names[info->num_engines], name, name_len);
+			found = info->num_engines;
 		}
 	}
 
@@ -109,7 +122,8 @@ static const char *find_kv(const char *buf, const char *key, size_t keylen)
 }
 
 bool
-__igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
+__igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info,
+		       const char **name_map, unsigned int map_entries)
 {
 	char buf[4096], *_buf = buf;
 	char *l, *ctx = NULL;
@@ -139,7 +153,7 @@ __igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
 		} else if (!strncmp(l, "drm-engine-", 11) &&
 			   strncmp(l, "drm-engine-capacity-", 20)) {
 			idx = parse_engine(l, info, strlen("drm-engine-"),
-					   &val);
+					   name_map, map_entries, &val);
 			if (idx >= 0) {
 				if (!info->capacity[idx])
 					info->capacity[idx] = 1;
@@ -149,7 +163,7 @@ __igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
 		} else if (!strncmp(l, "drm-engine-capacity-", 20)) {
 			idx = parse_engine(l, info,
 					   strlen("drm-engine-capacity-"),
-					   &val);
+					   name_map, map_entries, &val);
 			if (idx >= 0)
 				info->capacity[idx] = val;
 		}
@@ -161,7 +175,8 @@ __igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
 	return true;
 }
 
-bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info)
+bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info,
+			  const char **name_map, unsigned int map_entries)
 {
 	char fd[64];
 	int dir, ret;
@@ -175,7 +190,7 @@ bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info)
 	if (dir < 0)
 		return false;
 
-	res = __igt_parse_drm_fdinfo(dir, fd, info);
+	res = __igt_parse_drm_fdinfo(dir, fd, info, name_map, map_entries);
 
 	close(dir);
 
diff --git a/lib/igt_drm_fdinfo.h b/lib/igt_drm_fdinfo.h
index c527bab9a204..bea4a6304734 100644
--- a/lib/igt_drm_fdinfo.h
+++ b/lib/igt_drm_fdinfo.h
@@ -37,12 +37,15 @@ struct drm_client_fdinfo {
 
 	unsigned int num_engines;
 	unsigned int capacity[16];
+	char names[16][256];
 	uint64_t busy[16];
 };
 
-bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info);
+bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info,
+			  const char **name_map, unsigned int map_entries);
 
 bool __igt_parse_drm_fdinfo(int dir, const char *fd,
-			    struct drm_client_fdinfo *info);
+			    struct drm_client_fdinfo *info,
+			    const char **name_map, unsigned int map_entries);
 
 #endif /* IGT_DRM_FDINFO_H */
diff --git a/tests/i915/drm_fdinfo.c b/tests/i915/drm_fdinfo.c
index e3b1ebb0f454..d1053a53ae2c 100644
--- a/tests/i915/drm_fdinfo.c
+++ b/tests/i915/drm_fdinfo.c
@@ -34,6 +34,13 @@ IGT_TEST_DESCRIPTION("Test the i915 drm fdinfo data");
 const double tolerance = 0.05f;
 const unsigned long batch_duration_ns = 500e6;
 
+static const char *engine_map[] = {
+	"render",
+	"copy",
+	"video",
+	"video-enhance",
+};
+
 #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), \
@@ -50,7 +57,8 @@ static void basics(int i915, unsigned int num_classes)
 	struct drm_client_fdinfo info = { };
 	bool ret;
 
-	ret = igt_parse_drm_fdinfo(i915, &info);
+	ret = igt_parse_drm_fdinfo(i915, &info, engine_map,
+				   ARRAY_SIZE(engine_map));
 	igt_assert(ret);
 
 	igt_assert(!strcmp(info.driver, "i915"));
@@ -181,7 +189,8 @@ static uint64_t read_busy(int i915, unsigned int class)
 {
 	struct drm_client_fdinfo info = { };
 
-	igt_assert(igt_parse_drm_fdinfo(i915, &info));
+	igt_assert(igt_parse_drm_fdinfo(i915, &info, engine_map,
+					ARRAY_SIZE(engine_map)));
 
 	return info.busy[class];
 }
@@ -267,7 +276,8 @@ static void read_busy_all(int i915, uint64_t *val)
 {
 	struct drm_client_fdinfo info = { };
 
-	igt_assert(igt_parse_drm_fdinfo(i915, &info));
+	igt_assert(igt_parse_drm_fdinfo(i915, &info, engine_map,
+					ARRAY_SIZE(engine_map)));
 
 	memcpy(val, info.busy, sizeof(info.busy));
 }
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index c4f990b2a7d0..99e8e1d8ffd4 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -2030,6 +2030,12 @@ int main(int argc, char **argv)
 {
 	unsigned int period_us = DEFAULT_PERIOD_MS * 1000;
 	struct igt_drm_clients *clients = NULL;
+	static const char *engine_map[] = {
+		"render",
+		"copy",
+		"video",
+		"video-enhance",
+	};
 	int con_w = -1, con_h = -1;
 	char *output_path = NULL;
 	struct engines *engines;
@@ -2177,7 +2183,8 @@ int main(int argc, char **argv)
 	}
 
 	pmu_sample(engines);
-	igt_drm_clients_scan(clients, client_match);
+	igt_drm_clients_scan(clients, client_match, engine_map,
+			     ARRAY_SIZE(engine_map));
 	codename = igt_device_get_pretty_name(&card, false);
 
 	while (!stop_top) {
@@ -2206,7 +2213,9 @@ int main(int argc, char **argv)
 
 		disp_clients =
 			display_clients(igt_drm_clients_scan(clients,
-							     client_match));
+							     client_match,
+							     engine_map,
+							     ARRAY_SIZE(engine_map)));
 
 		if (stop_top)
 			break;
-- 
2.32.0

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH i-g-t 06/11] libdrmclients: Record client drm minor
  2022-02-22 13:55 ` [Intel-gfx] " Tvrtko Ursulin
  (?)
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  -1 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Prepare for supporting clients belonging to multiple DRM cards by storing
the DRM minor in the client record.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c | 33 ++++++++++++++++++++-------------
 lib/igt_drm_clients.h |  6 ++++--
 2 files changed, 24 insertions(+), 15 deletions(-)

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
index 591602f4c9f5..116479a9f363 100644
--- a/lib/igt_drm_clients.c
+++ b/lib/igt_drm_clients.c
@@ -117,12 +117,13 @@ igt_drm_client_update(struct igt_drm_client *c, unsigned int pid, char *name,
 }
 
 void
-igt_drm_client_add(struct igt_drm_clients *clients, unsigned int id,
-		   unsigned int pid, char *name, uint64_t busy[16])
+igt_drm_client_add(struct igt_drm_clients *clients,
+		   struct drm_client_fdinfo *info,
+		   unsigned int pid, char *name, unsigned int drm_minor)
 {
 	struct igt_drm_client *c;
 
-	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE, id));
+	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE, info->id));
 
 	c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_FREE, 0);
 	if (!c) {
@@ -137,13 +138,14 @@ igt_drm_client_add(struct igt_drm_clients *clients, unsigned int id,
 		memset(c, 0, (clients->num_clients - idx) * sizeof(*c));
 	}
 
-	c->id = id;
+	c->id = info->id;
+	c->drm_minor = drm_minor;
 	c->clients = clients;
 	c->val = calloc(clients->num_classes, sizeof(c->val));
 	c->last = calloc(clients->num_classes, sizeof(c->last));
 	assert(c->val && c->last);
 
-	igt_drm_client_update(c, pid, name, busy);
+	igt_drm_client_update(c, pid, name, info->busy);
 }
 
 void igt_drm_client_free(struct igt_drm_client *c)
@@ -254,16 +256,21 @@ static bool get_task_name(const char *buffer, char *out, unsigned long sz)
 	return true;
 }
 
-static bool is_drm_fd(int fd_dir, const char *name)
+static bool is_drm_fd(int fd_dir, const char *name, unsigned int *minor)
 {
 	struct stat stat;
 	int ret;
 
 	ret = fstatat(fd_dir, name, &stat, 0);
 
-	return ret == 0 &&
-	       (stat.st_mode & S_IFMT) == S_IFCHR &&
-	       major(stat.st_rdev) == 226;
+	if (ret == 0 &&
+	    (stat.st_mode & S_IFMT) == S_IFCHR &&
+	    major(stat.st_rdev) == 226) {
+		*minor = minor(stat.st_rdev);
+		return true;
+	}
+
+	return false;
 }
 
 struct igt_drm_clients *
@@ -293,10 +300,10 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 		return clients;
 
 	while ((proc_dent = readdir(proc_dir)) != NULL) {
+		unsigned int client_pid, minor = 0;
 		int pid_dir = -1, fd_dir = -1;
 		struct dirent *fdinfo_dent;
 		char client_name[64] = { };
-		unsigned int client_pid;
 		DIR *fdinfo_dir = NULL;
 		char buf[4096];
 		size_t count;
@@ -338,7 +345,7 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 			if (!isdigit(fdinfo_dent->d_name[0]))
 				continue;
 
-			if (!is_drm_fd(fd_dir, fdinfo_dent->d_name))
+			if (!is_drm_fd(fd_dir, fdinfo_dent->d_name, &minor))
 				continue;
 
 			memset(&info, 0, sizeof(info));
@@ -358,8 +365,8 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 			c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_PROBE,
 						info.id);
 			if (!c)
-				igt_drm_client_add(clients, info.id, client_pid,
-						   client_name, info.busy);
+				igt_drm_client_add(clients, &info, client_pid,
+						   client_name, minor);
 			else
 				igt_drm_client_update(c, client_pid,
 						      client_name, info.busy);
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
index 91e9da4c0733..7a6318c6af5f 100644
--- a/lib/igt_drm_clients.h
+++ b/lib/igt_drm_clients.h
@@ -48,6 +48,7 @@ struct igt_drm_client {
 
 	enum igt_drm_client_status status;
 	unsigned int id;
+	unsigned int drm_minor;
 	unsigned int pid;
 	char name[24];
 	char print_name[24];
@@ -95,8 +96,9 @@ igt_drm_clients_sort(struct igt_drm_clients *clients,
 void igt_drm_client_update(struct igt_drm_client *c,
 			   unsigned int pid, char *name, uint64_t val[16]);
 
-void igt_drm_client_add(struct igt_drm_clients *clients, unsigned int id,
-			unsigned int pid, char *name, uint64_t busy[16]);
+void igt_drm_client_add(struct igt_drm_clients *clients,
+			struct drm_client_fdinfo *,
+			unsigned int pid, char *name, unsigned int drm_minor);
 
 void igt_drm_client_free(struct igt_drm_client *c);
 
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [Intel-gfx] [PATCH i-g-t 06/11] libdrmclients: Record client drm minor
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Prepare for supporting clients belonging to multiple DRM cards by storing
the DRM minor in the client record.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c | 33 ++++++++++++++++++++-------------
 lib/igt_drm_clients.h |  6 ++++--
 2 files changed, 24 insertions(+), 15 deletions(-)

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
index 591602f4c9f5..116479a9f363 100644
--- a/lib/igt_drm_clients.c
+++ b/lib/igt_drm_clients.c
@@ -117,12 +117,13 @@ igt_drm_client_update(struct igt_drm_client *c, unsigned int pid, char *name,
 }
 
 void
-igt_drm_client_add(struct igt_drm_clients *clients, unsigned int id,
-		   unsigned int pid, char *name, uint64_t busy[16])
+igt_drm_client_add(struct igt_drm_clients *clients,
+		   struct drm_client_fdinfo *info,
+		   unsigned int pid, char *name, unsigned int drm_minor)
 {
 	struct igt_drm_client *c;
 
-	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE, id));
+	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE, info->id));
 
 	c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_FREE, 0);
 	if (!c) {
@@ -137,13 +138,14 @@ igt_drm_client_add(struct igt_drm_clients *clients, unsigned int id,
 		memset(c, 0, (clients->num_clients - idx) * sizeof(*c));
 	}
 
-	c->id = id;
+	c->id = info->id;
+	c->drm_minor = drm_minor;
 	c->clients = clients;
 	c->val = calloc(clients->num_classes, sizeof(c->val));
 	c->last = calloc(clients->num_classes, sizeof(c->last));
 	assert(c->val && c->last);
 
-	igt_drm_client_update(c, pid, name, busy);
+	igt_drm_client_update(c, pid, name, info->busy);
 }
 
 void igt_drm_client_free(struct igt_drm_client *c)
@@ -254,16 +256,21 @@ static bool get_task_name(const char *buffer, char *out, unsigned long sz)
 	return true;
 }
 
-static bool is_drm_fd(int fd_dir, const char *name)
+static bool is_drm_fd(int fd_dir, const char *name, unsigned int *minor)
 {
 	struct stat stat;
 	int ret;
 
 	ret = fstatat(fd_dir, name, &stat, 0);
 
-	return ret == 0 &&
-	       (stat.st_mode & S_IFMT) == S_IFCHR &&
-	       major(stat.st_rdev) == 226;
+	if (ret == 0 &&
+	    (stat.st_mode & S_IFMT) == S_IFCHR &&
+	    major(stat.st_rdev) == 226) {
+		*minor = minor(stat.st_rdev);
+		return true;
+	}
+
+	return false;
 }
 
 struct igt_drm_clients *
@@ -293,10 +300,10 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 		return clients;
 
 	while ((proc_dent = readdir(proc_dir)) != NULL) {
+		unsigned int client_pid, minor = 0;
 		int pid_dir = -1, fd_dir = -1;
 		struct dirent *fdinfo_dent;
 		char client_name[64] = { };
-		unsigned int client_pid;
 		DIR *fdinfo_dir = NULL;
 		char buf[4096];
 		size_t count;
@@ -338,7 +345,7 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 			if (!isdigit(fdinfo_dent->d_name[0]))
 				continue;
 
-			if (!is_drm_fd(fd_dir, fdinfo_dent->d_name))
+			if (!is_drm_fd(fd_dir, fdinfo_dent->d_name, &minor))
 				continue;
 
 			memset(&info, 0, sizeof(info));
@@ -358,8 +365,8 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 			c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_PROBE,
 						info.id);
 			if (!c)
-				igt_drm_client_add(clients, info.id, client_pid,
-						   client_name, info.busy);
+				igt_drm_client_add(clients, &info, client_pid,
+						   client_name, minor);
 			else
 				igt_drm_client_update(c, client_pid,
 						      client_name, info.busy);
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
index 91e9da4c0733..7a6318c6af5f 100644
--- a/lib/igt_drm_clients.h
+++ b/lib/igt_drm_clients.h
@@ -48,6 +48,7 @@ struct igt_drm_client {
 
 	enum igt_drm_client_status status;
 	unsigned int id;
+	unsigned int drm_minor;
 	unsigned int pid;
 	char name[24];
 	char print_name[24];
@@ -95,8 +96,9 @@ igt_drm_clients_sort(struct igt_drm_clients *clients,
 void igt_drm_client_update(struct igt_drm_client *c,
 			   unsigned int pid, char *name, uint64_t val[16]);
 
-void igt_drm_client_add(struct igt_drm_clients *clients, unsigned int id,
-			unsigned int pid, char *name, uint64_t busy[16]);
+void igt_drm_client_add(struct igt_drm_clients *clients,
+			struct drm_client_fdinfo *,
+			unsigned int pid, char *name, unsigned int drm_minor);
 
 void igt_drm_client_free(struct igt_drm_client *c);
 
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [igt-dev] [PATCH i-g-t 06/11] libdrmclients: Record client drm minor
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Prepare for supporting clients belonging to multiple DRM cards by storing
the DRM minor in the client record.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c | 33 ++++++++++++++++++++-------------
 lib/igt_drm_clients.h |  6 ++++--
 2 files changed, 24 insertions(+), 15 deletions(-)

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
index 591602f4c9f5..116479a9f363 100644
--- a/lib/igt_drm_clients.c
+++ b/lib/igt_drm_clients.c
@@ -117,12 +117,13 @@ igt_drm_client_update(struct igt_drm_client *c, unsigned int pid, char *name,
 }
 
 void
-igt_drm_client_add(struct igt_drm_clients *clients, unsigned int id,
-		   unsigned int pid, char *name, uint64_t busy[16])
+igt_drm_client_add(struct igt_drm_clients *clients,
+		   struct drm_client_fdinfo *info,
+		   unsigned int pid, char *name, unsigned int drm_minor)
 {
 	struct igt_drm_client *c;
 
-	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE, id));
+	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE, info->id));
 
 	c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_FREE, 0);
 	if (!c) {
@@ -137,13 +138,14 @@ igt_drm_client_add(struct igt_drm_clients *clients, unsigned int id,
 		memset(c, 0, (clients->num_clients - idx) * sizeof(*c));
 	}
 
-	c->id = id;
+	c->id = info->id;
+	c->drm_minor = drm_minor;
 	c->clients = clients;
 	c->val = calloc(clients->num_classes, sizeof(c->val));
 	c->last = calloc(clients->num_classes, sizeof(c->last));
 	assert(c->val && c->last);
 
-	igt_drm_client_update(c, pid, name, busy);
+	igt_drm_client_update(c, pid, name, info->busy);
 }
 
 void igt_drm_client_free(struct igt_drm_client *c)
@@ -254,16 +256,21 @@ static bool get_task_name(const char *buffer, char *out, unsigned long sz)
 	return true;
 }
 
-static bool is_drm_fd(int fd_dir, const char *name)
+static bool is_drm_fd(int fd_dir, const char *name, unsigned int *minor)
 {
 	struct stat stat;
 	int ret;
 
 	ret = fstatat(fd_dir, name, &stat, 0);
 
-	return ret == 0 &&
-	       (stat.st_mode & S_IFMT) == S_IFCHR &&
-	       major(stat.st_rdev) == 226;
+	if (ret == 0 &&
+	    (stat.st_mode & S_IFMT) == S_IFCHR &&
+	    major(stat.st_rdev) == 226) {
+		*minor = minor(stat.st_rdev);
+		return true;
+	}
+
+	return false;
 }
 
 struct igt_drm_clients *
@@ -293,10 +300,10 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 		return clients;
 
 	while ((proc_dent = readdir(proc_dir)) != NULL) {
+		unsigned int client_pid, minor = 0;
 		int pid_dir = -1, fd_dir = -1;
 		struct dirent *fdinfo_dent;
 		char client_name[64] = { };
-		unsigned int client_pid;
 		DIR *fdinfo_dir = NULL;
 		char buf[4096];
 		size_t count;
@@ -338,7 +345,7 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 			if (!isdigit(fdinfo_dent->d_name[0]))
 				continue;
 
-			if (!is_drm_fd(fd_dir, fdinfo_dent->d_name))
+			if (!is_drm_fd(fd_dir, fdinfo_dent->d_name, &minor))
 				continue;
 
 			memset(&info, 0, sizeof(info));
@@ -358,8 +365,8 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 			c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_PROBE,
 						info.id);
 			if (!c)
-				igt_drm_client_add(clients, info.id, client_pid,
-						   client_name, info.busy);
+				igt_drm_client_add(clients, &info, client_pid,
+						   client_name, minor);
 			else
 				igt_drm_client_update(c, client_pid,
 						      client_name, info.busy);
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
index 91e9da4c0733..7a6318c6af5f 100644
--- a/lib/igt_drm_clients.h
+++ b/lib/igt_drm_clients.h
@@ -48,6 +48,7 @@ struct igt_drm_client {
 
 	enum igt_drm_client_status status;
 	unsigned int id;
+	unsigned int drm_minor;
 	unsigned int pid;
 	char name[24];
 	char print_name[24];
@@ -95,8 +96,9 @@ igt_drm_clients_sort(struct igt_drm_clients *clients,
 void igt_drm_client_update(struct igt_drm_client *c,
 			   unsigned int pid, char *name, uint64_t val[16]);
 
-void igt_drm_client_add(struct igt_drm_clients *clients, unsigned int id,
-			unsigned int pid, char *name, uint64_t busy[16]);
+void igt_drm_client_add(struct igt_drm_clients *clients,
+			struct drm_client_fdinfo *,
+			unsigned int pid, char *name, unsigned int drm_minor);
 
 void igt_drm_client_free(struct igt_drm_client *c);
 
-- 
2.32.0

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH i-g-t 07/11] libdrmclient: Support multiple DRM cards
  2022-02-22 13:55 ` [Intel-gfx] " Tvrtko Ursulin
  (?)
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  -1 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Require DRM minor match during client lookup.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c | 14 ++++++++------
 lib/igt_drm_clients.h |  2 +-
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
index 116479a9f363..cf044597d537 100644
--- a/lib/igt_drm_clients.c
+++ b/lib/igt_drm_clients.c
@@ -57,7 +57,7 @@ struct igt_drm_clients *igt_drm_clients_init(void *private_data)
 struct igt_drm_client *
 igt_drm_clients_find(struct igt_drm_clients *clients,
 		     enum igt_drm_client_status status,
-		     unsigned int id)
+		     unsigned int drm_minor, unsigned int id)
 {
 	unsigned int start, num;
 	struct igt_drm_client *c;
@@ -69,7 +69,8 @@ igt_drm_clients_find(struct igt_drm_clients *clients,
 		if (status != c->status)
 			continue;
 
-		if (status == IGT_DRM_CLIENT_FREE || c->id == id)
+		if (status == IGT_DRM_CLIENT_FREE ||
+		    (drm_minor == c->drm_minor && c->id == id))
 			return c;
 	}
 
@@ -123,9 +124,10 @@ igt_drm_client_add(struct igt_drm_clients *clients,
 {
 	struct igt_drm_client *c;
 
-	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE, info->id));
+	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE,
+				     drm_minor, info->id));
 
-	c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_FREE, 0);
+	c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_FREE, 0, 0);
 	if (!c) {
 		unsigned int idx = clients->num_clients;
 
@@ -359,11 +361,11 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 				continue;
 
 			if (igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE,
-						info.id))
+						 minor, info.id))
 				continue; /* Skip duplicate fds. */
 
 			c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_PROBE,
-						info.id);
+						 minor, info.id);
 			if (!c)
 				igt_drm_client_add(clients, &info, client_pid,
 						   client_name, minor);
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
index 7a6318c6af5f..6da445eab34a 100644
--- a/lib/igt_drm_clients.h
+++ b/lib/igt_drm_clients.h
@@ -87,7 +87,7 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 struct igt_drm_client *
 igt_drm_clients_find(struct igt_drm_clients *clients,
 		     enum igt_drm_client_status status,
-		     unsigned int id);
+		     unsigned int drm_minor, unsigned int id);
 
 struct igt_drm_clients *
 igt_drm_clients_sort(struct igt_drm_clients *clients,
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [Intel-gfx] [PATCH i-g-t 07/11] libdrmclient: Support multiple DRM cards
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Require DRM minor match during client lookup.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c | 14 ++++++++------
 lib/igt_drm_clients.h |  2 +-
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
index 116479a9f363..cf044597d537 100644
--- a/lib/igt_drm_clients.c
+++ b/lib/igt_drm_clients.c
@@ -57,7 +57,7 @@ struct igt_drm_clients *igt_drm_clients_init(void *private_data)
 struct igt_drm_client *
 igt_drm_clients_find(struct igt_drm_clients *clients,
 		     enum igt_drm_client_status status,
-		     unsigned int id)
+		     unsigned int drm_minor, unsigned int id)
 {
 	unsigned int start, num;
 	struct igt_drm_client *c;
@@ -69,7 +69,8 @@ igt_drm_clients_find(struct igt_drm_clients *clients,
 		if (status != c->status)
 			continue;
 
-		if (status == IGT_DRM_CLIENT_FREE || c->id == id)
+		if (status == IGT_DRM_CLIENT_FREE ||
+		    (drm_minor == c->drm_minor && c->id == id))
 			return c;
 	}
 
@@ -123,9 +124,10 @@ igt_drm_client_add(struct igt_drm_clients *clients,
 {
 	struct igt_drm_client *c;
 
-	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE, info->id));
+	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE,
+				     drm_minor, info->id));
 
-	c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_FREE, 0);
+	c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_FREE, 0, 0);
 	if (!c) {
 		unsigned int idx = clients->num_clients;
 
@@ -359,11 +361,11 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 				continue;
 
 			if (igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE,
-						info.id))
+						 minor, info.id))
 				continue; /* Skip duplicate fds. */
 
 			c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_PROBE,
-						info.id);
+						 minor, info.id);
 			if (!c)
 				igt_drm_client_add(clients, &info, client_pid,
 						   client_name, minor);
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
index 7a6318c6af5f..6da445eab34a 100644
--- a/lib/igt_drm_clients.h
+++ b/lib/igt_drm_clients.h
@@ -87,7 +87,7 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 struct igt_drm_client *
 igt_drm_clients_find(struct igt_drm_clients *clients,
 		     enum igt_drm_client_status status,
-		     unsigned int id);
+		     unsigned int drm_minor, unsigned int id);
 
 struct igt_drm_clients *
 igt_drm_clients_sort(struct igt_drm_clients *clients,
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [igt-dev] [PATCH i-g-t 07/11] libdrmclient: Support multiple DRM cards
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Require DRM minor match during client lookup.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c | 14 ++++++++------
 lib/igt_drm_clients.h |  2 +-
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
index 116479a9f363..cf044597d537 100644
--- a/lib/igt_drm_clients.c
+++ b/lib/igt_drm_clients.c
@@ -57,7 +57,7 @@ struct igt_drm_clients *igt_drm_clients_init(void *private_data)
 struct igt_drm_client *
 igt_drm_clients_find(struct igt_drm_clients *clients,
 		     enum igt_drm_client_status status,
-		     unsigned int id)
+		     unsigned int drm_minor, unsigned int id)
 {
 	unsigned int start, num;
 	struct igt_drm_client *c;
@@ -69,7 +69,8 @@ igt_drm_clients_find(struct igt_drm_clients *clients,
 		if (status != c->status)
 			continue;
 
-		if (status == IGT_DRM_CLIENT_FREE || c->id == id)
+		if (status == IGT_DRM_CLIENT_FREE ||
+		    (drm_minor == c->drm_minor && c->id == id))
 			return c;
 	}
 
@@ -123,9 +124,10 @@ igt_drm_client_add(struct igt_drm_clients *clients,
 {
 	struct igt_drm_client *c;
 
-	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE, info->id));
+	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE,
+				     drm_minor, info->id));
 
-	c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_FREE, 0);
+	c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_FREE, 0, 0);
 	if (!c) {
 		unsigned int idx = clients->num_clients;
 
@@ -359,11 +361,11 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 				continue;
 
 			if (igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE,
-						info.id))
+						 minor, info.id))
 				continue; /* Skip duplicate fds. */
 
 			c = igt_drm_clients_find(clients, IGT_DRM_CLIENT_PROBE,
-						info.id);
+						 minor, info.id);
 			if (!c)
 				igt_drm_client_add(clients, &info, client_pid,
 						   client_name, minor);
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
index 7a6318c6af5f..6da445eab34a 100644
--- a/lib/igt_drm_clients.h
+++ b/lib/igt_drm_clients.h
@@ -87,7 +87,7 @@ igt_drm_clients_scan(struct igt_drm_clients *clients,
 struct igt_drm_client *
 igt_drm_clients_find(struct igt_drm_clients *clients,
 		     enum igt_drm_client_status status,
-		     unsigned int id);
+		     unsigned int drm_minor, unsigned int id);
 
 struct igt_drm_clients *
 igt_drm_clients_sort(struct igt_drm_clients *clients,
-- 
2.32.0

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH i-g-t 08/11] libdrmfdinfo: Track largest engine index
  2022-02-22 13:55 ` [Intel-gfx] " Tvrtko Ursulin
  (?)
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  -1 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Prep code for incoming work.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_fdinfo.c | 2 ++
 lib/igt_drm_fdinfo.h | 1 +
 2 files changed, 3 insertions(+)

diff --git a/lib/igt_drm_fdinfo.c b/lib/igt_drm_fdinfo.c
index 96a8b768a4b1..964c6b708960 100644
--- a/lib/igt_drm_fdinfo.c
+++ b/lib/igt_drm_fdinfo.c
@@ -159,6 +159,8 @@ __igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info,
 					info->capacity[idx] = 1;
 				info->busy[idx] = val;
 				info->num_engines++;
+				if (idx > info->last_engine_index)
+					info->last_engine_index = idx;
 			}
 		} else if (!strncmp(l, "drm-engine-capacity-", 20)) {
 			idx = parse_engine(l, info,
diff --git a/lib/igt_drm_fdinfo.h b/lib/igt_drm_fdinfo.h
index bea4a6304734..804e8e1aa333 100644
--- a/lib/igt_drm_fdinfo.h
+++ b/lib/igt_drm_fdinfo.h
@@ -36,6 +36,7 @@ struct drm_client_fdinfo {
 	unsigned long id;
 
 	unsigned int num_engines;
+	unsigned int last_engine_index;
 	unsigned int capacity[16];
 	char names[16][256];
 	uint64_t busy[16];
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [Intel-gfx] [PATCH i-g-t 08/11] libdrmfdinfo: Track largest engine index
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Prep code for incoming work.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_fdinfo.c | 2 ++
 lib/igt_drm_fdinfo.h | 1 +
 2 files changed, 3 insertions(+)

diff --git a/lib/igt_drm_fdinfo.c b/lib/igt_drm_fdinfo.c
index 96a8b768a4b1..964c6b708960 100644
--- a/lib/igt_drm_fdinfo.c
+++ b/lib/igt_drm_fdinfo.c
@@ -159,6 +159,8 @@ __igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info,
 					info->capacity[idx] = 1;
 				info->busy[idx] = val;
 				info->num_engines++;
+				if (idx > info->last_engine_index)
+					info->last_engine_index = idx;
 			}
 		} else if (!strncmp(l, "drm-engine-capacity-", 20)) {
 			idx = parse_engine(l, info,
diff --git a/lib/igt_drm_fdinfo.h b/lib/igt_drm_fdinfo.h
index bea4a6304734..804e8e1aa333 100644
--- a/lib/igt_drm_fdinfo.h
+++ b/lib/igt_drm_fdinfo.h
@@ -36,6 +36,7 @@ struct drm_client_fdinfo {
 	unsigned long id;
 
 	unsigned int num_engines;
+	unsigned int last_engine_index;
 	unsigned int capacity[16];
 	char names[16][256];
 	uint64_t busy[16];
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [igt-dev] [PATCH i-g-t 08/11] libdrmfdinfo: Track largest engine index
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Prep code for incoming work.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_fdinfo.c | 2 ++
 lib/igt_drm_fdinfo.h | 1 +
 2 files changed, 3 insertions(+)

diff --git a/lib/igt_drm_fdinfo.c b/lib/igt_drm_fdinfo.c
index 96a8b768a4b1..964c6b708960 100644
--- a/lib/igt_drm_fdinfo.c
+++ b/lib/igt_drm_fdinfo.c
@@ -159,6 +159,8 @@ __igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info,
 					info->capacity[idx] = 1;
 				info->busy[idx] = val;
 				info->num_engines++;
+				if (idx > info->last_engine_index)
+					info->last_engine_index = idx;
 			}
 		} else if (!strncmp(l, "drm-engine-capacity-", 20)) {
 			idx = parse_engine(l, info,
diff --git a/lib/igt_drm_fdinfo.h b/lib/igt_drm_fdinfo.h
index bea4a6304734..804e8e1aa333 100644
--- a/lib/igt_drm_fdinfo.h
+++ b/lib/igt_drm_fdinfo.h
@@ -36,6 +36,7 @@ struct drm_client_fdinfo {
 	unsigned long id;
 
 	unsigned int num_engines;
+	unsigned int last_engine_index;
 	unsigned int capacity[16];
 	char names[16][256];
 	uint64_t busy[16];
-- 
2.32.0

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH i-g-t 09/11] libdrmclient/intel_gpu_top: Decouple hardcoded engine assumptions
  2022-02-22 13:55 ` [Intel-gfx] " Tvrtko Ursulin
  (?)
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  -1 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Intel_gpu_top gets it's main engine configuration data via PMU probe and
uses that for per client view as well. Furthemore code so far assumed only
clients belonging from a single DRM card would be tracked in a single
clients list.

Break this inter-dependency by moving the engine data to be per client and
also have libdrmclient probe the engine configuration independently using
the previously added libdrmfdinfo facilities.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c |  34 +++++++++++--
 lib/igt_drm_clients.h |  11 ++---
 tools/intel_gpu_top.c | 112 ++++++++++++++++++++++++++++++------------
 3 files changed, 117 insertions(+), 40 deletions(-)

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
index cf044597d537..6299f3632b74 100644
--- a/lib/igt_drm_clients.c
+++ b/lib/igt_drm_clients.c
@@ -103,7 +103,7 @@ igt_drm_client_update(struct igt_drm_client *c, unsigned int pid, char *name,
 	c->last_runtime = 0;
 	c->total_runtime = 0;
 
-	for (i = 0; i < c->clients->num_classes; i++) {
+	for (i = 0; i <= c->engines->max_engine_id; i++) {
 		if (val[i] < c->last[i])
 			continue; /* It will catch up soon. */
 
@@ -123,6 +123,7 @@ igt_drm_client_add(struct igt_drm_clients *clients,
 		   unsigned int pid, char *name, unsigned int drm_minor)
 {
 	struct igt_drm_client *c;
+	unsigned int i;
 
 	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE,
 				     drm_minor, info->id));
@@ -143,8 +144,28 @@ igt_drm_client_add(struct igt_drm_clients *clients,
 	c->id = info->id;
 	c->drm_minor = drm_minor;
 	c->clients = clients;
-	c->val = calloc(clients->num_classes, sizeof(c->val));
-	c->last = calloc(clients->num_classes, sizeof(c->last));
+	c->engines = malloc(sizeof(*c->engines));
+	assert(c->engines);
+	memset(c->engines, 0, sizeof(*c->engines));
+	c->engines->capacity = calloc(info->last_engine_index + 1,
+				      sizeof(*c->engines->capacity));
+	assert(c->engines->capacity);
+	c->engines->names = calloc(info->last_engine_index + 1,
+				   sizeof(*c->engines->names));
+	assert(c->engines->names);
+
+	for (i = 0; i <= info->last_engine_index; i++) {
+		if (!info->capacity[i])
+			continue;
+
+		c->engines->capacity[i] = info->capacity[i];
+		c->engines->names[i] = strdup(info->names[i]);
+		assert(c->engines->names[i]);
+		c->engines->num_engines++;
+		c->engines->max_engine_id = i;
+	}
+	c->val = calloc(c->engines->max_engine_id + 1, sizeof(c->val));
+	c->last = calloc(c->engines->max_engine_id + 1, sizeof(c->last));
 	assert(c->val && c->last);
 
 	igt_drm_client_update(c, pid, name, info->busy);
@@ -152,6 +173,13 @@ igt_drm_client_add(struct igt_drm_clients *clients,
 
 void igt_drm_client_free(struct igt_drm_client *c)
 {
+	unsigned int i;
+
+	for (i = 0; i <= c->engines->max_engine_id; i++)
+		free(c->engines->names[i]);
+	free(c->engines->capacity);
+	free(c->engines->names);
+	free(c->engines);
 	free(c->val);
 	free(c->last);
 	memset(c, 0, sizeof(*c));
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
index 6da445eab34a..4668df2d7fd6 100644
--- a/lib/igt_drm_clients.h
+++ b/lib/igt_drm_clients.h
@@ -35,10 +35,11 @@ enum igt_drm_client_status {
 	IGT_DRM_CLIENT_PROBE
 };
 
-struct igt_drm_client_engine_class {
-	unsigned int engine_class;
-	const char *name;
+struct igt_drm_client_engines {
 	unsigned int num_engines;
+	unsigned int max_engine_id;
+	unsigned int *capacity;
+	char **names;
 };
 
 struct igt_drm_clients;
@@ -47,6 +48,7 @@ struct igt_drm_client {
 	struct igt_drm_clients *clients;
 
 	enum igt_drm_client_status status;
+	struct igt_drm_client_engines *engines;
 	unsigned int id;
 	unsigned int drm_minor;
 	unsigned int pid;
@@ -63,9 +65,6 @@ struct igt_drm_clients {
 	unsigned int num_clients;
 	unsigned int active_clients;
 
-	unsigned int num_classes;
-	struct igt_drm_client_engine_class *engine_class;
-
 	void *private_data;
 
 	struct igt_drm_client *client; /* Must be last. */
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index 99e8e1d8ffd4..fb7ec9ae6d74 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -66,6 +66,12 @@ struct pmu_counter {
 	bool present;
 };
 
+struct engine_class {
+	unsigned int engine_class;
+	const char *name;
+	unsigned int num_engines;
+};
+
 struct engine {
 	const char *name;
 	char *display_name;
@@ -84,7 +90,7 @@ struct engine {
 struct engines {
 	unsigned int num_engines;
 	unsigned int num_classes;
-	struct igt_drm_client_engine_class *class;
+	struct engine_class *class;
 	unsigned int num_counters;
 	DIR *root;
 	int fd;
@@ -116,6 +122,11 @@ struct engines {
 
 };
 
+struct intel_clients {
+	const char *pci_slot;
+	struct igt_drm_client_engines classes;
+};
+
 static struct termios termios_orig;
 
 __attribute__((format(scanf,3,4)))
@@ -733,9 +744,8 @@ static struct igt_drm_clients *display_clients(struct igt_drm_clients *clients)
 	ac = calloc(clients->num_clients, sizeof(*c));
 	assert(ac);
 
-	aggregated->num_classes = clients->num_classes;
-	aggregated->engine_class = clients->engine_class;
 	aggregated->private_data = clients->private_data;
+
 	aggregated->client = ac;
 
 	igt_for_each_drm_client(clients, c, tmp) {
@@ -756,7 +766,8 @@ static struct igt_drm_clients *display_clients(struct igt_drm_clients *clients)
 			ac->pid = c->pid;
 			strcpy(ac->name, c->name);
 			strcpy(ac->print_name, c->print_name);
-			ac->val = calloc(clients->num_classes,
+			ac->engines = c->engines;
+			ac->val = calloc(c->engines->max_engine_id + 1,
 					 sizeof(ac->val[0]));
 			assert(ac->val);
 			ac->samples = 1;
@@ -771,7 +782,7 @@ static struct igt_drm_clients *display_clients(struct igt_drm_clients *clients)
 		ac->total_runtime += c->total_runtime;
 		ac->last_runtime += c->last_runtime;
 
-		for (i = 0; i < clients->num_classes; i++)
+		for (i = 0; i <= c->engines->max_engine_id; i++)
 			ac->val[i] += c->val[i];
 	}
 
@@ -1498,15 +1509,15 @@ print_engines_footer(struct engines *engines, double t,
 
 static int class_cmp(const void *_a, const void *_b)
 {
-	const struct igt_drm_client_engine_class *a = _a;
-	const struct igt_drm_client_engine_class *b = _b;
+	const struct engine_class *a = _a;
+	const struct engine_class *b = _b;
 
 	return a->engine_class - b->engine_class;
 }
 
 static void init_engine_classes(struct engines *engines)
 {
-	struct igt_drm_client_engine_class *classes;
+	struct engine_class *classes;
 	unsigned int i, num;
 	int max = -1;
 
@@ -1678,6 +1689,8 @@ static int
 print_clients_header(struct igt_drm_clients *clients, int lines,
 		     int con_w, int con_h, int *class_w)
 {
+	struct intel_clients *iclients = clients->private_data;
+
 	if (output_mode == INTERACTIVE) {
 		const char *pidname = "   PID              NAME ";
 		unsigned int num_active = 0;
@@ -1692,25 +1705,25 @@ print_clients_header(struct igt_drm_clients *clients, int lines,
 		if (lines++ >= con_h || len >= con_w)
 			return lines;
 
-		if (clients->num_classes) {
+		if (iclients->classes.num_engines) {
 			unsigned int i;
 			int width;
 
-			for (i = 0; i < clients->num_classes; i++) {
-				if (clients->engine_class[i].num_engines)
+			for (i = 0; i <= iclients->classes.max_engine_id; i++) {
+				if (iclients->classes.capacity[i])
 					num_active++;
 			}
 
 			*class_w = width = (con_w - len) / num_active;
 
-			for (i = 0; i < clients->num_classes; i++) {
-				const char *name = clients->engine_class[i].name;
+			for (i = 0; i <= iclients->classes.max_engine_id; i++) {
+				const char *name = iclients->classes.names[i];
 				int name_len = strlen(name);
 				int pad = (width - name_len) / 2;
 				int spaces = width - pad - name_len;
 
-				if (!clients->engine_class[i].num_engines)
-					continue; /* Assert in the ideal world. */
+				if (!iclients->classes.capacity[i])
+					continue;
 
 				if (pad < 0 || spaces < 0)
 					continue;
@@ -1725,7 +1738,7 @@ print_clients_header(struct igt_drm_clients *clients, int lines,
 		n_spaces(con_w - len);
 		printf("\033[0m\n");
 	} else {
-		if (clients->num_classes)
+		if (iclients->classes.num_engines)
 			pops->open_struct("clients");
 	}
 
@@ -1740,6 +1753,7 @@ print_client(struct igt_drm_client *c, struct engines *engines, double t, int li
 	     int con_w, int con_h, unsigned int period_us, int *class_w)
 {
 	struct igt_drm_clients *clients = c->clients;
+	struct intel_clients *iclients = clients->private_data;
 	unsigned int i;
 
 	if (output_mode == INTERACTIVE) {
@@ -1750,14 +1764,16 @@ print_client(struct igt_drm_client *c, struct engines *engines, double t, int li
 
 		printf("%6u %17s ", c->pid, c->print_name);
 
-		for (i = 0; c->samples > 1 && i < clients->num_classes; i++) {
+		for (i = 0;
+		     c->samples > 1 && i <= iclients->classes.max_engine_id;
+		     i++) {
 			double pct;
 
-			if (!clients->engine_class[i].num_engines)
-				continue; /* Assert in the ideal world. */
+			if (!iclients->classes.capacity[i])
+				continue;
 
 			pct = (double)c->val[i] / period_us / 1e3 * 100 /
-			      clients->engine_class[i].num_engines;
+			      iclients->classes.capacity[i];
 
 			/*
 			 * Guard against possible time-drift between sampling
@@ -1785,11 +1801,11 @@ print_client(struct igt_drm_client *c, struct engines *engines, double t, int li
 		if (c->samples > 1) {
 			pops->open_struct("engine-classes");
 
-			for (i = 0; i < clients->num_classes; i++) {
+			for (i = 0; i <= iclients->classes.max_engine_id; i++) {
 				double pct;
 
 				snprintf(buf, sizeof(buf), "%s",
-					clients->engine_class[i].name);
+					 iclients->classes.names[i]);
 				pops->open_struct(buf);
 
 				pct = (double)c->val[i] / period_us / 1e3 * 100;
@@ -1818,7 +1834,9 @@ print_clients_footer(struct igt_drm_clients *clients, double t,
 		if (lines++ < con_h)
 			printf("\n");
 	} else {
-		if (clients->num_classes)
+		struct intel_clients *iclients = clients->private_data;
+
+		if (iclients->classes.num_engines)
 			pops->close_struct();
 	}
 
@@ -2018,14 +2036,47 @@ static void show_help_screen(void)
 static bool client_match(const struct igt_drm_clients *clients,
 			 const struct drm_client_fdinfo *info)
 {
+	struct intel_clients *iclients = clients->private_data;
+
 	if (strcmp(info->driver, "i915"))
 		return false;
-	if (strcmp(info->pdev, clients->private_data))
+	if (strcmp(info->pdev, iclients->pci_slot))
 		return false;
 
 	return true;
 }
 
+static void
+intel_init_clients(struct intel_clients *iclients,
+		   const struct igt_device_card *card, struct engines *engines)
+{
+	unsigned int i;
+
+	iclients->pci_slot = strdup(card->pci_slot_name[0] ?
+				    card->pci_slot_name : IGPU_PCI);
+	assert(iclients->pci_slot);
+
+	iclients->classes.num_engines = engines->num_classes;
+	iclients->classes.max_engine_id = engines->num_classes - 1;
+
+	iclients->classes.capacity = calloc(engines->num_classes,
+					    sizeof(*iclients->classes.capacity));
+	assert(iclients->classes.capacity);
+	iclients->classes.names = calloc(engines->num_classes,
+					 sizeof(*iclients->classes.names));
+	assert(iclients->classes.names);
+
+	for (i = 0; i < engines->num_classes; i++) {
+		if (!engines->class[i].num_engines)
+			continue;
+
+		iclients->classes.num_engines++;
+		iclients->classes.max_engine_id = i;
+		iclients->classes.capacity[i] = engines->class[i].num_engines;
+		iclients->classes.names[i] = strdup(engines->class[i].name);
+	}
+}
+
 int main(int argc, char **argv)
 {
 	unsigned int period_us = DEFAULT_PERIOD_MS * 1000;
@@ -2036,6 +2087,7 @@ int main(int argc, char **argv)
 		"video",
 		"video-enhance",
 	};
+	struct intel_clients iclients;
 	int con_w = -1, con_h = -1;
 	char *output_path = NULL;
 	struct engines *engines;
@@ -2146,6 +2198,8 @@ int main(int argc, char **argv)
 	else
 		pmu_device = strdup("i915");
 
+	codename = igt_device_get_pretty_name(&card, false);
+
 	engines = discover_engines(pmu_device);
 	if (!engines) {
 		fprintf(stderr,
@@ -2174,18 +2228,14 @@ int main(int argc, char **argv)
 
 	ret = EXIT_SUCCESS;
 
-	clients = igt_drm_clients_init(strdup(card.pci_slot_name[0] ?
-					      card.pci_slot_name : IGPU_PCI));
 	init_engine_classes(engines);
-	if (clients) {
-		clients->num_classes = engines->num_classes;
-		clients->engine_class = engines->class;
-	}
+
+	intel_init_clients(&iclients, &card, engines);
+	clients = igt_drm_clients_init(&iclients);
 
 	pmu_sample(engines);
 	igt_drm_clients_scan(clients, client_match, engine_map,
 			     ARRAY_SIZE(engine_map));
-	codename = igt_device_get_pretty_name(&card, false);
 
 	while (!stop_top) {
 		struct igt_drm_clients *disp_clients;
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [Intel-gfx] [PATCH i-g-t 09/11] libdrmclient/intel_gpu_top: Decouple hardcoded engine assumptions
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Intel_gpu_top gets it's main engine configuration data via PMU probe and
uses that for per client view as well. Furthemore code so far assumed only
clients belonging from a single DRM card would be tracked in a single
clients list.

Break this inter-dependency by moving the engine data to be per client and
also have libdrmclient probe the engine configuration independently using
the previously added libdrmfdinfo facilities.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c |  34 +++++++++++--
 lib/igt_drm_clients.h |  11 ++---
 tools/intel_gpu_top.c | 112 ++++++++++++++++++++++++++++++------------
 3 files changed, 117 insertions(+), 40 deletions(-)

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
index cf044597d537..6299f3632b74 100644
--- a/lib/igt_drm_clients.c
+++ b/lib/igt_drm_clients.c
@@ -103,7 +103,7 @@ igt_drm_client_update(struct igt_drm_client *c, unsigned int pid, char *name,
 	c->last_runtime = 0;
 	c->total_runtime = 0;
 
-	for (i = 0; i < c->clients->num_classes; i++) {
+	for (i = 0; i <= c->engines->max_engine_id; i++) {
 		if (val[i] < c->last[i])
 			continue; /* It will catch up soon. */
 
@@ -123,6 +123,7 @@ igt_drm_client_add(struct igt_drm_clients *clients,
 		   unsigned int pid, char *name, unsigned int drm_minor)
 {
 	struct igt_drm_client *c;
+	unsigned int i;
 
 	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE,
 				     drm_minor, info->id));
@@ -143,8 +144,28 @@ igt_drm_client_add(struct igt_drm_clients *clients,
 	c->id = info->id;
 	c->drm_minor = drm_minor;
 	c->clients = clients;
-	c->val = calloc(clients->num_classes, sizeof(c->val));
-	c->last = calloc(clients->num_classes, sizeof(c->last));
+	c->engines = malloc(sizeof(*c->engines));
+	assert(c->engines);
+	memset(c->engines, 0, sizeof(*c->engines));
+	c->engines->capacity = calloc(info->last_engine_index + 1,
+				      sizeof(*c->engines->capacity));
+	assert(c->engines->capacity);
+	c->engines->names = calloc(info->last_engine_index + 1,
+				   sizeof(*c->engines->names));
+	assert(c->engines->names);
+
+	for (i = 0; i <= info->last_engine_index; i++) {
+		if (!info->capacity[i])
+			continue;
+
+		c->engines->capacity[i] = info->capacity[i];
+		c->engines->names[i] = strdup(info->names[i]);
+		assert(c->engines->names[i]);
+		c->engines->num_engines++;
+		c->engines->max_engine_id = i;
+	}
+	c->val = calloc(c->engines->max_engine_id + 1, sizeof(c->val));
+	c->last = calloc(c->engines->max_engine_id + 1, sizeof(c->last));
 	assert(c->val && c->last);
 
 	igt_drm_client_update(c, pid, name, info->busy);
@@ -152,6 +173,13 @@ igt_drm_client_add(struct igt_drm_clients *clients,
 
 void igt_drm_client_free(struct igt_drm_client *c)
 {
+	unsigned int i;
+
+	for (i = 0; i <= c->engines->max_engine_id; i++)
+		free(c->engines->names[i]);
+	free(c->engines->capacity);
+	free(c->engines->names);
+	free(c->engines);
 	free(c->val);
 	free(c->last);
 	memset(c, 0, sizeof(*c));
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
index 6da445eab34a..4668df2d7fd6 100644
--- a/lib/igt_drm_clients.h
+++ b/lib/igt_drm_clients.h
@@ -35,10 +35,11 @@ enum igt_drm_client_status {
 	IGT_DRM_CLIENT_PROBE
 };
 
-struct igt_drm_client_engine_class {
-	unsigned int engine_class;
-	const char *name;
+struct igt_drm_client_engines {
 	unsigned int num_engines;
+	unsigned int max_engine_id;
+	unsigned int *capacity;
+	char **names;
 };
 
 struct igt_drm_clients;
@@ -47,6 +48,7 @@ struct igt_drm_client {
 	struct igt_drm_clients *clients;
 
 	enum igt_drm_client_status status;
+	struct igt_drm_client_engines *engines;
 	unsigned int id;
 	unsigned int drm_minor;
 	unsigned int pid;
@@ -63,9 +65,6 @@ struct igt_drm_clients {
 	unsigned int num_clients;
 	unsigned int active_clients;
 
-	unsigned int num_classes;
-	struct igt_drm_client_engine_class *engine_class;
-
 	void *private_data;
 
 	struct igt_drm_client *client; /* Must be last. */
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index 99e8e1d8ffd4..fb7ec9ae6d74 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -66,6 +66,12 @@ struct pmu_counter {
 	bool present;
 };
 
+struct engine_class {
+	unsigned int engine_class;
+	const char *name;
+	unsigned int num_engines;
+};
+
 struct engine {
 	const char *name;
 	char *display_name;
@@ -84,7 +90,7 @@ struct engine {
 struct engines {
 	unsigned int num_engines;
 	unsigned int num_classes;
-	struct igt_drm_client_engine_class *class;
+	struct engine_class *class;
 	unsigned int num_counters;
 	DIR *root;
 	int fd;
@@ -116,6 +122,11 @@ struct engines {
 
 };
 
+struct intel_clients {
+	const char *pci_slot;
+	struct igt_drm_client_engines classes;
+};
+
 static struct termios termios_orig;
 
 __attribute__((format(scanf,3,4)))
@@ -733,9 +744,8 @@ static struct igt_drm_clients *display_clients(struct igt_drm_clients *clients)
 	ac = calloc(clients->num_clients, sizeof(*c));
 	assert(ac);
 
-	aggregated->num_classes = clients->num_classes;
-	aggregated->engine_class = clients->engine_class;
 	aggregated->private_data = clients->private_data;
+
 	aggregated->client = ac;
 
 	igt_for_each_drm_client(clients, c, tmp) {
@@ -756,7 +766,8 @@ static struct igt_drm_clients *display_clients(struct igt_drm_clients *clients)
 			ac->pid = c->pid;
 			strcpy(ac->name, c->name);
 			strcpy(ac->print_name, c->print_name);
-			ac->val = calloc(clients->num_classes,
+			ac->engines = c->engines;
+			ac->val = calloc(c->engines->max_engine_id + 1,
 					 sizeof(ac->val[0]));
 			assert(ac->val);
 			ac->samples = 1;
@@ -771,7 +782,7 @@ static struct igt_drm_clients *display_clients(struct igt_drm_clients *clients)
 		ac->total_runtime += c->total_runtime;
 		ac->last_runtime += c->last_runtime;
 
-		for (i = 0; i < clients->num_classes; i++)
+		for (i = 0; i <= c->engines->max_engine_id; i++)
 			ac->val[i] += c->val[i];
 	}
 
@@ -1498,15 +1509,15 @@ print_engines_footer(struct engines *engines, double t,
 
 static int class_cmp(const void *_a, const void *_b)
 {
-	const struct igt_drm_client_engine_class *a = _a;
-	const struct igt_drm_client_engine_class *b = _b;
+	const struct engine_class *a = _a;
+	const struct engine_class *b = _b;
 
 	return a->engine_class - b->engine_class;
 }
 
 static void init_engine_classes(struct engines *engines)
 {
-	struct igt_drm_client_engine_class *classes;
+	struct engine_class *classes;
 	unsigned int i, num;
 	int max = -1;
 
@@ -1678,6 +1689,8 @@ static int
 print_clients_header(struct igt_drm_clients *clients, int lines,
 		     int con_w, int con_h, int *class_w)
 {
+	struct intel_clients *iclients = clients->private_data;
+
 	if (output_mode == INTERACTIVE) {
 		const char *pidname = "   PID              NAME ";
 		unsigned int num_active = 0;
@@ -1692,25 +1705,25 @@ print_clients_header(struct igt_drm_clients *clients, int lines,
 		if (lines++ >= con_h || len >= con_w)
 			return lines;
 
-		if (clients->num_classes) {
+		if (iclients->classes.num_engines) {
 			unsigned int i;
 			int width;
 
-			for (i = 0; i < clients->num_classes; i++) {
-				if (clients->engine_class[i].num_engines)
+			for (i = 0; i <= iclients->classes.max_engine_id; i++) {
+				if (iclients->classes.capacity[i])
 					num_active++;
 			}
 
 			*class_w = width = (con_w - len) / num_active;
 
-			for (i = 0; i < clients->num_classes; i++) {
-				const char *name = clients->engine_class[i].name;
+			for (i = 0; i <= iclients->classes.max_engine_id; i++) {
+				const char *name = iclients->classes.names[i];
 				int name_len = strlen(name);
 				int pad = (width - name_len) / 2;
 				int spaces = width - pad - name_len;
 
-				if (!clients->engine_class[i].num_engines)
-					continue; /* Assert in the ideal world. */
+				if (!iclients->classes.capacity[i])
+					continue;
 
 				if (pad < 0 || spaces < 0)
 					continue;
@@ -1725,7 +1738,7 @@ print_clients_header(struct igt_drm_clients *clients, int lines,
 		n_spaces(con_w - len);
 		printf("\033[0m\n");
 	} else {
-		if (clients->num_classes)
+		if (iclients->classes.num_engines)
 			pops->open_struct("clients");
 	}
 
@@ -1740,6 +1753,7 @@ print_client(struct igt_drm_client *c, struct engines *engines, double t, int li
 	     int con_w, int con_h, unsigned int period_us, int *class_w)
 {
 	struct igt_drm_clients *clients = c->clients;
+	struct intel_clients *iclients = clients->private_data;
 	unsigned int i;
 
 	if (output_mode == INTERACTIVE) {
@@ -1750,14 +1764,16 @@ print_client(struct igt_drm_client *c, struct engines *engines, double t, int li
 
 		printf("%6u %17s ", c->pid, c->print_name);
 
-		for (i = 0; c->samples > 1 && i < clients->num_classes; i++) {
+		for (i = 0;
+		     c->samples > 1 && i <= iclients->classes.max_engine_id;
+		     i++) {
 			double pct;
 
-			if (!clients->engine_class[i].num_engines)
-				continue; /* Assert in the ideal world. */
+			if (!iclients->classes.capacity[i])
+				continue;
 
 			pct = (double)c->val[i] / period_us / 1e3 * 100 /
-			      clients->engine_class[i].num_engines;
+			      iclients->classes.capacity[i];
 
 			/*
 			 * Guard against possible time-drift between sampling
@@ -1785,11 +1801,11 @@ print_client(struct igt_drm_client *c, struct engines *engines, double t, int li
 		if (c->samples > 1) {
 			pops->open_struct("engine-classes");
 
-			for (i = 0; i < clients->num_classes; i++) {
+			for (i = 0; i <= iclients->classes.max_engine_id; i++) {
 				double pct;
 
 				snprintf(buf, sizeof(buf), "%s",
-					clients->engine_class[i].name);
+					 iclients->classes.names[i]);
 				pops->open_struct(buf);
 
 				pct = (double)c->val[i] / period_us / 1e3 * 100;
@@ -1818,7 +1834,9 @@ print_clients_footer(struct igt_drm_clients *clients, double t,
 		if (lines++ < con_h)
 			printf("\n");
 	} else {
-		if (clients->num_classes)
+		struct intel_clients *iclients = clients->private_data;
+
+		if (iclients->classes.num_engines)
 			pops->close_struct();
 	}
 
@@ -2018,14 +2036,47 @@ static void show_help_screen(void)
 static bool client_match(const struct igt_drm_clients *clients,
 			 const struct drm_client_fdinfo *info)
 {
+	struct intel_clients *iclients = clients->private_data;
+
 	if (strcmp(info->driver, "i915"))
 		return false;
-	if (strcmp(info->pdev, clients->private_data))
+	if (strcmp(info->pdev, iclients->pci_slot))
 		return false;
 
 	return true;
 }
 
+static void
+intel_init_clients(struct intel_clients *iclients,
+		   const struct igt_device_card *card, struct engines *engines)
+{
+	unsigned int i;
+
+	iclients->pci_slot = strdup(card->pci_slot_name[0] ?
+				    card->pci_slot_name : IGPU_PCI);
+	assert(iclients->pci_slot);
+
+	iclients->classes.num_engines = engines->num_classes;
+	iclients->classes.max_engine_id = engines->num_classes - 1;
+
+	iclients->classes.capacity = calloc(engines->num_classes,
+					    sizeof(*iclients->classes.capacity));
+	assert(iclients->classes.capacity);
+	iclients->classes.names = calloc(engines->num_classes,
+					 sizeof(*iclients->classes.names));
+	assert(iclients->classes.names);
+
+	for (i = 0; i < engines->num_classes; i++) {
+		if (!engines->class[i].num_engines)
+			continue;
+
+		iclients->classes.num_engines++;
+		iclients->classes.max_engine_id = i;
+		iclients->classes.capacity[i] = engines->class[i].num_engines;
+		iclients->classes.names[i] = strdup(engines->class[i].name);
+	}
+}
+
 int main(int argc, char **argv)
 {
 	unsigned int period_us = DEFAULT_PERIOD_MS * 1000;
@@ -2036,6 +2087,7 @@ int main(int argc, char **argv)
 		"video",
 		"video-enhance",
 	};
+	struct intel_clients iclients;
 	int con_w = -1, con_h = -1;
 	char *output_path = NULL;
 	struct engines *engines;
@@ -2146,6 +2198,8 @@ int main(int argc, char **argv)
 	else
 		pmu_device = strdup("i915");
 
+	codename = igt_device_get_pretty_name(&card, false);
+
 	engines = discover_engines(pmu_device);
 	if (!engines) {
 		fprintf(stderr,
@@ -2174,18 +2228,14 @@ int main(int argc, char **argv)
 
 	ret = EXIT_SUCCESS;
 
-	clients = igt_drm_clients_init(strdup(card.pci_slot_name[0] ?
-					      card.pci_slot_name : IGPU_PCI));
 	init_engine_classes(engines);
-	if (clients) {
-		clients->num_classes = engines->num_classes;
-		clients->engine_class = engines->class;
-	}
+
+	intel_init_clients(&iclients, &card, engines);
+	clients = igt_drm_clients_init(&iclients);
 
 	pmu_sample(engines);
 	igt_drm_clients_scan(clients, client_match, engine_map,
 			     ARRAY_SIZE(engine_map));
-	codename = igt_device_get_pretty_name(&card, false);
 
 	while (!stop_top) {
 		struct igt_drm_clients *disp_clients;
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [igt-dev] [PATCH i-g-t 09/11] libdrmclient/intel_gpu_top: Decouple hardcoded engine assumptions
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Intel_gpu_top gets it's main engine configuration data via PMU probe and
uses that for per client view as well. Furthemore code so far assumed only
clients belonging from a single DRM card would be tracked in a single
clients list.

Break this inter-dependency by moving the engine data to be per client and
also have libdrmclient probe the engine configuration independently using
the previously added libdrmfdinfo facilities.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c |  34 +++++++++++--
 lib/igt_drm_clients.h |  11 ++---
 tools/intel_gpu_top.c | 112 ++++++++++++++++++++++++++++++------------
 3 files changed, 117 insertions(+), 40 deletions(-)

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
index cf044597d537..6299f3632b74 100644
--- a/lib/igt_drm_clients.c
+++ b/lib/igt_drm_clients.c
@@ -103,7 +103,7 @@ igt_drm_client_update(struct igt_drm_client *c, unsigned int pid, char *name,
 	c->last_runtime = 0;
 	c->total_runtime = 0;
 
-	for (i = 0; i < c->clients->num_classes; i++) {
+	for (i = 0; i <= c->engines->max_engine_id; i++) {
 		if (val[i] < c->last[i])
 			continue; /* It will catch up soon. */
 
@@ -123,6 +123,7 @@ igt_drm_client_add(struct igt_drm_clients *clients,
 		   unsigned int pid, char *name, unsigned int drm_minor)
 {
 	struct igt_drm_client *c;
+	unsigned int i;
 
 	assert(!igt_drm_clients_find(clients, IGT_DRM_CLIENT_ALIVE,
 				     drm_minor, info->id));
@@ -143,8 +144,28 @@ igt_drm_client_add(struct igt_drm_clients *clients,
 	c->id = info->id;
 	c->drm_minor = drm_minor;
 	c->clients = clients;
-	c->val = calloc(clients->num_classes, sizeof(c->val));
-	c->last = calloc(clients->num_classes, sizeof(c->last));
+	c->engines = malloc(sizeof(*c->engines));
+	assert(c->engines);
+	memset(c->engines, 0, sizeof(*c->engines));
+	c->engines->capacity = calloc(info->last_engine_index + 1,
+				      sizeof(*c->engines->capacity));
+	assert(c->engines->capacity);
+	c->engines->names = calloc(info->last_engine_index + 1,
+				   sizeof(*c->engines->names));
+	assert(c->engines->names);
+
+	for (i = 0; i <= info->last_engine_index; i++) {
+		if (!info->capacity[i])
+			continue;
+
+		c->engines->capacity[i] = info->capacity[i];
+		c->engines->names[i] = strdup(info->names[i]);
+		assert(c->engines->names[i]);
+		c->engines->num_engines++;
+		c->engines->max_engine_id = i;
+	}
+	c->val = calloc(c->engines->max_engine_id + 1, sizeof(c->val));
+	c->last = calloc(c->engines->max_engine_id + 1, sizeof(c->last));
 	assert(c->val && c->last);
 
 	igt_drm_client_update(c, pid, name, info->busy);
@@ -152,6 +173,13 @@ igt_drm_client_add(struct igt_drm_clients *clients,
 
 void igt_drm_client_free(struct igt_drm_client *c)
 {
+	unsigned int i;
+
+	for (i = 0; i <= c->engines->max_engine_id; i++)
+		free(c->engines->names[i]);
+	free(c->engines->capacity);
+	free(c->engines->names);
+	free(c->engines);
 	free(c->val);
 	free(c->last);
 	memset(c, 0, sizeof(*c));
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
index 6da445eab34a..4668df2d7fd6 100644
--- a/lib/igt_drm_clients.h
+++ b/lib/igt_drm_clients.h
@@ -35,10 +35,11 @@ enum igt_drm_client_status {
 	IGT_DRM_CLIENT_PROBE
 };
 
-struct igt_drm_client_engine_class {
-	unsigned int engine_class;
-	const char *name;
+struct igt_drm_client_engines {
 	unsigned int num_engines;
+	unsigned int max_engine_id;
+	unsigned int *capacity;
+	char **names;
 };
 
 struct igt_drm_clients;
@@ -47,6 +48,7 @@ struct igt_drm_client {
 	struct igt_drm_clients *clients;
 
 	enum igt_drm_client_status status;
+	struct igt_drm_client_engines *engines;
 	unsigned int id;
 	unsigned int drm_minor;
 	unsigned int pid;
@@ -63,9 +65,6 @@ struct igt_drm_clients {
 	unsigned int num_clients;
 	unsigned int active_clients;
 
-	unsigned int num_classes;
-	struct igt_drm_client_engine_class *engine_class;
-
 	void *private_data;
 
 	struct igt_drm_client *client; /* Must be last. */
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index 99e8e1d8ffd4..fb7ec9ae6d74 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -66,6 +66,12 @@ struct pmu_counter {
 	bool present;
 };
 
+struct engine_class {
+	unsigned int engine_class;
+	const char *name;
+	unsigned int num_engines;
+};
+
 struct engine {
 	const char *name;
 	char *display_name;
@@ -84,7 +90,7 @@ struct engine {
 struct engines {
 	unsigned int num_engines;
 	unsigned int num_classes;
-	struct igt_drm_client_engine_class *class;
+	struct engine_class *class;
 	unsigned int num_counters;
 	DIR *root;
 	int fd;
@@ -116,6 +122,11 @@ struct engines {
 
 };
 
+struct intel_clients {
+	const char *pci_slot;
+	struct igt_drm_client_engines classes;
+};
+
 static struct termios termios_orig;
 
 __attribute__((format(scanf,3,4)))
@@ -733,9 +744,8 @@ static struct igt_drm_clients *display_clients(struct igt_drm_clients *clients)
 	ac = calloc(clients->num_clients, sizeof(*c));
 	assert(ac);
 
-	aggregated->num_classes = clients->num_classes;
-	aggregated->engine_class = clients->engine_class;
 	aggregated->private_data = clients->private_data;
+
 	aggregated->client = ac;
 
 	igt_for_each_drm_client(clients, c, tmp) {
@@ -756,7 +766,8 @@ static struct igt_drm_clients *display_clients(struct igt_drm_clients *clients)
 			ac->pid = c->pid;
 			strcpy(ac->name, c->name);
 			strcpy(ac->print_name, c->print_name);
-			ac->val = calloc(clients->num_classes,
+			ac->engines = c->engines;
+			ac->val = calloc(c->engines->max_engine_id + 1,
 					 sizeof(ac->val[0]));
 			assert(ac->val);
 			ac->samples = 1;
@@ -771,7 +782,7 @@ static struct igt_drm_clients *display_clients(struct igt_drm_clients *clients)
 		ac->total_runtime += c->total_runtime;
 		ac->last_runtime += c->last_runtime;
 
-		for (i = 0; i < clients->num_classes; i++)
+		for (i = 0; i <= c->engines->max_engine_id; i++)
 			ac->val[i] += c->val[i];
 	}
 
@@ -1498,15 +1509,15 @@ print_engines_footer(struct engines *engines, double t,
 
 static int class_cmp(const void *_a, const void *_b)
 {
-	const struct igt_drm_client_engine_class *a = _a;
-	const struct igt_drm_client_engine_class *b = _b;
+	const struct engine_class *a = _a;
+	const struct engine_class *b = _b;
 
 	return a->engine_class - b->engine_class;
 }
 
 static void init_engine_classes(struct engines *engines)
 {
-	struct igt_drm_client_engine_class *classes;
+	struct engine_class *classes;
 	unsigned int i, num;
 	int max = -1;
 
@@ -1678,6 +1689,8 @@ static int
 print_clients_header(struct igt_drm_clients *clients, int lines,
 		     int con_w, int con_h, int *class_w)
 {
+	struct intel_clients *iclients = clients->private_data;
+
 	if (output_mode == INTERACTIVE) {
 		const char *pidname = "   PID              NAME ";
 		unsigned int num_active = 0;
@@ -1692,25 +1705,25 @@ print_clients_header(struct igt_drm_clients *clients, int lines,
 		if (lines++ >= con_h || len >= con_w)
 			return lines;
 
-		if (clients->num_classes) {
+		if (iclients->classes.num_engines) {
 			unsigned int i;
 			int width;
 
-			for (i = 0; i < clients->num_classes; i++) {
-				if (clients->engine_class[i].num_engines)
+			for (i = 0; i <= iclients->classes.max_engine_id; i++) {
+				if (iclients->classes.capacity[i])
 					num_active++;
 			}
 
 			*class_w = width = (con_w - len) / num_active;
 
-			for (i = 0; i < clients->num_classes; i++) {
-				const char *name = clients->engine_class[i].name;
+			for (i = 0; i <= iclients->classes.max_engine_id; i++) {
+				const char *name = iclients->classes.names[i];
 				int name_len = strlen(name);
 				int pad = (width - name_len) / 2;
 				int spaces = width - pad - name_len;
 
-				if (!clients->engine_class[i].num_engines)
-					continue; /* Assert in the ideal world. */
+				if (!iclients->classes.capacity[i])
+					continue;
 
 				if (pad < 0 || spaces < 0)
 					continue;
@@ -1725,7 +1738,7 @@ print_clients_header(struct igt_drm_clients *clients, int lines,
 		n_spaces(con_w - len);
 		printf("\033[0m\n");
 	} else {
-		if (clients->num_classes)
+		if (iclients->classes.num_engines)
 			pops->open_struct("clients");
 	}
 
@@ -1740,6 +1753,7 @@ print_client(struct igt_drm_client *c, struct engines *engines, double t, int li
 	     int con_w, int con_h, unsigned int period_us, int *class_w)
 {
 	struct igt_drm_clients *clients = c->clients;
+	struct intel_clients *iclients = clients->private_data;
 	unsigned int i;
 
 	if (output_mode == INTERACTIVE) {
@@ -1750,14 +1764,16 @@ print_client(struct igt_drm_client *c, struct engines *engines, double t, int li
 
 		printf("%6u %17s ", c->pid, c->print_name);
 
-		for (i = 0; c->samples > 1 && i < clients->num_classes; i++) {
+		for (i = 0;
+		     c->samples > 1 && i <= iclients->classes.max_engine_id;
+		     i++) {
 			double pct;
 
-			if (!clients->engine_class[i].num_engines)
-				continue; /* Assert in the ideal world. */
+			if (!iclients->classes.capacity[i])
+				continue;
 
 			pct = (double)c->val[i] / period_us / 1e3 * 100 /
-			      clients->engine_class[i].num_engines;
+			      iclients->classes.capacity[i];
 
 			/*
 			 * Guard against possible time-drift between sampling
@@ -1785,11 +1801,11 @@ print_client(struct igt_drm_client *c, struct engines *engines, double t, int li
 		if (c->samples > 1) {
 			pops->open_struct("engine-classes");
 
-			for (i = 0; i < clients->num_classes; i++) {
+			for (i = 0; i <= iclients->classes.max_engine_id; i++) {
 				double pct;
 
 				snprintf(buf, sizeof(buf), "%s",
-					clients->engine_class[i].name);
+					 iclients->classes.names[i]);
 				pops->open_struct(buf);
 
 				pct = (double)c->val[i] / period_us / 1e3 * 100;
@@ -1818,7 +1834,9 @@ print_clients_footer(struct igt_drm_clients *clients, double t,
 		if (lines++ < con_h)
 			printf("\n");
 	} else {
-		if (clients->num_classes)
+		struct intel_clients *iclients = clients->private_data;
+
+		if (iclients->classes.num_engines)
 			pops->close_struct();
 	}
 
@@ -2018,14 +2036,47 @@ static void show_help_screen(void)
 static bool client_match(const struct igt_drm_clients *clients,
 			 const struct drm_client_fdinfo *info)
 {
+	struct intel_clients *iclients = clients->private_data;
+
 	if (strcmp(info->driver, "i915"))
 		return false;
-	if (strcmp(info->pdev, clients->private_data))
+	if (strcmp(info->pdev, iclients->pci_slot))
 		return false;
 
 	return true;
 }
 
+static void
+intel_init_clients(struct intel_clients *iclients,
+		   const struct igt_device_card *card, struct engines *engines)
+{
+	unsigned int i;
+
+	iclients->pci_slot = strdup(card->pci_slot_name[0] ?
+				    card->pci_slot_name : IGPU_PCI);
+	assert(iclients->pci_slot);
+
+	iclients->classes.num_engines = engines->num_classes;
+	iclients->classes.max_engine_id = engines->num_classes - 1;
+
+	iclients->classes.capacity = calloc(engines->num_classes,
+					    sizeof(*iclients->classes.capacity));
+	assert(iclients->classes.capacity);
+	iclients->classes.names = calloc(engines->num_classes,
+					 sizeof(*iclients->classes.names));
+	assert(iclients->classes.names);
+
+	for (i = 0; i < engines->num_classes; i++) {
+		if (!engines->class[i].num_engines)
+			continue;
+
+		iclients->classes.num_engines++;
+		iclients->classes.max_engine_id = i;
+		iclients->classes.capacity[i] = engines->class[i].num_engines;
+		iclients->classes.names[i] = strdup(engines->class[i].name);
+	}
+}
+
 int main(int argc, char **argv)
 {
 	unsigned int period_us = DEFAULT_PERIOD_MS * 1000;
@@ -2036,6 +2087,7 @@ int main(int argc, char **argv)
 		"video",
 		"video-enhance",
 	};
+	struct intel_clients iclients;
 	int con_w = -1, con_h = -1;
 	char *output_path = NULL;
 	struct engines *engines;
@@ -2146,6 +2198,8 @@ int main(int argc, char **argv)
 	else
 		pmu_device = strdup("i915");
 
+	codename = igt_device_get_pretty_name(&card, false);
+
 	engines = discover_engines(pmu_device);
 	if (!engines) {
 		fprintf(stderr,
@@ -2174,18 +2228,14 @@ int main(int argc, char **argv)
 
 	ret = EXIT_SUCCESS;
 
-	clients = igt_drm_clients_init(strdup(card.pci_slot_name[0] ?
-					      card.pci_slot_name : IGPU_PCI));
 	init_engine_classes(engines);
-	if (clients) {
-		clients->num_classes = engines->num_classes;
-		clients->engine_class = engines->class;
-	}
+
+	intel_init_clients(&iclients, &card, engines);
+	clients = igt_drm_clients_init(&iclients);
 
 	pmu_sample(engines);
 	igt_drm_clients_scan(clients, client_match, engine_map,
 			     ARRAY_SIZE(engine_map));
-	codename = igt_device_get_pretty_name(&card, false);
 
 	while (!stop_top) {
 		struct igt_drm_clients *disp_clients;
-- 
2.32.0

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH i-g-t 10/11] libdrmclient: Enforce client status sort order in the library
  2022-02-22 13:55 ` [Intel-gfx] " Tvrtko Ursulin
  (?)
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  -1 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Some libdrmclient operations require that inactive clients are last in the
list. Rather than relying on callers of the library sort routine to
implement their comparison callbacks correctly, enforce this order
directly in the library and let callers comparison callbacks concern
themselves only with ordering they are interested in.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c | 25 +++++++++++--
 lib/igt_drm_clients.h |  2 +-
 tools/intel_gpu_top.c | 81 +++++++++++++++++++------------------------
 3 files changed, 58 insertions(+), 50 deletions(-)

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
index 6299f3632b74..e8e86969f984 100644
--- a/lib/igt_drm_clients.c
+++ b/lib/igt_drm_clients.c
@@ -185,10 +185,29 @@ void igt_drm_client_free(struct igt_drm_client *c)
 	memset(c, 0, sizeof(*c));
 }
 
+struct sort_context
+{
+	int (*user_cmp)(const void *, const void *, void *);
+};
+
+static int sort_cmp(const void *_a, const void *_b, void *_ctx)
+{
+	const struct sort_context *ctx = _ctx;
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
+	int cmp = b->status - a->status;
+
+	if (cmp == 0)
+		return ctx->user_cmp(_a, _b, _ctx);
+	else
+		return cmp;
+}
+
 struct igt_drm_clients *
 igt_drm_clients_sort(struct igt_drm_clients *clients,
-		     int (*cmp)(const void *, const void *))
+		     int (*cmp)(const void *, const void *, void *))
 {
+	struct sort_context ctx = { .user_cmp = cmp };
 	unsigned int active, free;
 	struct igt_drm_client *c;
 	int tmp;
@@ -196,8 +215,8 @@ igt_drm_clients_sort(struct igt_drm_clients *clients,
 	if (!clients)
 		return clients;
 
-	qsort(clients->client, clients->num_clients, sizeof(*clients->client),
-	      cmp);
+	qsort_r(clients->client, clients->num_clients, sizeof(*clients->client),
+	      sort_cmp, &ctx);
 
 	/* Trim excessive array space. */
 	active = 0;
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
index 4668df2d7fd6..47547aa8b75f 100644
--- a/lib/igt_drm_clients.h
+++ b/lib/igt_drm_clients.h
@@ -90,7 +90,7 @@ igt_drm_clients_find(struct igt_drm_clients *clients,
 
 struct igt_drm_clients *
 igt_drm_clients_sort(struct igt_drm_clients *clients,
-		     int (*cmp)(const void *, const void *));
+		     int (*cmp)(const void *, const void *, void *));
 
 void igt_drm_client_update(struct igt_drm_client *c,
 			   unsigned int pid, char *name, uint64_t val[16]);
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index fb7ec9ae6d74..fcc4e5501d3d 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -644,85 +644,74 @@ static void pmu_sample(struct engines *engines)
 	}
 }
 
-static int client_last_cmp(const void *_a, const void *_b)
+static int
+__client_id_cmp(const struct igt_drm_client *a,
+		const struct igt_drm_client *b)
+{
+	if (a->id > b->id)
+		return 1;
+	else if (a->id < b->id)
+		return -1;
+	else
+		return 0;
+}
+
+static int client_last_cmp(const void *_a, const void *_b, void *unused)
 {
 	const struct igt_drm_client *a = _a;
 	const struct igt_drm_client *b = _b;
-	long tot_a, tot_b;
+	long val_a = a->last_runtime, val_b = b->last_runtime;
 
 	/*
 	 * Sort clients in descending order of runtime in the previous sampling
-	 * period for active ones, followed by inactive. Tie-breaker is client
-	 * id.
+	 * period. Tie-breaker is client id.
 	 */
 
-	tot_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->last_runtime : -1;
-	tot_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->last_runtime : -1;
-
-	tot_b -= tot_a;
-	if (tot_b > 0)
+	if (val_a == val_b)
+		return __client_id_cmp(a, b);
+	else if (val_b > val_a)
 		return 1;
-	if (tot_b < 0)
+	else
 		return -1;
-
-	return (int)b->id - a->id;
 }
 
-static int client_total_cmp(const void *_a, const void *_b)
+static int client_total_cmp(const void *_a, const void *_b, void *unused)
 {
 	const struct igt_drm_client *a = _a;
 	const struct igt_drm_client *b = _b;
-	long tot_a, tot_b;
+	long val_a = a->total_runtime, val_b = b->total_runtime;
 
-	tot_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->total_runtime : -1;
-	tot_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->total_runtime : -1;
-
-	tot_b -= tot_a;
-	if (tot_b > 0)
+	if (val_a == val_b)
+		return __client_id_cmp(a, b);
+	else if (val_b > val_a)
 		return 1;
-	if (tot_b < 0)
+	else
 		return -1;
-
-	return (int)b->id - a->id;
 }
 
-static int client_id_cmp(const void *_a, const void *_b)
+static int client_id_cmp(const void *_a, const void *_b, void *unused)
 {
 	const struct igt_drm_client *a = _a;
 	const struct igt_drm_client *b = _b;
-	int id_a, id_b;
-
-	id_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->id : -1;
-	id_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->id : -1;
-
-	id_b -= id_a;
-	if (id_b > 0)
-		return 1;
-	if (id_b < 0)
-		return -1;
 
-	return (int)b->id - a->id;
+	return __client_id_cmp(a, b);
 }
 
-static int client_pid_cmp(const void *_a, const void *_b)
+static int client_pid_cmp(const void *_a, const void *_b, void *unused)
 {
 	const struct igt_drm_client *a = _a;
 	const struct igt_drm_client *b = _b;
-	int pid_a, pid_b;
-
-	pid_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->pid : INT_MAX;
-	pid_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->pid : INT_MAX;
+	int val_a = a->pid, val_b = b->pid;
 
-	pid_b -= pid_a;
-	if (pid_b > 0)
+	if (val_a == val_b)
+		return __client_id_cmp(a, b);
+	else if (val_b > val_a)
 		return -1;
-	if (pid_b < 0)
+	else
 		return 1;
-
-	return (int)a->id - b->id;
 }
 
-static int (*client_cmp)(const void *, const void *) = client_last_cmp;
+static int (*client_cmp)(const void *, const void *, void *) = client_last_cmp;
 
 static bool aggregate_pids = true;
 
@@ -1910,7 +1899,7 @@ static void interactive_stdin(void)
 static void select_client_sort(void)
 {
 	struct {
-		int (*cmp)(const void *, const void *);
+		int (*cmp)(const void *, const void *, void *);
 		const char *msg;
 	} cmp[] = {
 		{ client_last_cmp, "Sorting clients by current GPU usage." },
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [Intel-gfx] [PATCH i-g-t 10/11] libdrmclient: Enforce client status sort order in the library
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Some libdrmclient operations require that inactive clients are last in the
list. Rather than relying on callers of the library sort routine to
implement their comparison callbacks correctly, enforce this order
directly in the library and let callers comparison callbacks concern
themselves only with ordering they are interested in.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c | 25 +++++++++++--
 lib/igt_drm_clients.h |  2 +-
 tools/intel_gpu_top.c | 81 +++++++++++++++++++------------------------
 3 files changed, 58 insertions(+), 50 deletions(-)

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
index 6299f3632b74..e8e86969f984 100644
--- a/lib/igt_drm_clients.c
+++ b/lib/igt_drm_clients.c
@@ -185,10 +185,29 @@ void igt_drm_client_free(struct igt_drm_client *c)
 	memset(c, 0, sizeof(*c));
 }
 
+struct sort_context
+{
+	int (*user_cmp)(const void *, const void *, void *);
+};
+
+static int sort_cmp(const void *_a, const void *_b, void *_ctx)
+{
+	const struct sort_context *ctx = _ctx;
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
+	int cmp = b->status - a->status;
+
+	if (cmp == 0)
+		return ctx->user_cmp(_a, _b, _ctx);
+	else
+		return cmp;
+}
+
 struct igt_drm_clients *
 igt_drm_clients_sort(struct igt_drm_clients *clients,
-		     int (*cmp)(const void *, const void *))
+		     int (*cmp)(const void *, const void *, void *))
 {
+	struct sort_context ctx = { .user_cmp = cmp };
 	unsigned int active, free;
 	struct igt_drm_client *c;
 	int tmp;
@@ -196,8 +215,8 @@ igt_drm_clients_sort(struct igt_drm_clients *clients,
 	if (!clients)
 		return clients;
 
-	qsort(clients->client, clients->num_clients, sizeof(*clients->client),
-	      cmp);
+	qsort_r(clients->client, clients->num_clients, sizeof(*clients->client),
+	      sort_cmp, &ctx);
 
 	/* Trim excessive array space. */
 	active = 0;
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
index 4668df2d7fd6..47547aa8b75f 100644
--- a/lib/igt_drm_clients.h
+++ b/lib/igt_drm_clients.h
@@ -90,7 +90,7 @@ igt_drm_clients_find(struct igt_drm_clients *clients,
 
 struct igt_drm_clients *
 igt_drm_clients_sort(struct igt_drm_clients *clients,
-		     int (*cmp)(const void *, const void *));
+		     int (*cmp)(const void *, const void *, void *));
 
 void igt_drm_client_update(struct igt_drm_client *c,
 			   unsigned int pid, char *name, uint64_t val[16]);
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index fb7ec9ae6d74..fcc4e5501d3d 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -644,85 +644,74 @@ static void pmu_sample(struct engines *engines)
 	}
 }
 
-static int client_last_cmp(const void *_a, const void *_b)
+static int
+__client_id_cmp(const struct igt_drm_client *a,
+		const struct igt_drm_client *b)
+{
+	if (a->id > b->id)
+		return 1;
+	else if (a->id < b->id)
+		return -1;
+	else
+		return 0;
+}
+
+static int client_last_cmp(const void *_a, const void *_b, void *unused)
 {
 	const struct igt_drm_client *a = _a;
 	const struct igt_drm_client *b = _b;
-	long tot_a, tot_b;
+	long val_a = a->last_runtime, val_b = b->last_runtime;
 
 	/*
 	 * Sort clients in descending order of runtime in the previous sampling
-	 * period for active ones, followed by inactive. Tie-breaker is client
-	 * id.
+	 * period. Tie-breaker is client id.
 	 */
 
-	tot_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->last_runtime : -1;
-	tot_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->last_runtime : -1;
-
-	tot_b -= tot_a;
-	if (tot_b > 0)
+	if (val_a == val_b)
+		return __client_id_cmp(a, b);
+	else if (val_b > val_a)
 		return 1;
-	if (tot_b < 0)
+	else
 		return -1;
-
-	return (int)b->id - a->id;
 }
 
-static int client_total_cmp(const void *_a, const void *_b)
+static int client_total_cmp(const void *_a, const void *_b, void *unused)
 {
 	const struct igt_drm_client *a = _a;
 	const struct igt_drm_client *b = _b;
-	long tot_a, tot_b;
+	long val_a = a->total_runtime, val_b = b->total_runtime;
 
-	tot_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->total_runtime : -1;
-	tot_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->total_runtime : -1;
-
-	tot_b -= tot_a;
-	if (tot_b > 0)
+	if (val_a == val_b)
+		return __client_id_cmp(a, b);
+	else if (val_b > val_a)
 		return 1;
-	if (tot_b < 0)
+	else
 		return -1;
-
-	return (int)b->id - a->id;
 }
 
-static int client_id_cmp(const void *_a, const void *_b)
+static int client_id_cmp(const void *_a, const void *_b, void *unused)
 {
 	const struct igt_drm_client *a = _a;
 	const struct igt_drm_client *b = _b;
-	int id_a, id_b;
-
-	id_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->id : -1;
-	id_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->id : -1;
-
-	id_b -= id_a;
-	if (id_b > 0)
-		return 1;
-	if (id_b < 0)
-		return -1;
 
-	return (int)b->id - a->id;
+	return __client_id_cmp(a, b);
 }
 
-static int client_pid_cmp(const void *_a, const void *_b)
+static int client_pid_cmp(const void *_a, const void *_b, void *unused)
 {
 	const struct igt_drm_client *a = _a;
 	const struct igt_drm_client *b = _b;
-	int pid_a, pid_b;
-
-	pid_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->pid : INT_MAX;
-	pid_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->pid : INT_MAX;
+	int val_a = a->pid, val_b = b->pid;
 
-	pid_b -= pid_a;
-	if (pid_b > 0)
+	if (val_a == val_b)
+		return __client_id_cmp(a, b);
+	else if (val_b > val_a)
 		return -1;
-	if (pid_b < 0)
+	else
 		return 1;
-
-	return (int)a->id - b->id;
 }
 
-static int (*client_cmp)(const void *, const void *) = client_last_cmp;
+static int (*client_cmp)(const void *, const void *, void *) = client_last_cmp;
 
 static bool aggregate_pids = true;
 
@@ -1910,7 +1899,7 @@ static void interactive_stdin(void)
 static void select_client_sort(void)
 {
 	struct {
-		int (*cmp)(const void *, const void *);
+		int (*cmp)(const void *, const void *, void *);
 		const char *msg;
 	} cmp[] = {
 		{ client_last_cmp, "Sorting clients by current GPU usage." },
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [igt-dev] [PATCH i-g-t 10/11] libdrmclient: Enforce client status sort order in the library
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Some libdrmclient operations require that inactive clients are last in the
list. Rather than relying on callers of the library sort routine to
implement their comparison callbacks correctly, enforce this order
directly in the library and let callers comparison callbacks concern
themselves only with ordering they are interested in.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 lib/igt_drm_clients.c | 25 +++++++++++--
 lib/igt_drm_clients.h |  2 +-
 tools/intel_gpu_top.c | 81 +++++++++++++++++++------------------------
 3 files changed, 58 insertions(+), 50 deletions(-)

diff --git a/lib/igt_drm_clients.c b/lib/igt_drm_clients.c
index 6299f3632b74..e8e86969f984 100644
--- a/lib/igt_drm_clients.c
+++ b/lib/igt_drm_clients.c
@@ -185,10 +185,29 @@ void igt_drm_client_free(struct igt_drm_client *c)
 	memset(c, 0, sizeof(*c));
 }
 
+struct sort_context
+{
+	int (*user_cmp)(const void *, const void *, void *);
+};
+
+static int sort_cmp(const void *_a, const void *_b, void *_ctx)
+{
+	const struct sort_context *ctx = _ctx;
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
+	int cmp = b->status - a->status;
+
+	if (cmp == 0)
+		return ctx->user_cmp(_a, _b, _ctx);
+	else
+		return cmp;
+}
+
 struct igt_drm_clients *
 igt_drm_clients_sort(struct igt_drm_clients *clients,
-		     int (*cmp)(const void *, const void *))
+		     int (*cmp)(const void *, const void *, void *))
 {
+	struct sort_context ctx = { .user_cmp = cmp };
 	unsigned int active, free;
 	struct igt_drm_client *c;
 	int tmp;
@@ -196,8 +215,8 @@ igt_drm_clients_sort(struct igt_drm_clients *clients,
 	if (!clients)
 		return clients;
 
-	qsort(clients->client, clients->num_clients, sizeof(*clients->client),
-	      cmp);
+	qsort_r(clients->client, clients->num_clients, sizeof(*clients->client),
+	      sort_cmp, &ctx);
 
 	/* Trim excessive array space. */
 	active = 0;
diff --git a/lib/igt_drm_clients.h b/lib/igt_drm_clients.h
index 4668df2d7fd6..47547aa8b75f 100644
--- a/lib/igt_drm_clients.h
+++ b/lib/igt_drm_clients.h
@@ -90,7 +90,7 @@ igt_drm_clients_find(struct igt_drm_clients *clients,
 
 struct igt_drm_clients *
 igt_drm_clients_sort(struct igt_drm_clients *clients,
-		     int (*cmp)(const void *, const void *));
+		     int (*cmp)(const void *, const void *, void *));
 
 void igt_drm_client_update(struct igt_drm_client *c,
 			   unsigned int pid, char *name, uint64_t val[16]);
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index fb7ec9ae6d74..fcc4e5501d3d 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -644,85 +644,74 @@ static void pmu_sample(struct engines *engines)
 	}
 }
 
-static int client_last_cmp(const void *_a, const void *_b)
+static int
+__client_id_cmp(const struct igt_drm_client *a,
+		const struct igt_drm_client *b)
+{
+	if (a->id > b->id)
+		return 1;
+	else if (a->id < b->id)
+		return -1;
+	else
+		return 0;
+}
+
+static int client_last_cmp(const void *_a, const void *_b, void *unused)
 {
 	const struct igt_drm_client *a = _a;
 	const struct igt_drm_client *b = _b;
-	long tot_a, tot_b;
+	long val_a = a->last_runtime, val_b = b->last_runtime;
 
 	/*
 	 * Sort clients in descending order of runtime in the previous sampling
-	 * period for active ones, followed by inactive. Tie-breaker is client
-	 * id.
+	 * period. Tie-breaker is client id.
 	 */
 
-	tot_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->last_runtime : -1;
-	tot_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->last_runtime : -1;
-
-	tot_b -= tot_a;
-	if (tot_b > 0)
+	if (val_a == val_b)
+		return __client_id_cmp(a, b);
+	else if (val_b > val_a)
 		return 1;
-	if (tot_b < 0)
+	else
 		return -1;
-
-	return (int)b->id - a->id;
 }
 
-static int client_total_cmp(const void *_a, const void *_b)
+static int client_total_cmp(const void *_a, const void *_b, void *unused)
 {
 	const struct igt_drm_client *a = _a;
 	const struct igt_drm_client *b = _b;
-	long tot_a, tot_b;
+	long val_a = a->total_runtime, val_b = b->total_runtime;
 
-	tot_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->total_runtime : -1;
-	tot_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->total_runtime : -1;
-
-	tot_b -= tot_a;
-	if (tot_b > 0)
+	if (val_a == val_b)
+		return __client_id_cmp(a, b);
+	else if (val_b > val_a)
 		return 1;
-	if (tot_b < 0)
+	else
 		return -1;
-
-	return (int)b->id - a->id;
 }
 
-static int client_id_cmp(const void *_a, const void *_b)
+static int client_id_cmp(const void *_a, const void *_b, void *unused)
 {
 	const struct igt_drm_client *a = _a;
 	const struct igt_drm_client *b = _b;
-	int id_a, id_b;
-
-	id_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->id : -1;
-	id_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->id : -1;
-
-	id_b -= id_a;
-	if (id_b > 0)
-		return 1;
-	if (id_b < 0)
-		return -1;
 
-	return (int)b->id - a->id;
+	return __client_id_cmp(a, b);
 }
 
-static int client_pid_cmp(const void *_a, const void *_b)
+static int client_pid_cmp(const void *_a, const void *_b, void *unused)
 {
 	const struct igt_drm_client *a = _a;
 	const struct igt_drm_client *b = _b;
-	int pid_a, pid_b;
-
-	pid_a = a->status == IGT_DRM_CLIENT_ALIVE ? a->pid : INT_MAX;
-	pid_b = b->status == IGT_DRM_CLIENT_ALIVE ? b->pid : INT_MAX;
+	int val_a = a->pid, val_b = b->pid;
 
-	pid_b -= pid_a;
-	if (pid_b > 0)
+	if (val_a == val_b)
+		return __client_id_cmp(a, b);
+	else if (val_b > val_a)
 		return -1;
-	if (pid_b < 0)
+	else
 		return 1;
-
-	return (int)a->id - b->id;
 }
 
-static int (*client_cmp)(const void *, const void *) = client_last_cmp;
+static int (*client_cmp)(const void *, const void *, void *) = client_last_cmp;
 
 static bool aggregate_pids = true;
 
@@ -1910,7 +1899,7 @@ static void interactive_stdin(void)
 static void select_client_sort(void)
 {
 	struct {
-		int (*cmp)(const void *, const void *);
+		int (*cmp)(const void *, const void *, void *);
 		const char *msg;
 	} cmp[] = {
 		{ client_last_cmp, "Sorting clients by current GPU usage." },
-- 
2.32.0

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH i-g-t 11/11] gputop: Basic vendor agnostic GPU top tool
  2022-02-22 13:55 ` [Intel-gfx] " Tvrtko Ursulin
  (?)
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  -1 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Rob Clark, Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Rudimentary vendor agnostic example of how lib_igt_drm_clients can be used
to display a sorted by card and usage list of processes using GPUs.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
Cc: Rob Clark <robdclark@chromium.org>
---
 tools/gputop.c    | 276 ++++++++++++++++++++++++++++++++++++++++++++++
 tools/meson.build |   5 +
 2 files changed, 281 insertions(+)
 create mode 100644 tools/gputop.c

diff --git a/tools/gputop.c b/tools/gputop.c
new file mode 100644
index 000000000000..dbc5b7b4ce42
--- /dev/null
+++ b/tools/gputop.c
@@ -0,0 +1,276 @@
+/*
+ * 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 <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <math.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <termios.h>
+#include <sys/sysmacros.h>
+#include <stdbool.h>
+
+#include "igt_drm_clients.h"
+#include "igt_drm_fdinfo.h"
+
+#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
+
+static const char *bars[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" };
+
+static void n_spaces(const unsigned int n)
+{
+	unsigned int i;
+
+	for (i = 0; i < n; i++)
+		putchar(' ');
+}
+
+static void print_percentage_bar(double percent, int max_len)
+{
+	int bar_len, i, len = max_len - 2;
+	const int w = 8;
+
+	assert(max_len > 0);
+
+	bar_len = ceil(w * percent * len / 100.0);
+	if (bar_len > w * len)
+		bar_len = w * len;
+
+	putchar('|');
+
+	for (i = bar_len; i >= w; i -= w)
+		printf("%s", bars[w]);
+	if (i)
+		printf("%s", bars[i]);
+
+	len -= (bar_len + (w - 1)) / w;
+	n_spaces(len);
+
+	putchar('|');
+}
+
+static int
+print_client_header(struct igt_drm_client *c, int lines, int con_w, int con_h,
+		    int *engine_w)
+{
+	const char *pidname = "   PID              NAME ";
+	int ret, len = strlen(pidname);
+
+	if (lines++ >= con_h || len >= con_w)
+		return lines;
+	printf("\033[7m");
+	ret = printf("DRM minor %u", c->drm_minor);
+	n_spaces(con_w - ret);
+
+	if (lines++ >= con_h)
+		return lines;
+	printf("\n%s", pidname);
+
+	if (c->engines->num_engines) {
+		unsigned int i;
+		int width;
+
+		*engine_w = width = (con_w - len) / c->engines->num_engines;
+
+		for (i = 0; i <= c->engines->max_engine_id; i++) {
+			const char *name = c->engines->names[i];
+			int name_len = strlen(name);
+			int pad = (width - name_len) / 2;
+			int spaces = width - pad - name_len;
+
+			if (!name)
+				continue;
+
+			if (pad < 0 || spaces < 0)
+				continue;
+
+			n_spaces(pad);
+			printf("%s", name);
+			n_spaces(spaces);
+			len += pad + name_len + spaces;
+		}
+	}
+
+	n_spaces(con_w - len);
+	printf("\033[0m\n");
+
+	return lines;
+}
+
+
+static bool
+newheader(const struct igt_drm_client *c, const struct igt_drm_client *pc)
+{
+	return !pc || c->drm_minor != pc->drm_minor;
+}
+
+static bool filter_idle = true;
+
+static int
+print_client(struct igt_drm_client *c, struct igt_drm_client **prevc,
+	     double t, int lines, int con_w, int con_h,
+	     unsigned int period_us, int *engine_w)
+{
+	unsigned int i;
+
+	if (filter_idle && (!c->total_runtime || c->samples < 2))
+		return lines;
+
+	if (newheader(c, *prevc)) {
+		lines = print_client_header(c, lines, con_w, con_h, engine_w);
+		if (lines >= con_h)
+			return lines;
+	}
+
+	*prevc = c;
+
+	printf("%6u %17s ", c->pid, c->print_name);
+	lines++;
+
+	for (i = 0; c->samples > 1 && i <= c->engines->max_engine_id; i++) {
+		double pct;
+
+		if (!c->engines->capacity[i])
+			continue;
+
+		pct = (double)c->val[i] / period_us / 1e3 * 100 /
+		      c->engines->capacity[i];
+
+		/*
+		 * Guard against possible time-drift between sampling
+		 * client data and time we obtained our time-delta from
+		 * PMU.
+		 */
+		if (pct > 100.0)
+			pct = 100.0;
+
+		print_percentage_bar(pct, *engine_w);
+	}
+
+	putchar('\n');
+
+	return lines;
+}
+
+static int
+__client_id_cmp(const struct igt_drm_client *a,
+		const struct igt_drm_client *b)
+{
+	if (a->id > b->id)
+		return 1;
+	else if (a->id < b->id)
+		return -1;
+	else
+		return 0;
+}
+
+static int client_cmp(const void *_a, const void *_b, void *unused)
+{
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
+	long val_a, val_b;
+
+	val_a = a->drm_minor;
+	val_b = b->drm_minor;
+	if (val_a > val_b)
+		return 1;
+	else if (val_b > val_a)
+		return -1;
+
+	val_a = a->last_runtime;
+	val_b = b->last_runtime;
+	if (val_a == val_b)
+		return __client_id_cmp(a, b);
+	else if (val_b > val_a)
+		return 1;
+	else
+		return -1;
+
+}
+
+int main(int argc, char **argv)
+{
+	unsigned int period_us = 2e6;
+	struct igt_drm_clients *clients = NULL;
+	int con_w = -1, con_h = -1;
+
+	clients = igt_drm_clients_init(NULL);
+	if (!clients)
+		exit(1);
+
+	igt_drm_clients_scan(clients, NULL, NULL, 0);
+
+	for (;;) {
+		struct igt_drm_client *c, *prevc = NULL;
+		int i, engine_w = 0, lines = 0;
+		struct winsize ws;
+
+		if (ioctl(0, TIOCGWINSZ, &ws) != -1) {
+			con_w = ws.ws_col;
+			con_h = ws.ws_row;
+			if (con_w == 0 && con_h == 0) {
+				/* Serial console. */
+				con_w = 80;
+				con_h = 24;
+			}
+		}
+
+		igt_drm_clients_scan(clients, NULL, NULL, 0);
+		igt_drm_clients_sort(clients, client_cmp);
+
+		printf("\033[H\033[J");
+
+		igt_for_each_drm_client(clients, c, i) {
+			assert(c->status != IGT_DRM_CLIENT_PROBE);
+			if (c->status != IGT_DRM_CLIENT_ALIVE)
+				break; /* Active clients are first in the array. */
+
+			lines = print_client(c, &prevc, (double)period_us / 1e6,
+					     lines, con_w, con_h, period_us,
+					     &engine_w);
+			if (lines >= con_h)
+				break;
+		}
+
+		if (lines++ < con_h)
+			printf("\n");
+
+		usleep(period_us);
+	}
+
+	return 0;
+}
diff --git a/tools/meson.build b/tools/meson.build
index e65aceeef9fa..508964004bb0 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -69,6 +69,11 @@ if libudev.found()
 		   install : true)
 endif
 
+executable('gputop', 'gputop.c',
+           install : true,
+           install_rpath : bindir_rpathdir,
+           dependencies : [lib_igt_drm_clients,lib_igt_drm_fdinfo,math])
+
 intel_l3_parity_src = [ 'intel_l3_parity.c', 'intel_l3_udev_listener.c' ]
 executable('intel_l3_parity', sources : intel_l3_parity_src,
 	   dependencies : tool_deps,
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [Intel-gfx] [PATCH i-g-t 11/11] gputop: Basic vendor agnostic GPU top tool
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Rob Clark, Intel-gfx, dri-devel

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Rudimentary vendor agnostic example of how lib_igt_drm_clients can be used
to display a sorted by card and usage list of processes using GPUs.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
Cc: Rob Clark <robdclark@chromium.org>
---
 tools/gputop.c    | 276 ++++++++++++++++++++++++++++++++++++++++++++++
 tools/meson.build |   5 +
 2 files changed, 281 insertions(+)
 create mode 100644 tools/gputop.c

diff --git a/tools/gputop.c b/tools/gputop.c
new file mode 100644
index 000000000000..dbc5b7b4ce42
--- /dev/null
+++ b/tools/gputop.c
@@ -0,0 +1,276 @@
+/*
+ * 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 <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <math.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <termios.h>
+#include <sys/sysmacros.h>
+#include <stdbool.h>
+
+#include "igt_drm_clients.h"
+#include "igt_drm_fdinfo.h"
+
+#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
+
+static const char *bars[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" };
+
+static void n_spaces(const unsigned int n)
+{
+	unsigned int i;
+
+	for (i = 0; i < n; i++)
+		putchar(' ');
+}
+
+static void print_percentage_bar(double percent, int max_len)
+{
+	int bar_len, i, len = max_len - 2;
+	const int w = 8;
+
+	assert(max_len > 0);
+
+	bar_len = ceil(w * percent * len / 100.0);
+	if (bar_len > w * len)
+		bar_len = w * len;
+
+	putchar('|');
+
+	for (i = bar_len; i >= w; i -= w)
+		printf("%s", bars[w]);
+	if (i)
+		printf("%s", bars[i]);
+
+	len -= (bar_len + (w - 1)) / w;
+	n_spaces(len);
+
+	putchar('|');
+}
+
+static int
+print_client_header(struct igt_drm_client *c, int lines, int con_w, int con_h,
+		    int *engine_w)
+{
+	const char *pidname = "   PID              NAME ";
+	int ret, len = strlen(pidname);
+
+	if (lines++ >= con_h || len >= con_w)
+		return lines;
+	printf("\033[7m");
+	ret = printf("DRM minor %u", c->drm_minor);
+	n_spaces(con_w - ret);
+
+	if (lines++ >= con_h)
+		return lines;
+	printf("\n%s", pidname);
+
+	if (c->engines->num_engines) {
+		unsigned int i;
+		int width;
+
+		*engine_w = width = (con_w - len) / c->engines->num_engines;
+
+		for (i = 0; i <= c->engines->max_engine_id; i++) {
+			const char *name = c->engines->names[i];
+			int name_len = strlen(name);
+			int pad = (width - name_len) / 2;
+			int spaces = width - pad - name_len;
+
+			if (!name)
+				continue;
+
+			if (pad < 0 || spaces < 0)
+				continue;
+
+			n_spaces(pad);
+			printf("%s", name);
+			n_spaces(spaces);
+			len += pad + name_len + spaces;
+		}
+	}
+
+	n_spaces(con_w - len);
+	printf("\033[0m\n");
+
+	return lines;
+}
+
+
+static bool
+newheader(const struct igt_drm_client *c, const struct igt_drm_client *pc)
+{
+	return !pc || c->drm_minor != pc->drm_minor;
+}
+
+static bool filter_idle = true;
+
+static int
+print_client(struct igt_drm_client *c, struct igt_drm_client **prevc,
+	     double t, int lines, int con_w, int con_h,
+	     unsigned int period_us, int *engine_w)
+{
+	unsigned int i;
+
+	if (filter_idle && (!c->total_runtime || c->samples < 2))
+		return lines;
+
+	if (newheader(c, *prevc)) {
+		lines = print_client_header(c, lines, con_w, con_h, engine_w);
+		if (lines >= con_h)
+			return lines;
+	}
+
+	*prevc = c;
+
+	printf("%6u %17s ", c->pid, c->print_name);
+	lines++;
+
+	for (i = 0; c->samples > 1 && i <= c->engines->max_engine_id; i++) {
+		double pct;
+
+		if (!c->engines->capacity[i])
+			continue;
+
+		pct = (double)c->val[i] / period_us / 1e3 * 100 /
+		      c->engines->capacity[i];
+
+		/*
+		 * Guard against possible time-drift between sampling
+		 * client data and time we obtained our time-delta from
+		 * PMU.
+		 */
+		if (pct > 100.0)
+			pct = 100.0;
+
+		print_percentage_bar(pct, *engine_w);
+	}
+
+	putchar('\n');
+
+	return lines;
+}
+
+static int
+__client_id_cmp(const struct igt_drm_client *a,
+		const struct igt_drm_client *b)
+{
+	if (a->id > b->id)
+		return 1;
+	else if (a->id < b->id)
+		return -1;
+	else
+		return 0;
+}
+
+static int client_cmp(const void *_a, const void *_b, void *unused)
+{
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
+	long val_a, val_b;
+
+	val_a = a->drm_minor;
+	val_b = b->drm_minor;
+	if (val_a > val_b)
+		return 1;
+	else if (val_b > val_a)
+		return -1;
+
+	val_a = a->last_runtime;
+	val_b = b->last_runtime;
+	if (val_a == val_b)
+		return __client_id_cmp(a, b);
+	else if (val_b > val_a)
+		return 1;
+	else
+		return -1;
+
+}
+
+int main(int argc, char **argv)
+{
+	unsigned int period_us = 2e6;
+	struct igt_drm_clients *clients = NULL;
+	int con_w = -1, con_h = -1;
+
+	clients = igt_drm_clients_init(NULL);
+	if (!clients)
+		exit(1);
+
+	igt_drm_clients_scan(clients, NULL, NULL, 0);
+
+	for (;;) {
+		struct igt_drm_client *c, *prevc = NULL;
+		int i, engine_w = 0, lines = 0;
+		struct winsize ws;
+
+		if (ioctl(0, TIOCGWINSZ, &ws) != -1) {
+			con_w = ws.ws_col;
+			con_h = ws.ws_row;
+			if (con_w == 0 && con_h == 0) {
+				/* Serial console. */
+				con_w = 80;
+				con_h = 24;
+			}
+		}
+
+		igt_drm_clients_scan(clients, NULL, NULL, 0);
+		igt_drm_clients_sort(clients, client_cmp);
+
+		printf("\033[H\033[J");
+
+		igt_for_each_drm_client(clients, c, i) {
+			assert(c->status != IGT_DRM_CLIENT_PROBE);
+			if (c->status != IGT_DRM_CLIENT_ALIVE)
+				break; /* Active clients are first in the array. */
+
+			lines = print_client(c, &prevc, (double)period_us / 1e6,
+					     lines, con_w, con_h, period_us,
+					     &engine_w);
+			if (lines >= con_h)
+				break;
+		}
+
+		if (lines++ < con_h)
+			printf("\n");
+
+		usleep(period_us);
+	}
+
+	return 0;
+}
diff --git a/tools/meson.build b/tools/meson.build
index e65aceeef9fa..508964004bb0 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -69,6 +69,11 @@ if libudev.found()
 		   install : true)
 endif
 
+executable('gputop', 'gputop.c',
+           install : true,
+           install_rpath : bindir_rpathdir,
+           dependencies : [lib_igt_drm_clients,lib_igt_drm_fdinfo,math])
+
 intel_l3_parity_src = [ 'intel_l3_parity.c', 'intel_l3_udev_listener.c' ]
 executable('intel_l3_parity', sources : intel_l3_parity_src,
 	   dependencies : tool_deps,
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [igt-dev] [PATCH i-g-t 11/11] gputop: Basic vendor agnostic GPU top tool
@ 2022-02-22 13:56   ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-02-22 13:56 UTC (permalink / raw)
  To: igt-dev; +Cc: Rob Clark, Intel-gfx, dri-devel, Tvrtko Ursulin

From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Rudimentary vendor agnostic example of how lib_igt_drm_clients can be used
to display a sorted by card and usage list of processes using GPUs.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
Cc: Rob Clark <robdclark@chromium.org>
---
 tools/gputop.c    | 276 ++++++++++++++++++++++++++++++++++++++++++++++
 tools/meson.build |   5 +
 2 files changed, 281 insertions(+)
 create mode 100644 tools/gputop.c

diff --git a/tools/gputop.c b/tools/gputop.c
new file mode 100644
index 000000000000..dbc5b7b4ce42
--- /dev/null
+++ b/tools/gputop.c
@@ -0,0 +1,276 @@
+/*
+ * 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 <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <math.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <termios.h>
+#include <sys/sysmacros.h>
+#include <stdbool.h>
+
+#include "igt_drm_clients.h"
+#include "igt_drm_fdinfo.h"
+
+#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
+
+static const char *bars[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" };
+
+static void n_spaces(const unsigned int n)
+{
+	unsigned int i;
+
+	for (i = 0; i < n; i++)
+		putchar(' ');
+}
+
+static void print_percentage_bar(double percent, int max_len)
+{
+	int bar_len, i, len = max_len - 2;
+	const int w = 8;
+
+	assert(max_len > 0);
+
+	bar_len = ceil(w * percent * len / 100.0);
+	if (bar_len > w * len)
+		bar_len = w * len;
+
+	putchar('|');
+
+	for (i = bar_len; i >= w; i -= w)
+		printf("%s", bars[w]);
+	if (i)
+		printf("%s", bars[i]);
+
+	len -= (bar_len + (w - 1)) / w;
+	n_spaces(len);
+
+	putchar('|');
+}
+
+static int
+print_client_header(struct igt_drm_client *c, int lines, int con_w, int con_h,
+		    int *engine_w)
+{
+	const char *pidname = "   PID              NAME ";
+	int ret, len = strlen(pidname);
+
+	if (lines++ >= con_h || len >= con_w)
+		return lines;
+	printf("\033[7m");
+	ret = printf("DRM minor %u", c->drm_minor);
+	n_spaces(con_w - ret);
+
+	if (lines++ >= con_h)
+		return lines;
+	printf("\n%s", pidname);
+
+	if (c->engines->num_engines) {
+		unsigned int i;
+		int width;
+
+		*engine_w = width = (con_w - len) / c->engines->num_engines;
+
+		for (i = 0; i <= c->engines->max_engine_id; i++) {
+			const char *name = c->engines->names[i];
+			int name_len = strlen(name);
+			int pad = (width - name_len) / 2;
+			int spaces = width - pad - name_len;
+
+			if (!name)
+				continue;
+
+			if (pad < 0 || spaces < 0)
+				continue;
+
+			n_spaces(pad);
+			printf("%s", name);
+			n_spaces(spaces);
+			len += pad + name_len + spaces;
+		}
+	}
+
+	n_spaces(con_w - len);
+	printf("\033[0m\n");
+
+	return lines;
+}
+
+
+static bool
+newheader(const struct igt_drm_client *c, const struct igt_drm_client *pc)
+{
+	return !pc || c->drm_minor != pc->drm_minor;
+}
+
+static bool filter_idle = true;
+
+static int
+print_client(struct igt_drm_client *c, struct igt_drm_client **prevc,
+	     double t, int lines, int con_w, int con_h,
+	     unsigned int period_us, int *engine_w)
+{
+	unsigned int i;
+
+	if (filter_idle && (!c->total_runtime || c->samples < 2))
+		return lines;
+
+	if (newheader(c, *prevc)) {
+		lines = print_client_header(c, lines, con_w, con_h, engine_w);
+		if (lines >= con_h)
+			return lines;
+	}
+
+	*prevc = c;
+
+	printf("%6u %17s ", c->pid, c->print_name);
+	lines++;
+
+	for (i = 0; c->samples > 1 && i <= c->engines->max_engine_id; i++) {
+		double pct;
+
+		if (!c->engines->capacity[i])
+			continue;
+
+		pct = (double)c->val[i] / period_us / 1e3 * 100 /
+		      c->engines->capacity[i];
+
+		/*
+		 * Guard against possible time-drift between sampling
+		 * client data and time we obtained our time-delta from
+		 * PMU.
+		 */
+		if (pct > 100.0)
+			pct = 100.0;
+
+		print_percentage_bar(pct, *engine_w);
+	}
+
+	putchar('\n');
+
+	return lines;
+}
+
+static int
+__client_id_cmp(const struct igt_drm_client *a,
+		const struct igt_drm_client *b)
+{
+	if (a->id > b->id)
+		return 1;
+	else if (a->id < b->id)
+		return -1;
+	else
+		return 0;
+}
+
+static int client_cmp(const void *_a, const void *_b, void *unused)
+{
+	const struct igt_drm_client *a = _a;
+	const struct igt_drm_client *b = _b;
+	long val_a, val_b;
+
+	val_a = a->drm_minor;
+	val_b = b->drm_minor;
+	if (val_a > val_b)
+		return 1;
+	else if (val_b > val_a)
+		return -1;
+
+	val_a = a->last_runtime;
+	val_b = b->last_runtime;
+	if (val_a == val_b)
+		return __client_id_cmp(a, b);
+	else if (val_b > val_a)
+		return 1;
+	else
+		return -1;
+
+}
+
+int main(int argc, char **argv)
+{
+	unsigned int period_us = 2e6;
+	struct igt_drm_clients *clients = NULL;
+	int con_w = -1, con_h = -1;
+
+	clients = igt_drm_clients_init(NULL);
+	if (!clients)
+		exit(1);
+
+	igt_drm_clients_scan(clients, NULL, NULL, 0);
+
+	for (;;) {
+		struct igt_drm_client *c, *prevc = NULL;
+		int i, engine_w = 0, lines = 0;
+		struct winsize ws;
+
+		if (ioctl(0, TIOCGWINSZ, &ws) != -1) {
+			con_w = ws.ws_col;
+			con_h = ws.ws_row;
+			if (con_w == 0 && con_h == 0) {
+				/* Serial console. */
+				con_w = 80;
+				con_h = 24;
+			}
+		}
+
+		igt_drm_clients_scan(clients, NULL, NULL, 0);
+		igt_drm_clients_sort(clients, client_cmp);
+
+		printf("\033[H\033[J");
+
+		igt_for_each_drm_client(clients, c, i) {
+			assert(c->status != IGT_DRM_CLIENT_PROBE);
+			if (c->status != IGT_DRM_CLIENT_ALIVE)
+				break; /* Active clients are first in the array. */
+
+			lines = print_client(c, &prevc, (double)period_us / 1e6,
+					     lines, con_w, con_h, period_us,
+					     &engine_w);
+			if (lines >= con_h)
+				break;
+		}
+
+		if (lines++ < con_h)
+			printf("\n");
+
+		usleep(period_us);
+	}
+
+	return 0;
+}
diff --git a/tools/meson.build b/tools/meson.build
index e65aceeef9fa..508964004bb0 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -69,6 +69,11 @@ if libudev.found()
 		   install : true)
 endif
 
+executable('gputop', 'gputop.c',
+           install : true,
+           install_rpath : bindir_rpathdir,
+           dependencies : [lib_igt_drm_clients,lib_igt_drm_fdinfo,math])
+
 intel_l3_parity_src = [ 'intel_l3_parity.c', 'intel_l3_udev_listener.c' ]
 executable('intel_l3_parity', sources : intel_l3_parity_src,
 	   dependencies : tool_deps,
-- 
2.32.0

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [igt-dev] ✓ Fi.CI.BAT: success for Per client GPU utilisation
  2022-02-22 13:55 ` [Intel-gfx] " Tvrtko Ursulin
                   ` (12 preceding siblings ...)
  (?)
@ 2022-02-22 17:06 ` Patchwork
  -1 siblings, 0 replies; 50+ messages in thread
From: Patchwork @ 2022-02-22 17:06 UTC (permalink / raw)
  To: Tvrtko Ursulin; +Cc: igt-dev

[-- Attachment #1: Type: text/plain, Size: 4746 bytes --]

== Series Details ==

Series: Per client GPU utilisation
URL   : https://patchwork.freedesktop.org/series/100571/
State : success

== Summary ==

CI Bug Log - changes from CI_DRM_11266 -> IGTPW_6676
====================================================

Summary
-------

  **SUCCESS**

  No regressions found.

  External URL: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/index.html

Participating hosts (45 -> 43)
------------------------------

  Additional (1): fi-pnv-d510 
  Missing    (3): fi-ctg-p8600 fi-bsw-cyan fi-hsw-4200u 

Known issues
------------

  Here are the changes found in IGTPW_6676 that come from known issues:

### IGT changes ###

#### Issues hit ####

  * igt@gem_flink_basic@bad-flink:
    - fi-skl-6600u:       [PASS][1] -> [FAIL][2] ([i915#4547])
   [1]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_11266/fi-skl-6600u/igt@gem_flink_basic@bad-flink.html
   [2]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/fi-skl-6600u/igt@gem_flink_basic@bad-flink.html

  * igt@i915_selftest@live@hangcheck:
    - bat-dg1-6:          [PASS][3] -> [DMESG-FAIL][4] ([i915#4494] / [i915#4957])
   [3]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_11266/bat-dg1-6/igt@i915_selftest@live@hangcheck.html
   [4]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/bat-dg1-6/igt@i915_selftest@live@hangcheck.html

  * igt@kms_frontbuffer_tracking@basic:
    - fi-cml-u2:          [PASS][5] -> [DMESG-WARN][6] ([i915#4269])
   [5]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_11266/fi-cml-u2/igt@kms_frontbuffer_tracking@basic.html
   [6]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/fi-cml-u2/igt@kms_frontbuffer_tracking@basic.html

  * igt@prime_vgem@basic-userptr:
    - fi-pnv-d510:        NOTRUN -> [SKIP][7] ([fdo#109271]) +57 similar issues
   [7]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/fi-pnv-d510/igt@prime_vgem@basic-userptr.html

  * igt@runner@aborted:
    - fi-bdw-5557u:       NOTRUN -> [FAIL][8] ([i915#2426] / [i915#4312])
   [8]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/fi-bdw-5557u/igt@runner@aborted.html

  
#### Possible fixes ####

  * igt@i915_module_load@reload:
    - fi-bsw-kefka:       [DMESG-WARN][9] ([i915#1982]) -> [PASS][10]
   [9]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_11266/fi-bsw-kefka/igt@i915_module_load@reload.html
   [10]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/fi-bsw-kefka/igt@i915_module_load@reload.html

  * igt@kms_pipe_crc_basic@compare-crc-sanitycheck-pipe-b:
    - fi-cfl-8109u:       [DMESG-WARN][11] ([i915#295]) -> [PASS][12] +12 similar issues
   [11]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_11266/fi-cfl-8109u/igt@kms_pipe_crc_basic@compare-crc-sanitycheck-pipe-b.html
   [12]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/fi-cfl-8109u/igt@kms_pipe_crc_basic@compare-crc-sanitycheck-pipe-b.html

  
#### Warnings ####

  * igt@i915_selftest@live@hangcheck:
    - bat-dg1-5:          [DMESG-FAIL][13] ([i915#4494] / [i915#4957]) -> [DMESG-FAIL][14] ([i915#4957])
   [13]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_11266/bat-dg1-5/igt@i915_selftest@live@hangcheck.html
   [14]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/bat-dg1-5/igt@i915_selftest@live@hangcheck.html

  
  [fdo#109271]: https://bugs.freedesktop.org/show_bug.cgi?id=109271
  [i915#1982]: https://gitlab.freedesktop.org/drm/intel/issues/1982
  [i915#2426]: https://gitlab.freedesktop.org/drm/intel/issues/2426
  [i915#295]: https://gitlab.freedesktop.org/drm/intel/issues/295
  [i915#4269]: https://gitlab.freedesktop.org/drm/intel/issues/4269
  [i915#4312]: https://gitlab.freedesktop.org/drm/intel/issues/4312
  [i915#4494]: https://gitlab.freedesktop.org/drm/intel/issues/4494
  [i915#4547]: https://gitlab.freedesktop.org/drm/intel/issues/4547
  [i915#4957]: https://gitlab.freedesktop.org/drm/intel/issues/4957


Build changes
-------------

  * CI: CI-20190529 -> None
  * IGT: IGT_6351 -> IGTPW_6676

  CI-20190529: 20190529
  CI_DRM_11266: 62ca4a90fa28434483476be736d67371671169e4 @ git://anongit.freedesktop.org/gfx-ci/linux
  IGTPW_6676: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/index.html
  IGT_6351: 5f3cfa485eb46902e55c6b96c80dc8c57ddf3b43 @ https://gitlab.freedesktop.org/drm/igt-gpu-tools.git



== Testlist changes ==

+igt@drm_fdinfo@all-busy-check-all
+igt@drm_fdinfo@all-busy-idle-check-all
+igt@drm_fdinfo@basics
+igt@drm_fdinfo@busy
+igt@drm_fdinfo@busy-check-all
+igt@drm_fdinfo@busy-hang
+igt@drm_fdinfo@busy-idle
+igt@drm_fdinfo@busy-idle-check-all
+igt@drm_fdinfo@idle
+igt@drm_fdinfo@isolation
+igt@drm_fdinfo@most-busy-check-all
+igt@drm_fdinfo@most-busy-idle-check-all

== Logs ==

For more details see: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/index.html

[-- Attachment #2: Type: text/html, Size: 5809 bytes --]

^ permalink raw reply	[flat|nested] 50+ messages in thread

* [igt-dev] ✓ Fi.CI.IGT: success for Per client GPU utilisation
  2022-02-22 13:55 ` [Intel-gfx] " Tvrtko Ursulin
                   ` (13 preceding siblings ...)
  (?)
@ 2022-02-22 21:17 ` Patchwork
  -1 siblings, 0 replies; 50+ messages in thread
From: Patchwork @ 2022-02-22 21:17 UTC (permalink / raw)
  To: Tvrtko Ursulin; +Cc: igt-dev

[-- Attachment #1: Type: text/plain, Size: 30245 bytes --]

== Series Details ==

Series: Per client GPU utilisation
URL   : https://patchwork.freedesktop.org/series/100571/
State : success

== Summary ==

CI Bug Log - changes from CI_DRM_11266_full -> IGTPW_6676_full
====================================================

Summary
-------

  **SUCCESS**

  No regressions found.

  External URL: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/index.html

Participating hosts (13 -> 8)
------------------------------

  Missing    (5): pig-kbl-iris pig-glk-j5005 pig-skl-6260u shard-rkl shard-dg1 

Possible new issues
-------------------

  Here are the unknown changes that may have been introduced in IGTPW_6676_full:

### IGT changes ###

#### Possible regressions ####

  * {igt@drm_fdinfo@busy-check-all@bcs0} (NEW):
    - shard-glk:          NOTRUN -> [FAIL][1] +34 similar issues
   [1]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-glk3/igt@drm_fdinfo@busy-check-all@bcs0.html

  * {igt@drm_fdinfo@busy@bcs0} (NEW):
    - shard-apl:          NOTRUN -> [FAIL][2] +33 similar issues
   [2]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-apl3/igt@drm_fdinfo@busy@bcs0.html

  * {igt@drm_fdinfo@busy@vcs0} (NEW):
    - shard-snb:          NOTRUN -> [FAIL][3] +26 similar issues
   [3]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-snb4/igt@drm_fdinfo@busy@vcs0.html

  * {igt@drm_fdinfo@isolation@bcs0} (NEW):
    - shard-kbl:          NOTRUN -> [FAIL][4] +20 similar issues
   [4]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-kbl4/igt@drm_fdinfo@isolation@bcs0.html
    - {shard-tglu}:       NOTRUN -> [FAIL][5] +19 similar issues
   [5]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglu-2/igt@drm_fdinfo@isolation@bcs0.html
    - shard-tglb:         NOTRUN -> [FAIL][6] +20 similar issues
   [6]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb7/igt@drm_fdinfo@isolation@bcs0.html

  * {igt@drm_fdinfo@most-busy-check-all@vecs0} (NEW):
    - shard-iclb:         NOTRUN -> [FAIL][7] +28 similar issues
   [7]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb7/igt@drm_fdinfo@most-busy-check-all@vecs0.html

  
New tests
---------

  New tests have been introduced between CI_DRM_11266_full and IGTPW_6676_full:

### New IGT tests (49) ###

  * igt@drm_fdinfo@all-busy-check-all:
    - Statuses : 6 fail(s)
    - Exec time: [0.02, 0.16] s

  * igt@drm_fdinfo@all-busy-idle-check-all:
    - Statuses : 3 fail(s)
    - Exec time: [0.08, 0.13] s

  * igt@drm_fdinfo@basics:
    - Statuses : 4 fail(s)
    - Exec time: [0.01, 0.03] s

  * igt@drm_fdinfo@busy:
    - Statuses :
    - Exec time: [None] s

  * igt@drm_fdinfo@busy-check-all:
    - Statuses :
    - Exec time: [None] s

  * igt@drm_fdinfo@busy-check-all@bcs0:
    - Statuses : 7 fail(s)
    - Exec time: [0.01, 0.04] s

  * igt@drm_fdinfo@busy-check-all@rcs0:
    - Statuses : 7 fail(s)
    - Exec time: [0.03, 0.24] s

  * igt@drm_fdinfo@busy-check-all@vcs0:
    - Statuses : 7 fail(s)
    - Exec time: [0.01, 0.51] s

  * igt@drm_fdinfo@busy-check-all@vcs1:
    - Statuses : 3 fail(s)
    - Exec time: [0.01] s

  * igt@drm_fdinfo@busy-check-all@vecs0:
    - Statuses : 6 fail(s)
    - Exec time: [0.01, 0.04] s

  * igt@drm_fdinfo@busy-hang:
    - Statuses :
    - Exec time: [None] s

  * igt@drm_fdinfo@busy-idle:
    - Statuses :
    - Exec time: [None] s

  * igt@drm_fdinfo@busy-idle-check-all:
    - Statuses :
    - Exec time: [None] s

  * igt@drm_fdinfo@busy-idle-check-all@bcs0:
    - Statuses : 4 fail(s)
    - Exec time: [0.01, 0.04] s

  * igt@drm_fdinfo@busy-idle-check-all@rcs0:
    - Statuses : 4 fail(s)
    - Exec time: [0.02, 0.13] s

  * igt@drm_fdinfo@busy-idle-check-all@vcs0:
    - Statuses : 4 fail(s)
    - Exec time: [0.01, 0.51] s

  * igt@drm_fdinfo@busy-idle-check-all@vcs1:
    - Statuses : 1 fail(s)
    - Exec time: [0.01] s

  * igt@drm_fdinfo@busy-idle-check-all@vecs0:
    - Statuses : 3 fail(s)
    - Exec time: [0.01, 0.04] s

  * igt@drm_fdinfo@busy-idle@bcs0:
    - Statuses : 7 fail(s)
    - Exec time: [0.01, 0.04] s

  * igt@drm_fdinfo@busy-idle@rcs0:
    - Statuses : 7 fail(s)
    - Exec time: [0.01, 0.05] s

  * igt@drm_fdinfo@busy-idle@vcs0:
    - Statuses : 7 fail(s)
    - Exec time: [0.01, 0.51] s

  * igt@drm_fdinfo@busy-idle@vcs1:
    - Statuses : 4 fail(s)
    - Exec time: [0.01] s

  * igt@drm_fdinfo@busy-idle@vecs0:
    - Statuses : 6 fail(s)
    - Exec time: [0.01, 0.03] s

  * igt@drm_fdinfo@busy@bcs0:
    - Statuses : 3 fail(s)
    - Exec time: [0.01, 0.04] s

  * igt@drm_fdinfo@busy@rcs0:
    - Statuses : 3 fail(s)
    - Exec time: [0.01, 0.05] s

  * igt@drm_fdinfo@busy@vcs0:
    - Statuses : 3 fail(s)
    - Exec time: [0.03, 0.51] s

  * igt@drm_fdinfo@busy@vecs0:
    - Statuses : 2 fail(s)
    - Exec time: [0.04] s

  * igt@drm_fdinfo@idle:
    - Statuses :
    - Exec time: [None] s

  * igt@drm_fdinfo@idle@bcs0:
    - Statuses : 3 fail(s)
    - Exec time: [0.01, 0.03] s

  * igt@drm_fdinfo@idle@rcs0:
    - Statuses : 3 fail(s)
    - Exec time: [0.12, 0.24] s

  * igt@drm_fdinfo@idle@vcs0:
    - Statuses : 3 fail(s)
    - Exec time: [0.01, 0.02] s

  * igt@drm_fdinfo@idle@vecs0:
    - Statuses : 2 fail(s)
    - Exec time: [0.02] s

  * igt@drm_fdinfo@isolation:
    - Statuses :
    - Exec time: [None] s

  * igt@drm_fdinfo@isolation@bcs0:
    - Statuses : 7 fail(s)
    - Exec time: [0.01, 0.04] s

  * igt@drm_fdinfo@isolation@rcs0:
    - Statuses : 7 fail(s)
    - Exec time: [0.03, 0.16] s

  * igt@drm_fdinfo@isolation@vcs0:
    - Statuses : 7 fail(s)
    - Exec time: [0.01, 0.51] s

  * igt@drm_fdinfo@isolation@vcs1:
    - Statuses : 3 fail(s)
    - Exec time: [0.01] s

  * igt@drm_fdinfo@isolation@vecs0:
    - Statuses : 6 fail(s)
    - Exec time: [0.01, 0.04] s

  * igt@drm_fdinfo@most-busy-check-all:
    - Statuses :
    - Exec time: [None] s

  * igt@drm_fdinfo@most-busy-check-all@bcs0:
    - Statuses : 4 fail(s)
    - Exec time: [0.01, 0.03] s

  * igt@drm_fdinfo@most-busy-check-all@rcs0:
    - Statuses : 4 fail(s)
    - Exec time: [0.04, 0.12] s

  * igt@drm_fdinfo@most-busy-check-all@vcs0:
    - Statuses : 4 fail(s)
    - Exec time: [0.01, 0.03] s

  * igt@drm_fdinfo@most-busy-check-all@vecs0:
    - Statuses : 3 fail(s)
    - Exec time: [0.01, 0.03] s

  * igt@drm_fdinfo@most-busy-idle-check-all:
    - Statuses :
    - Exec time: [None] s

  * igt@drm_fdinfo@most-busy-idle-check-all@bcs0:
    - Statuses : 7 fail(s)
    - Exec time: [0.01, 0.04] s

  * igt@drm_fdinfo@most-busy-idle-check-all@rcs0:
    - Statuses : 7 fail(s)
    - Exec time: [0.03, 0.13] s

  * igt@drm_fdinfo@most-busy-idle-check-all@vcs0:
    - Statuses : 7 fail(s)
    - Exec time: [0.01, 0.03] s

  * igt@drm_fdinfo@most-busy-idle-check-all@vcs1:
    - Statuses : 3 fail(s)
    - Exec time: [0.01] s

  * igt@drm_fdinfo@most-busy-idle-check-all@vecs0:
    - Statuses : 6 fail(s)
    - Exec time: [0.01, 0.03] s

  

Known issues
------------

  Here are the changes found in IGTPW_6676_full that come from known issues:

### IGT changes ###

#### Issues hit ####

  * igt@gem_create@create-massive:
    - shard-kbl:          NOTRUN -> [DMESG-WARN][8] ([i915#4991])
   [8]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-kbl7/igt@gem_create@create-massive.html
    - shard-tglb:         NOTRUN -> [DMESG-WARN][9] ([i915#4991])
   [9]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb7/igt@gem_create@create-massive.html

  * igt@gem_ctx_persistence@legacy-engines-queued:
    - shard-snb:          NOTRUN -> [SKIP][10] ([fdo#109271] / [i915#1099]) +1 similar issue
   [10]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-snb2/igt@gem_ctx_persistence@legacy-engines-queued.html

  * igt@gem_ctx_sseu@invalid-sseu:
    - shard-tglb:         NOTRUN -> [SKIP][11] ([i915#280]) +1 similar issue
   [11]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb5/igt@gem_ctx_sseu@invalid-sseu.html

  * igt@gem_exec_balancer@parallel-keep-in-fence:
    - shard-kbl:          NOTRUN -> [DMESG-WARN][12] ([i915#5076]) +1 similar issue
   [12]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-kbl3/igt@gem_exec_balancer@parallel-keep-in-fence.html

  * igt@gem_exec_capture@pi@vecs0:
    - shard-iclb:         NOTRUN -> [INCOMPLETE][13] ([i915#3371])
   [13]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb3/igt@gem_exec_capture@pi@vecs0.html

  * igt@gem_exec_fair@basic-none-rrul@rcs0:
    - shard-tglb:         NOTRUN -> [FAIL][14] ([i915#2842]) +2 similar issues
   [14]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb7/igt@gem_exec_fair@basic-none-rrul@rcs0.html

  * igt@gem_exec_fair@basic-none-share@rcs0:
    - shard-iclb:         [PASS][15] -> [FAIL][16] ([i915#2842])
   [15]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_11266/shard-iclb7/igt@gem_exec_fair@basic-none-share@rcs0.html
   [16]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb4/igt@gem_exec_fair@basic-none-share@rcs0.html

  * igt@gem_exec_fair@basic-none-solo@rcs0:
    - shard-kbl:          [PASS][17] -> [FAIL][18] ([i915#2842])
   [17]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_11266/shard-kbl7/igt@gem_exec_fair@basic-none-solo@rcs0.html
   [18]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-kbl4/igt@gem_exec_fair@basic-none-solo@rcs0.html

  * igt@gem_exec_fair@basic-pace@vecs0:
    - shard-tglb:         [PASS][19] -> [FAIL][20] ([i915#2842]) +1 similar issue
   [19]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_11266/shard-tglb1/igt@gem_exec_fair@basic-pace@vecs0.html
   [20]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb6/igt@gem_exec_fair@basic-pace@vecs0.html

  * igt@gem_exec_params@secure-non-root:
    - shard-tglb:         NOTRUN -> [SKIP][21] ([fdo#112283]) +1 similar issue
   [21]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb5/igt@gem_exec_params@secure-non-root.html
    - shard-iclb:         NOTRUN -> [SKIP][22] ([fdo#112283]) +1 similar issue
   [22]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb5/igt@gem_exec_params@secure-non-root.html

  * igt@gem_lmem_swapping@parallel-random:
    - shard-kbl:          NOTRUN -> [SKIP][23] ([fdo#109271] / [i915#4613]) +2 similar issues
   [23]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-kbl1/igt@gem_lmem_swapping@parallel-random.html

  * igt@gem_lmem_swapping@random:
    - shard-tglb:         NOTRUN -> [SKIP][24] ([i915#4613]) +4 similar issues
   [24]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb3/igt@gem_lmem_swapping@random.html

  * igt@gem_lmem_swapping@verify:
    - shard-iclb:         NOTRUN -> [SKIP][25] ([i915#4613]) +1 similar issue
   [25]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb3/igt@gem_lmem_swapping@verify.html
    - shard-apl:          NOTRUN -> [SKIP][26] ([fdo#109271] / [i915#4613])
   [26]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-apl2/igt@gem_lmem_swapping@verify.html

  * igt@gem_partial_pwrite_pread@write-display:
    - shard-glk:          [PASS][27] -> [DMESG-WARN][28] ([i915#118]) +3 similar issues
   [27]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_11266/shard-glk9/igt@gem_partial_pwrite_pread@write-display.html
   [28]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-glk5/igt@gem_partial_pwrite_pread@write-display.html

  * igt@gem_pxp@create-protected-buffer:
    - shard-iclb:         NOTRUN -> [SKIP][29] ([i915#4270]) +2 similar issues
   [29]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb5/igt@gem_pxp@create-protected-buffer.html

  * igt@gem_pxp@regular-baseline-src-copy-readible:
    - shard-tglb:         NOTRUN -> [SKIP][30] ([i915#4270]) +4 similar issues
   [30]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb2/igt@gem_pxp@regular-baseline-src-copy-readible.html

  * igt@gem_render_copy@y-tiled-ccs-to-y-tiled-mc-ccs:
    - shard-iclb:         NOTRUN -> [SKIP][31] ([i915#768]) +3 similar issues
   [31]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb2/igt@gem_render_copy@y-tiled-ccs-to-y-tiled-mc-ccs.html

  * igt@gem_userptr_blits@input-checking:
    - shard-apl:          NOTRUN -> [DMESG-WARN][32] ([i915#4991])
   [32]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-apl7/igt@gem_userptr_blits@input-checking.html
    - shard-glk:          NOTRUN -> [DMESG-WARN][33] ([i915#4991])
   [33]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-glk1/igt@gem_userptr_blits@input-checking.html
    - shard-snb:          NOTRUN -> [DMESG-WARN][34] ([i915#4991])
   [34]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-snb7/igt@gem_userptr_blits@input-checking.html

  * igt@gem_userptr_blits@unsync-unmap-cycles:
    - shard-tglb:         NOTRUN -> [SKIP][35] ([i915#3297])
   [35]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb3/igt@gem_userptr_blits@unsync-unmap-cycles.html

  * igt@gem_userptr_blits@vma-merge:
    - shard-snb:          NOTRUN -> [FAIL][36] ([i915#2724])
   [36]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-snb5/igt@gem_userptr_blits@vma-merge.html
    - shard-apl:          NOTRUN -> [FAIL][37] ([i915#3318])
   [37]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-apl1/igt@gem_userptr_blits@vma-merge.html
    - shard-iclb:         NOTRUN -> [FAIL][38] ([i915#3318])
   [38]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb1/igt@gem_userptr_blits@vma-merge.html
    - shard-glk:          NOTRUN -> [FAIL][39] ([i915#3318])
   [39]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-glk5/igt@gem_userptr_blits@vma-merge.html
    - shard-kbl:          NOTRUN -> [FAIL][40] ([i915#3318])
   [40]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-kbl4/igt@gem_userptr_blits@vma-merge.html
    - shard-tglb:         NOTRUN -> [FAIL][41] ([i915#3318])
   [41]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb6/igt@gem_userptr_blits@vma-merge.html

  * igt@gen7_exec_parse@basic-allocation:
    - shard-tglb:         NOTRUN -> [SKIP][42] ([fdo#109289]) +5 similar issues
   [42]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb7/igt@gen7_exec_parse@basic-allocation.html

  * igt@gen9_exec_parse@allowed-all:
    - shard-apl:          [PASS][43] -> [DMESG-WARN][44] ([i915#1436] / [i915#716])
   [43]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_11266/shard-apl8/igt@gen9_exec_parse@allowed-all.html
   [44]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-apl3/igt@gen9_exec_parse@allowed-all.html

  * igt@gen9_exec_parse@batch-without-end:
    - shard-iclb:         NOTRUN -> [SKIP][45] ([i915#2856]) +2 similar issues
   [45]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb1/igt@gen9_exec_parse@batch-without-end.html

  * igt@gen9_exec_parse@bb-oversize:
    - shard-tglb:         NOTRUN -> [SKIP][46] ([i915#2527] / [i915#2856]) +3 similar issues
   [46]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb1/igt@gen9_exec_parse@bb-oversize.html

  * igt@i915_pm_dc@dc6-dpms:
    - shard-kbl:          NOTRUN -> [FAIL][47] ([i915#454])
   [47]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-kbl4/igt@i915_pm_dc@dc6-dpms.html

  * igt@i915_pm_dc@dc9-dpms:
    - shard-iclb:         [PASS][48] -> [SKIP][49] ([i915#4281])
   [48]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_11266/shard-iclb4/igt@i915_pm_dc@dc9-dpms.html
   [49]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb3/igt@i915_pm_dc@dc9-dpms.html

  * igt@i915_pm_lpsp@kms-lpsp@kms-lpsp-dp:
    - shard-kbl:          NOTRUN -> [SKIP][50] ([fdo#109271] / [i915#1937])
   [50]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-kbl7/igt@i915_pm_lpsp@kms-lpsp@kms-lpsp-dp.html
    - shard-apl:          NOTRUN -> [SKIP][51] ([fdo#109271] / [i915#1937])
   [51]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-apl4/igt@i915_pm_lpsp@kms-lpsp@kms-lpsp-dp.html

  * igt@i915_pm_rc6_residency@rc6-fence:
    - shard-tglb:         NOTRUN -> [WARN][52] ([i915#2681] / [i915#2684])
   [52]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb1/igt@i915_pm_rc6_residency@rc6-fence.html

  * igt@i915_pm_rpm@dpms-non-lpsp:
    - shard-tglb:         NOTRUN -> [SKIP][53] ([fdo#111644] / [i915#1397] / [i915#2411])
   [53]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb6/igt@i915_pm_rpm@dpms-non-lpsp.html

  * igt@i915_pm_rpm@modeset-pc8-residency-stress:
    - shard-tglb:         NOTRUN -> [SKIP][54] ([fdo#109506] / [i915#2411])
   [54]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb2/igt@i915_pm_rpm@modeset-pc8-residency-stress.html

  * igt@i915_pm_rpm@system-suspend-execbuf:
    - shard-iclb:         [PASS][55] -> [INCOMPLETE][56] ([i915#5096])
   [55]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_11266/shard-iclb2/igt@i915_pm_rpm@system-suspend-execbuf.html
   [56]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb4/igt@i915_pm_rpm@system-suspend-execbuf.html

  * igt@i915_pm_sseu@full-enable:
    - shard-tglb:         NOTRUN -> [SKIP][57] ([i915#4387])
   [57]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb3/igt@i915_pm_sseu@full-enable.html

  * igt@i915_query@query-topology-unsupported:
    - shard-tglb:         NOTRUN -> [SKIP][58] ([fdo#109302])
   [58]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb7/igt@i915_query@query-topology-unsupported.html

  * igt@i915_selftest@live@gt_lrc:
    - shard-tglb:         NOTRUN -> [DMESG-FAIL][59] ([i915#2373])
   [59]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb5/igt@i915_selftest@live@gt_lrc.html

  * igt@i915_selftest@live@gt_pm:
    - shard-tglb:         NOTRUN -> [DMESG-FAIL][60] ([i915#1759])
   [60]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb5/igt@i915_selftest@live@gt_pm.html

  * igt@i915_selftest@live@hangcheck:
    - shard-snb:          [PASS][61] -> [INCOMPLETE][62] ([i915#3921])
   [61]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_11266/shard-snb5/igt@i915_selftest@live@hangcheck.html
   [62]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-snb7/igt@i915_selftest@live@hangcheck.html

  * igt@i915_selftest@mock@hugepages:
    - shard-apl:          NOTRUN -> [INCOMPLETE][63] ([i915#5123])
   [63]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-apl8/igt@i915_selftest@mock@hugepages.html
    - shard-kbl:          NOTRUN -> [INCOMPLETE][64] ([i915#5123])
   [64]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-kbl6/igt@i915_selftest@mock@hugepages.html

  * igt@kms_atomic@atomic_plane_damage:
    - shard-iclb:         NOTRUN -> [SKIP][65] ([i915#4765])
   [65]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb6/igt@kms_atomic@atomic_plane_damage.html

  * igt@kms_atomic_transition@plane-all-modeset-transition-fencing:
    - shard-iclb:         NOTRUN -> [SKIP][66] ([i915#1769])
   [66]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb6/igt@kms_atomic_transition@plane-all-modeset-transition-fencing.html
    - shard-tglb:         NOTRUN -> [SKIP][67] ([i915#1769])
   [67]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb3/igt@kms_atomic_transition@plane-all-modeset-transition-fencing.html

  * igt@kms_big_fb@x-tiled-32bpp-rotate-180:
    - shard-glk:          NOTRUN -> [DMESG-WARN][68] ([i915#118])
   [68]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-glk1/igt@kms_big_fb@x-tiled-32bpp-rotate-180.html

  * igt@kms_big_fb@x-tiled-32bpp-rotate-90:
    - shard-tglb:         NOTRUN -> [SKIP][69] ([fdo#111614]) +1 similar issue
   [69]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb6/igt@kms_big_fb@x-tiled-32bpp-rotate-90.html
    - shard-iclb:         NOTRUN -> [SKIP][70] ([fdo#110725] / [fdo#111614]) +1 similar issue
   [70]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb3/igt@kms_big_fb@x-tiled-32bpp-rotate-90.html

  * igt@kms_big_fb@y-tiled-max-hw-stride-64bpp-rotate-0-hflip:
    - shard-kbl:          NOTRUN -> [SKIP][71] ([fdo#109271] / [i915#3777]) +1 similar issue
   [71]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-kbl1/igt@kms_big_fb@y-tiled-max-hw-stride-64bpp-rotate-0-hflip.html

  * igt@kms_big_fb@yf-tiled-8bpp-rotate-180:
    - shard-tglb:         NOTRUN -> [SKIP][72] ([fdo#111615]) +8 similar issues
   [72]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb1/igt@kms_big_fb@yf-tiled-8bpp-rotate-180.html

  * igt@kms_big_fb@yf-tiled-max-hw-stride-32bpp-rotate-180-hflip-async-flip:
    - shard-apl:          NOTRUN -> [SKIP][73] ([fdo#109271] / [i915#3777]) +4 similar issues
   [73]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-apl7/igt@kms_big_fb@yf-tiled-max-hw-stride-32bpp-rotate-180-hflip-async-flip.html
    - shard-glk:          NOTRUN -> [SKIP][74] ([fdo#109271] / [i915#3777]) +2 similar issues
   [74]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-glk1/igt@kms_big_fb@yf-tiled-max-hw-stride-32bpp-rotate-180-hflip-async-flip.html

  * igt@kms_big_fb@yf-tiled-max-hw-stride-64bpp-rotate-180-async-flip:
    - shard-iclb:         NOTRUN -> [SKIP][75] ([fdo#110723]) +4 similar issues
   [75]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb4/igt@kms_big_fb@yf-tiled-max-hw-stride-64bpp-rotate-180-async-flip.html

  * igt@kms_big_joiner@basic:
    - shard-tglb:         NOTRUN -> [SKIP][76] ([i915#2705])
   [76]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb5/igt@kms_big_joiner@basic.html

  * igt@kms_ccs@pipe-a-crc-sprite-planes-basic-y_tiled_ccs:
    - shard-tglb:         NOTRUN -> [SKIP][77] ([i915#3689]) +10 similar issues
   [77]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb7/igt@kms_ccs@pipe-a-crc-sprite-planes-basic-y_tiled_ccs.html

  * igt@kms_ccs@pipe-a-random-ccs-data-y_tiled_gen12_mc_ccs:
    - shard-kbl:          NOTRUN -> [SKIP][78] ([fdo#109271] / [i915#3886]) +10 similar issues
   [78]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-kbl1/igt@kms_ccs@pipe-a-random-ccs-data-y_tiled_gen12_mc_ccs.html

  * igt@kms_ccs@pipe-b-bad-rotation-90-y_tiled_gen12_mc_ccs:
    - shard-tglb:         NOTRUN -> [SKIP][79] ([i915#3689] / [i915#3886]) +6 similar issues
   [79]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb3/igt@kms_ccs@pipe-b-bad-rotation-90-y_tiled_gen12_mc_ccs.html

  * igt@kms_ccs@pipe-b-crc-sprite-planes-basic-y_tiled_gen12_rc_ccs_cc:
    - shard-apl:          NOTRUN -> [SKIP][80] ([fdo#109271] / [i915#3886]) +8 similar issues
   [80]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-apl8/igt@kms_ccs@pipe-b-crc-sprite-planes-basic-y_tiled_gen12_rc_ccs_cc.html

  * igt@kms_ccs@pipe-b-random-ccs-data-y_tiled_gen12_mc_ccs:
    - shard-iclb:         NOTRUN -> [SKIP][81] ([fdo#109278] / [i915#3886]) +6 similar issues
   [81]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb3/igt@kms_ccs@pipe-b-random-ccs-data-y_tiled_gen12_mc_ccs.html

  * igt@kms_ccs@pipe-c-ccs-on-another-bo-y_tiled_gen12_mc_ccs:
    - shard-glk:          NOTRUN -> [SKIP][82] ([fdo#109271] / [i915#3886]) +1 similar issue
   [82]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-glk7/igt@kms_ccs@pipe-c-ccs-on-another-bo-y_tiled_gen12_mc_ccs.html

  * igt@kms_ccs@pipe-c-random-ccs-data-yf_tiled_ccs:
    - shard-tglb:         NOTRUN -> [SKIP][83] ([fdo#111615] / [i915#3689]) +4 similar issues
   [83]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb3/igt@kms_ccs@pipe-c-random-ccs-data-yf_tiled_ccs.html

  * igt@kms_ccs@pipe-d-crc-primary-rotation-180-y_tiled_gen12_mc_ccs:
    - shard-glk:          NOTRUN -> [SKIP][84] ([fdo#109271]) +70 similar issues
   [84]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-glk3/igt@kms_ccs@pipe-d-crc-primary-rotation-180-y_tiled_gen12_mc_ccs.html

  * igt@kms_chamelium@common-hpd-after-suspend:
    - shard-glk:          NOTRUN -> [SKIP][85] ([fdo#109271] / [fdo#111827]) +6 similar issues
   [85]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-glk6/igt@kms_chamelium@common-hpd-after-suspend.html

  * igt@kms_chamelium@hdmi-edid-change-during-suspend:
    - shard-apl:          NOTRUN -> [SKIP][86] ([fdo#109271] / [fdo#111827]) +7 similar issues
   [86]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-apl4/igt@kms_chamelium@hdmi-edid-change-during-suspend.html

  * igt@kms_chamelium@hdmi-hpd-enable-disable-mode:
    - shard-iclb:         NOTRUN -> [SKIP][87] ([fdo#109284] / [fdo#111827]) +9 similar issues
   [87]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb1/igt@kms_chamelium@hdmi-hpd-enable-disable-mode.html
    - shard-snb:          NOTRUN -> [SKIP][88] ([fdo#109271] / [fdo#111827]) +6 similar issues
   [88]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-snb4/igt@kms_chamelium@hdmi-hpd-enable-disable-mode.html

  * igt@kms_color@pipe-d-ctm-max:
    - shard-iclb:         NOTRUN -> [SKIP][89] ([fdo#109278] / [i915#1149])
   [89]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb7/igt@kms_color@pipe-d-ctm-max.html

  * igt@kms_color_chamelium@pipe-b-ctm-0-25:
    - shard-kbl:          NOTRUN -> [SKIP][90] ([fdo#109271] / [fdo#111827]) +15 similar issues
   [90]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-kbl1/igt@kms_color_chamelium@pipe-b-ctm-0-25.html

  * igt@kms_color_chamelium@pipe-b-ctm-0-75:
    - shard-tglb:         NOTRUN -> [SKIP][91] ([fdo#109284] / [fdo#111827]) +13 similar issues
   [91]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb7/igt@kms_color_chamelium@pipe-b-ctm-0-75.html

  * igt@kms_content_protection@atomic-dpms:
    - shard-kbl:          NOTRUN -> [TIMEOUT][92] ([i915#1319])
   [92]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-kbl3/igt@kms_content_protection@atomic-dpms.html

  * igt@kms_content_protection@dp-mst-lic-type-1:
    - shard-iclb:         NOTRUN -> [SKIP][93] ([i915#3116])
   [93]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb3/igt@kms_content_protection@dp-mst-lic-type-1.html
    - shard-tglb:         NOTRUN -> [SKIP][94] ([i915#3116] / [i915#3299])
   [94]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb6/igt@kms_content_protection@dp-mst-lic-type-1.html

  * igt@kms_cursor_crc@pipe-a-cursor-512x512-rapid-movement:
    - shard-iclb:         NOTRUN -> [SKIP][95] ([fdo#109278] / [fdo#109279]) +3 similar issues
   [95]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb1/igt@kms_cursor_crc@pipe-a-cursor-512x512-rapid-movement.html

  * igt@kms_cursor_crc@pipe-b-cursor-32x32-offscreen:
    - shard-tglb:         NOTRUN -> [SKIP][96] ([i915#3319]) +5 similar issues
   [96]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb1/igt@kms_cursor_crc@pipe-b-cursor-32x32-offscreen.html

  * igt@kms_cursor_crc@pipe-b-cursor-max-size-onscreen:
    - shard-tglb:         NOTRUN -> [SKIP][97] ([i915#3359]) +4 similar issues
   [97]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb6/igt@kms_cursor_crc@pipe-b-cursor-max-size-onscreen.html

  * igt@kms_cursor_crc@pipe-d-cursor-256x256-onscreen:
    - shard-kbl:          NOTRUN -> [SKIP][98] ([fdo#109271]) +233 similar issues
   [98]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-kbl4/igt@kms_cursor_crc@pipe-d-cursor-256x256-onscreen.html

  * igt@kms_cursor_crc@pipe-d-cursor-512x170-random:
    - shard-tglb:         NOTRUN -> [SKIP][99] ([fdo#109279] / [i915#3359]) +9 similar issues
   [99]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb7/igt@kms_cursor_crc@pipe-d-cursor-512x170-random.html

  * igt@kms_cursor_legacy@2x-long-cursor-vs-flip-legacy:
    - shard-tglb:         NOTRUN -> [SKIP][100] ([fdo#109274] / [fdo#111825]) +14 similar issues
   [100]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb2/igt@kms_cursor_legacy@2x-long-cursor-vs-flip-legacy.html

  * igt@kms_cursor_legacy@cursora-vs-flipb-atomic-transitions-varying-size:
    - shard-iclb:         NOTRUN -> [SKIP][101] ([fdo#109274] / [fdo#109278]) +3 similar issues
   [101]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb2/igt@kms_cursor_legacy@cursora-vs-flipb-atomic-transitions-varying-size.html

  * igt@kms_dither@fb-8bpc-vs-panel-8bpc@edp-1-pipe-a:
    - shard-tglb:         NOTRUN -> [SKIP][102] ([i915#3788])
   [102]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb5/igt@kms_dither@fb-8bpc-vs-panel-8bpc@edp-1-pipe-a.html

  * igt@kms_dp_tiled_display@basic-test-pattern:
    - shard-iclb:         NOTRUN -> [SKIP][103] ([i915#426])
   [103]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb5/igt@kms_dp_tiled_display@basic-test-pattern.html
    - shard-tglb:         NOTRUN -> [SKIP][104] ([i915#426])
   [104]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-tglb5/igt@kms_dp_tiled_display@basic-test-pattern.html

  * igt@kms_flip@2x-flip-vs-expired-vblank-interruptible@ac-hdmi-a1-hdmi-a2:
    - shard-glk:          [PASS][105] -> [FAIL][106] ([i915#79])
   [105]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_11266/shard-glk4/igt@kms_flip@2x-flip-vs-expired-vblank-interruptible@ac-hdmi-a1-hdmi-a2.html
   [106]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-glk8/igt@kms_flip@2x-flip-vs-expired-vblank-interruptible@ac-hdmi-a1-hdmi-a2.html

  * igt@kms_flip@2x-flip-vs-rmfb-interruptible:
    - shard-iclb:         NOTRUN -> [SKIP][107] ([fdo#109274]) +4 similar issues
   [107]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-iclb8/igt@kms_flip@2x-flip-vs-rmfb-interruptible.html

  * igt@kms_flip@flip-vs-suspend-interruptible@a-dp1:
    - shard-kbl:          NOTRUN -> [DMESG-WARN][108] ([i915#180]) +3 similar issues
   [108]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/shard-kbl7/igt@kms_flip@flip-vs-suspend-interruptible@a-dp1.html

  * igt@kms_flip@flip-vs-suspend@c-dp1:
    - shard-apl:          [PASS][109] -> [DMESG-WARN][110] ([i915#180]) +1 similar issue
   [109]: https://intel-gfx-ci.01.org/tree/drm-tip/CI_DRM_11266/shard-apl3/igt@kms_flip@flip-vs-suspend@c-dp

== Logs ==

For more details see: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_6676/index.html

[-- Attachment #2: Type: text/html, Size: 34966 bytes --]

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [Intel-gfx] [PATCH i-g-t 01/11] lib: Helper library for parsing i915 fdinfo output
  2022-02-22 13:55   ` [Intel-gfx] " Tvrtko Ursulin
@ 2022-03-30 19:52     ` Umesh Nerlige Ramappa
  -1 siblings, 0 replies; 50+ messages in thread
From: Umesh Nerlige Ramappa @ 2022-03-30 19:52 UTC (permalink / raw)
  To: Tvrtko Ursulin; +Cc: igt-dev, Intel-gfx, dri-devel

On Tue, Feb 22, 2022 at 01:55:55PM +0000, Tvrtko Ursulin wrote:
>From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>
>Tests and intel_gpu_top will share common code for parsing this file.
>
>Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>---
> lib/igt_drm_fdinfo.c | 183 +++++++++++++++++++++++++++++++++++++++++++
> lib/igt_drm_fdinfo.h |  48 ++++++++++++
> lib/meson.build      |   7 ++
> 3 files changed, 238 insertions(+)
> create mode 100644 lib/igt_drm_fdinfo.c
> create mode 100644 lib/igt_drm_fdinfo.h
>
>diff --git a/lib/igt_drm_fdinfo.c b/lib/igt_drm_fdinfo.c
>new file mode 100644
>index 000000000000..28c1bdbda08e
>--- /dev/null
>+++ b/lib/igt_drm_fdinfo.c
>@@ -0,0 +1,183 @@
>+/*
>+ * 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 <ctype.h>
>+#include <sys/types.h>
>+#include <sys/stat.h>
>+#include <fcntl.h>
>+#include <stdio.h>
>+#include <string.h>
>+#include <stdlib.h>
>+#include <unistd.h>
>+
>+#include "drmtest.h"
>+
>+#include "igt_drm_fdinfo.h"
>+
>+static size_t read_fdinfo(char *buf, const size_t sz, int at, const char *name)
>+{
>+	size_t count;
>+	int fd;
>+
>+	fd = openat(at, name, O_RDONLY);
>+	if (fd < 0)
>+		return 0;
>+
>+	buf[sz - 1] = 0;

Wondering if this ^ should be after the read() in case 4096 bytes are read.

>+	count = read(fd, buf, sz);
>+	close(fd);
>+
>+	return count;
>+}
>+
>+static int parse_engine(char *line, struct drm_client_fdinfo *info,
>+			size_t prefix_len, uint64_t *val)
>+{
>+	static const char *e2class[] = {
>+		"render",
>+		"copy",
>+		"video",
>+		"video-enhance",
>+	};
>+	ssize_t name_len;
>+	char *name, *p;
>+	int found = -1;
>+	unsigned int i;
>+
>+	p = index(line, ':');
>+	if (!p || p == line)
>+		return -1;
>+
>+	name_len = p - line - prefix_len;
>+	if (name_len < 1)
>+		return -1;
>+
>+	name = line + prefix_len;
>+
>+	for (i = 0; i < ARRAY_SIZE(e2class); i++) {
>+		if (!strncmp(name, e2class[i], name_len)) {
>+			found = i;
>+			break;
>+		}
>+	}
>+
>+	if (found >= 0) {
>+		while (*++p && isspace(*p));
>+		*val = strtoull(p, NULL, 10);
>+	}
>+
>+	return found;
>+}
>+
>+static const char *find_kv(const char *buf, const char *key, size_t keylen)
>+{
>+	const char *p = buf;
>+
>+	p = index(buf, ':');
>+	if (!p || p == buf)
>+		return NULL;
>+
>+	if ((p - buf) != keylen)
>+		return NULL;
>+
>+	while (*++p && isspace(*p));
>+	if (*p && !strncmp(buf, key, keylen))

nit: why not just do the strncmp early in this function since buf, key, 
keylen have not changed?

>+		return p;
>+
>+	return NULL;
>+}
>+
>+bool
>+__igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
>+{
>+	char buf[4096], *_buf = buf;
>+	char *l, *ctx = NULL;
>+	unsigned int good = 0;
>+	size_t count;
>+

Should buf be zeroed out here?

>+	count = read_fdinfo(buf, sizeof(buf), dir, fd);
>+	if (!count)
>+		return false;
>+
>+	while ((l = strtok_r(_buf, "\n", &ctx))) {
>+		uint64_t val = 0;
>+		const char *v;
>+		int idx;
>+
>+		_buf = NULL;
>+
>+		if ((v = find_kv(l, "drm-driver", strlen("drm-driver")))) {
>+			strncpy(info->driver, v, sizeof(info->driver) - 1);
>+			good++;
>+		} else if ((v = find_kv(l, "drm-pdev", strlen("drm-pdev")))) {
>+			strncpy(info->pdev, v, sizeof(info->pdev) - 1);
>+		}  else if ((v = find_kv(l, "drm-client-id",
>+					 strlen("drm-client-id")))) {
>+			info->id = atol(v);
>+			good++;
>+		} else if (!strncmp(l, "drm-engine-", 11) &&
>+			   strncmp(l, "drm-engine-capacity-", 20)) {
>+			idx = parse_engine(l, info, strlen("drm-engine-"),
>+					   &val);
>+			if (idx >= 0) {
>+				if (!info->capacity[idx])
>+					info->capacity[idx] = 1;
>+				info->busy[idx] = val;
>+				info->num_engines++;
>+			}
>+		} else if (!strncmp(l, "drm-engine-capacity-", 20)) {
>+			idx = parse_engine(l, info,
>+					   strlen("drm-engine-capacity-"),
>+					   &val);
>+			if (idx >= 0)
>+				info->capacity[idx] = val;
>+		}
>+	}
>+
>+	if (good < 2 || !info->num_engines)
>+		return false; /* fdinfo format not as expected */
>+
>+	return true;
>+}
>+
>+bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info)
>+{
>+	char fd[64];
>+	int dir, ret;
>+	bool res;
>+
>+	ret = snprintf(fd, sizeof(fd), "%u", drm_fd);
>+	if (ret < 0 || ret == sizeof(fd))
>+		return false;
>+
>+	dir = open("/proc/self/fdinfo", O_DIRECTORY | O_RDONLY);
>+	if (dir < 0)
>+		return false;
>+
>+	res = __igt_parse_drm_fdinfo(dir, fd, info);
>+
>+	close(dir);
>+
>+	return res;
>+}
>diff --git a/lib/igt_drm_fdinfo.h b/lib/igt_drm_fdinfo.h
>new file mode 100644
>index 000000000000..c527bab9a204
>--- /dev/null
>+++ b/lib/igt_drm_fdinfo.h
>@@ -0,0 +1,48 @@
>+/*
>+ * 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.
>+ *
>+ */
>+
>+#ifndef IGT_DRM_FDINFO_H
>+#define IGT_DRM_FDINFO_H
>+
>+#include <sys/types.h>
>+#include <dirent.h>
>+#include <stdint.h>
>+#include <stdbool.h>
>+
>+struct drm_client_fdinfo {
>+	char driver[128];
>+	char pdev[128];
>+	unsigned long id;
>+
>+	unsigned int num_engines;
>+	unsigned int capacity[16];
>+	uint64_t busy[16];

I see 16 in a lot of places, wondering if it could use a #define?

Thanks,
Umesh

>+};
>+
>+bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info);
>+
>+bool __igt_parse_drm_fdinfo(int dir, const char *fd,
>+			    struct drm_client_fdinfo *info);
>+
>+#endif /* IGT_DRM_FDINFO_H */
>diff --git a/lib/meson.build b/lib/meson.build
>index 3e43316d1e36..d00817dc58bc 100644
>--- a/lib/meson.build
>+++ b/lib/meson.build
>@@ -16,6 +16,7 @@ lib_sources = [
> 	'igt_debugfs.c',
> 	'igt_device.c',
> 	'igt_device_scan.c',
>+	'igt_drm_fdinfo.c',
> 	'igt_aux.c',
> 	'igt_gt.c',
> 	'igt_halffloat.c',
>@@ -216,6 +217,12 @@ lib_igt_device_scan_build = static_library('igt_device_scan',
> lib_igt_device_scan = declare_dependency(link_with : lib_igt_device_scan_build,
> 				  include_directories : inc)
>
>+lib_igt_drm_fdinfo_build = static_library('igt_drm_fdinfo',
>+	['igt_drm_fdinfo.c'],
>+	include_directories : inc)
>+
>+lib_igt_drm_fdinfo = declare_dependency(link_with : lib_igt_drm_fdinfo_build,
>+				  include_directories : inc)
> i915_perf_files = [
>   'igt_list.c',
>   'i915/perf.c',
>-- 
>2.32.0
>

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [igt-dev] [Intel-gfx] [PATCH i-g-t 01/11] lib: Helper library for parsing i915 fdinfo output
@ 2022-03-30 19:52     ` Umesh Nerlige Ramappa
  0 siblings, 0 replies; 50+ messages in thread
From: Umesh Nerlige Ramappa @ 2022-03-30 19:52 UTC (permalink / raw)
  To: Tvrtko Ursulin; +Cc: igt-dev, Intel-gfx, dri-devel

On Tue, Feb 22, 2022 at 01:55:55PM +0000, Tvrtko Ursulin wrote:
>From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>
>Tests and intel_gpu_top will share common code for parsing this file.
>
>Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>---
> lib/igt_drm_fdinfo.c | 183 +++++++++++++++++++++++++++++++++++++++++++
> lib/igt_drm_fdinfo.h |  48 ++++++++++++
> lib/meson.build      |   7 ++
> 3 files changed, 238 insertions(+)
> create mode 100644 lib/igt_drm_fdinfo.c
> create mode 100644 lib/igt_drm_fdinfo.h
>
>diff --git a/lib/igt_drm_fdinfo.c b/lib/igt_drm_fdinfo.c
>new file mode 100644
>index 000000000000..28c1bdbda08e
>--- /dev/null
>+++ b/lib/igt_drm_fdinfo.c
>@@ -0,0 +1,183 @@
>+/*
>+ * 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 <ctype.h>
>+#include <sys/types.h>
>+#include <sys/stat.h>
>+#include <fcntl.h>
>+#include <stdio.h>
>+#include <string.h>
>+#include <stdlib.h>
>+#include <unistd.h>
>+
>+#include "drmtest.h"
>+
>+#include "igt_drm_fdinfo.h"
>+
>+static size_t read_fdinfo(char *buf, const size_t sz, int at, const char *name)
>+{
>+	size_t count;
>+	int fd;
>+
>+	fd = openat(at, name, O_RDONLY);
>+	if (fd < 0)
>+		return 0;
>+
>+	buf[sz - 1] = 0;

Wondering if this ^ should be after the read() in case 4096 bytes are read.

>+	count = read(fd, buf, sz);
>+	close(fd);
>+
>+	return count;
>+}
>+
>+static int parse_engine(char *line, struct drm_client_fdinfo *info,
>+			size_t prefix_len, uint64_t *val)
>+{
>+	static const char *e2class[] = {
>+		"render",
>+		"copy",
>+		"video",
>+		"video-enhance",
>+	};
>+	ssize_t name_len;
>+	char *name, *p;
>+	int found = -1;
>+	unsigned int i;
>+
>+	p = index(line, ':');
>+	if (!p || p == line)
>+		return -1;
>+
>+	name_len = p - line - prefix_len;
>+	if (name_len < 1)
>+		return -1;
>+
>+	name = line + prefix_len;
>+
>+	for (i = 0; i < ARRAY_SIZE(e2class); i++) {
>+		if (!strncmp(name, e2class[i], name_len)) {
>+			found = i;
>+			break;
>+		}
>+	}
>+
>+	if (found >= 0) {
>+		while (*++p && isspace(*p));
>+		*val = strtoull(p, NULL, 10);
>+	}
>+
>+	return found;
>+}
>+
>+static const char *find_kv(const char *buf, const char *key, size_t keylen)
>+{
>+	const char *p = buf;
>+
>+	p = index(buf, ':');
>+	if (!p || p == buf)
>+		return NULL;
>+
>+	if ((p - buf) != keylen)
>+		return NULL;
>+
>+	while (*++p && isspace(*p));
>+	if (*p && !strncmp(buf, key, keylen))

nit: why not just do the strncmp early in this function since buf, key, 
keylen have not changed?

>+		return p;
>+
>+	return NULL;
>+}
>+
>+bool
>+__igt_parse_drm_fdinfo(int dir, const char *fd, struct drm_client_fdinfo *info)
>+{
>+	char buf[4096], *_buf = buf;
>+	char *l, *ctx = NULL;
>+	unsigned int good = 0;
>+	size_t count;
>+

Should buf be zeroed out here?

>+	count = read_fdinfo(buf, sizeof(buf), dir, fd);
>+	if (!count)
>+		return false;
>+
>+	while ((l = strtok_r(_buf, "\n", &ctx))) {
>+		uint64_t val = 0;
>+		const char *v;
>+		int idx;
>+
>+		_buf = NULL;
>+
>+		if ((v = find_kv(l, "drm-driver", strlen("drm-driver")))) {
>+			strncpy(info->driver, v, sizeof(info->driver) - 1);
>+			good++;
>+		} else if ((v = find_kv(l, "drm-pdev", strlen("drm-pdev")))) {
>+			strncpy(info->pdev, v, sizeof(info->pdev) - 1);
>+		}  else if ((v = find_kv(l, "drm-client-id",
>+					 strlen("drm-client-id")))) {
>+			info->id = atol(v);
>+			good++;
>+		} else if (!strncmp(l, "drm-engine-", 11) &&
>+			   strncmp(l, "drm-engine-capacity-", 20)) {
>+			idx = parse_engine(l, info, strlen("drm-engine-"),
>+					   &val);
>+			if (idx >= 0) {
>+				if (!info->capacity[idx])
>+					info->capacity[idx] = 1;
>+				info->busy[idx] = val;
>+				info->num_engines++;
>+			}
>+		} else if (!strncmp(l, "drm-engine-capacity-", 20)) {
>+			idx = parse_engine(l, info,
>+					   strlen("drm-engine-capacity-"),
>+					   &val);
>+			if (idx >= 0)
>+				info->capacity[idx] = val;
>+		}
>+	}
>+
>+	if (good < 2 || !info->num_engines)
>+		return false; /* fdinfo format not as expected */
>+
>+	return true;
>+}
>+
>+bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info)
>+{
>+	char fd[64];
>+	int dir, ret;
>+	bool res;
>+
>+	ret = snprintf(fd, sizeof(fd), "%u", drm_fd);
>+	if (ret < 0 || ret == sizeof(fd))
>+		return false;
>+
>+	dir = open("/proc/self/fdinfo", O_DIRECTORY | O_RDONLY);
>+	if (dir < 0)
>+		return false;
>+
>+	res = __igt_parse_drm_fdinfo(dir, fd, info);
>+
>+	close(dir);
>+
>+	return res;
>+}
>diff --git a/lib/igt_drm_fdinfo.h b/lib/igt_drm_fdinfo.h
>new file mode 100644
>index 000000000000..c527bab9a204
>--- /dev/null
>+++ b/lib/igt_drm_fdinfo.h
>@@ -0,0 +1,48 @@
>+/*
>+ * 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.
>+ *
>+ */
>+
>+#ifndef IGT_DRM_FDINFO_H
>+#define IGT_DRM_FDINFO_H
>+
>+#include <sys/types.h>
>+#include <dirent.h>
>+#include <stdint.h>
>+#include <stdbool.h>
>+
>+struct drm_client_fdinfo {
>+	char driver[128];
>+	char pdev[128];
>+	unsigned long id;
>+
>+	unsigned int num_engines;
>+	unsigned int capacity[16];
>+	uint64_t busy[16];

I see 16 in a lot of places, wondering if it could use a #define?

Thanks,
Umesh

>+};
>+
>+bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info);
>+
>+bool __igt_parse_drm_fdinfo(int dir, const char *fd,
>+			    struct drm_client_fdinfo *info);
>+
>+#endif /* IGT_DRM_FDINFO_H */
>diff --git a/lib/meson.build b/lib/meson.build
>index 3e43316d1e36..d00817dc58bc 100644
>--- a/lib/meson.build
>+++ b/lib/meson.build
>@@ -16,6 +16,7 @@ lib_sources = [
> 	'igt_debugfs.c',
> 	'igt_device.c',
> 	'igt_device_scan.c',
>+	'igt_drm_fdinfo.c',
> 	'igt_aux.c',
> 	'igt_gt.c',
> 	'igt_halffloat.c',
>@@ -216,6 +217,12 @@ lib_igt_device_scan_build = static_library('igt_device_scan',
> lib_igt_device_scan = declare_dependency(link_with : lib_igt_device_scan_build,
> 				  include_directories : inc)
>
>+lib_igt_drm_fdinfo_build = static_library('igt_drm_fdinfo',
>+	['igt_drm_fdinfo.c'],
>+	include_directories : inc)
>+
>+lib_igt_drm_fdinfo = declare_dependency(link_with : lib_igt_drm_fdinfo_build,
>+				  include_directories : inc)
> i915_perf_files = [
>   'igt_list.c',
>   'i915/perf.c',
>-- 
>2.32.0
>

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [igt-dev] [PATCH i-g-t 02/11] tests/i915/drm_fdinfo: Basic and functional tests for GPU busyness exported via fdinfo
  2022-02-22 13:55   ` Tvrtko Ursulin
@ 2022-03-30 20:11     ` Umesh Nerlige Ramappa
  -1 siblings, 0 replies; 50+ messages in thread
From: Umesh Nerlige Ramappa @ 2022-03-30 20:11 UTC (permalink / raw)
  To: Tvrtko Ursulin; +Cc: igt-dev, Intel-gfx, dri-devel, Tvrtko Ursulin

This looks very similar to existing perf_pmu tests with the slight 
change that the busyness is now captured from the fdinfo.

lgtm,
Reviewed-by: Umesh Nerlige Ramappa <umesh.nerlige.ramappa@intel.com>

Umesh

On Tue, Feb 22, 2022 at 01:55:56PM +0000, Tvrtko Ursulin wrote:
>From: Tvrtko Ursulin <tvrtko.ursulin@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@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
>

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [Intel-gfx] [igt-dev] [PATCH i-g-t 02/11] tests/i915/drm_fdinfo: Basic and functional tests for GPU busyness exported via fdinfo
@ 2022-03-30 20:11     ` Umesh Nerlige Ramappa
  0 siblings, 0 replies; 50+ messages in thread
From: Umesh Nerlige Ramappa @ 2022-03-30 20:11 UTC (permalink / raw)
  To: Tvrtko Ursulin; +Cc: igt-dev, Intel-gfx, dri-devel

This looks very similar to existing perf_pmu tests with the slight 
change that the busyness is now captured from the fdinfo.

lgtm,
Reviewed-by: Umesh Nerlige Ramappa <umesh.nerlige.ramappa@intel.com>

Umesh

On Tue, Feb 22, 2022 at 01:55:56PM +0000, Tvrtko Ursulin wrote:
>From: Tvrtko Ursulin <tvrtko.ursulin@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@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
>

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [Intel-gfx] [PATCH i-g-t 01/11] lib: Helper library for parsing i915 fdinfo output
  2022-03-30 19:52     ` [igt-dev] " Umesh Nerlige Ramappa
@ 2022-03-31 13:22       ` Tvrtko Ursulin
  -1 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-03-31 13:22 UTC (permalink / raw)
  To: Umesh Nerlige Ramappa; +Cc: igt-dev, Intel-gfx, dri-devel


On 30/03/2022 20:52, Umesh Nerlige Ramappa wrote:
> On Tue, Feb 22, 2022 at 01:55:55PM +0000, Tvrtko Ursulin wrote:
>> From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>>
>> Tests and intel_gpu_top will share common code for parsing this file.
>>
>> Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>> ---
>> lib/igt_drm_fdinfo.c | 183 +++++++++++++++++++++++++++++++++++++++++++
>> lib/igt_drm_fdinfo.h |  48 ++++++++++++
>> lib/meson.build      |   7 ++
>> 3 files changed, 238 insertions(+)
>> create mode 100644 lib/igt_drm_fdinfo.c
>> create mode 100644 lib/igt_drm_fdinfo.h
>>
>> diff --git a/lib/igt_drm_fdinfo.c b/lib/igt_drm_fdinfo.c
>> new file mode 100644
>> index 000000000000..28c1bdbda08e
>> --- /dev/null
>> +++ b/lib/igt_drm_fdinfo.c
>> @@ -0,0 +1,183 @@
>> +/*
>> + * 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 <ctype.h>
>> +#include <sys/types.h>
>> +#include <sys/stat.h>
>> +#include <fcntl.h>
>> +#include <stdio.h>
>> +#include <string.h>
>> +#include <stdlib.h>
>> +#include <unistd.h>
>> +
>> +#include "drmtest.h"
>> +
>> +#include "igt_drm_fdinfo.h"
>> +
>> +static size_t read_fdinfo(char *buf, const size_t sz, int at, const 
>> char *name)
>> +{
>> +    size_t count;
>> +    int fd;
>> +
>> +    fd = openat(at, name, O_RDONLY);
>> +    if (fd < 0)
>> +        return 0;
>> +
>> +    buf[sz - 1] = 0;
> 
> Wondering if this ^ should be after the read() in case 4096 bytes are read.

Yes, I guess even before and after so it's guaranteed to be null 
terminated no matter what.

> 
>> +    count = read(fd, buf, sz);
>> +    close(fd);
>> +
>> +    return count;
>> +}
>> +
>> +static int parse_engine(char *line, struct drm_client_fdinfo *info,
>> +            size_t prefix_len, uint64_t *val)
>> +{
>> +    static const char *e2class[] = {
>> +        "render",
>> +        "copy",
>> +        "video",
>> +        "video-enhance",
>> +    };
>> +    ssize_t name_len;
>> +    char *name, *p;
>> +    int found = -1;
>> +    unsigned int i;
>> +
>> +    p = index(line, ':');
>> +    if (!p || p == line)
>> +        return -1;
>> +
>> +    name_len = p - line - prefix_len;
>> +    if (name_len < 1)
>> +        return -1;
>> +
>> +    name = line + prefix_len;
>> +
>> +    for (i = 0; i < ARRAY_SIZE(e2class); i++) {
>> +        if (!strncmp(name, e2class[i], name_len)) {
>> +            found = i;
>> +            break;
>> +        }
>> +    }
>> +
>> +    if (found >= 0) {
>> +        while (*++p && isspace(*p));
>> +        *val = strtoull(p, NULL, 10);
>> +    }
>> +
>> +    return found;
>> +}
>> +
>> +static const char *find_kv(const char *buf, const char *key, size_t 
>> keylen)
>> +{
>> +    const char *p = buf;
>> +
>> +    p = index(buf, ':');
>> +    if (!p || p == buf)
>> +        return NULL;
>> +
>> +    if ((p - buf) != keylen)
>> +        return NULL;
>> +
>> +    while (*++p && isspace(*p));
>> +    if (*p && !strncmp(buf, key, keylen))
> 
> nit: why not just do the strncmp early in this function since buf, key, 
> keylen have not changed?

I see what you mean, first check that the key/prefix matches and do the 
other checks, right?

I honestly don't remember if there was a special reason why I laid it 
out like it is. I know it is an annoyingly hot path and I did do many 
tweaks to bring the CPU utilisation low.

But thinking about it now, the strncmp at the start should indeed be 
more efficient so I will change it.

> 
>> +        return p;
>> +
>> +    return NULL;
>> +}
>> +
>> +bool
>> +__igt_parse_drm_fdinfo(int dir, const char *fd, struct 
>> drm_client_fdinfo *info)
>> +{
>> +    char buf[4096], *_buf = buf;
>> +    char *l, *ctx = NULL;
>> +    unsigned int good = 0;
>> +    size_t count;
>> +
> 
> Should buf be zeroed out here?

Right, either here or in read_fdinfo as mentioned above.

>> +    count = read_fdinfo(buf, sizeof(buf), dir, fd);
>> +    if (!count)
>> +        return false;
>> +
>> +    while ((l = strtok_r(_buf, "\n", &ctx))) {
>> +        uint64_t val = 0;
>> +        const char *v;
>> +        int idx;
>> +
>> +        _buf = NULL;
>> +
>> +        if ((v = find_kv(l, "drm-driver", strlen("drm-driver")))) {
>> +            strncpy(info->driver, v, sizeof(info->driver) - 1);
>> +            good++;
>> +        } else if ((v = find_kv(l, "drm-pdev", strlen("drm-pdev")))) {
>> +            strncpy(info->pdev, v, sizeof(info->pdev) - 1);
>> +        }  else if ((v = find_kv(l, "drm-client-id",
>> +                     strlen("drm-client-id")))) {
>> +            info->id = atol(v);
>> +            good++;
>> +        } else if (!strncmp(l, "drm-engine-", 11) &&
>> +               strncmp(l, "drm-engine-capacity-", 20)) {
>> +            idx = parse_engine(l, info, strlen("drm-engine-"),
>> +                       &val);
>> +            if (idx >= 0) {
>> +                if (!info->capacity[idx])
>> +                    info->capacity[idx] = 1;
>> +                info->busy[idx] = val;
>> +                info->num_engines++;
>> +            }
>> +        } else if (!strncmp(l, "drm-engine-capacity-", 20)) {
>> +            idx = parse_engine(l, info,
>> +                       strlen("drm-engine-capacity-"),
>> +                       &val);
>> +            if (idx >= 0)
>> +                info->capacity[idx] = val;
>> +        }
>> +    }
>> +
>> +    if (good < 2 || !info->num_engines)
>> +        return false; /* fdinfo format not as expected */
>> +
>> +    return true;
>> +}
>> +
>> +bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info)
>> +{
>> +    char fd[64];
>> +    int dir, ret;
>> +    bool res;
>> +
>> +    ret = snprintf(fd, sizeof(fd), "%u", drm_fd);
>> +    if (ret < 0 || ret == sizeof(fd))
>> +        return false;
>> +
>> +    dir = open("/proc/self/fdinfo", O_DIRECTORY | O_RDONLY);
>> +    if (dir < 0)
>> +        return false;
>> +
>> +    res = __igt_parse_drm_fdinfo(dir, fd, info);
>> +
>> +    close(dir);
>> +
>> +    return res;
>> +}
>> diff --git a/lib/igt_drm_fdinfo.h b/lib/igt_drm_fdinfo.h
>> new file mode 100644
>> index 000000000000..c527bab9a204
>> --- /dev/null
>> +++ b/lib/igt_drm_fdinfo.h
>> @@ -0,0 +1,48 @@
>> +/*
>> + * 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.
>> + *
>> + */
>> +
>> +#ifndef IGT_DRM_FDINFO_H
>> +#define IGT_DRM_FDINFO_H
>> +
>> +#include <sys/types.h>
>> +#include <dirent.h>
>> +#include <stdint.h>
>> +#include <stdbool.h>
>> +
>> +struct drm_client_fdinfo {
>> +    char driver[128];
>> +    char pdev[128];
>> +    unsigned long id;
>> +
>> +    unsigned int num_engines;
>> +    unsigned int capacity[16];
>> +    uint64_t busy[16];
> 
> I see 16 in a lot of places, wondering if it could use a #define?

I can see them here and one mention in igt_drm_clients. The latter it 
looks I can un-export igt_drm_client_update and make it take struct 
drm_client_fdinfo to better hide this.

And for here I will add a define like DRM_CLIENT_FDINFO_MAX_ENGINES.

Regards,

Tvrtko

> 
> Thanks,
> Umesh
> 
>> +};
>> +
>> +bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info);
>> +
>> +bool __igt_parse_drm_fdinfo(int dir, const char *fd,
>> +                struct drm_client_fdinfo *info);
>> +
>> +#endif /* IGT_DRM_FDINFO_H */
>> diff --git a/lib/meson.build b/lib/meson.build
>> index 3e43316d1e36..d00817dc58bc 100644
>> --- a/lib/meson.build
>> +++ b/lib/meson.build
>> @@ -16,6 +16,7 @@ lib_sources = [
>>     'igt_debugfs.c',
>>     'igt_device.c',
>>     'igt_device_scan.c',
>> +    'igt_drm_fdinfo.c',
>>     'igt_aux.c',
>>     'igt_gt.c',
>>     'igt_halffloat.c',
>> @@ -216,6 +217,12 @@ lib_igt_device_scan_build = 
>> static_library('igt_device_scan',
>> lib_igt_device_scan = declare_dependency(link_with : 
>> lib_igt_device_scan_build,
>>                   include_directories : inc)
>>
>> +lib_igt_drm_fdinfo_build = static_library('igt_drm_fdinfo',
>> +    ['igt_drm_fdinfo.c'],
>> +    include_directories : inc)
>> +
>> +lib_igt_drm_fdinfo = declare_dependency(link_with : 
>> lib_igt_drm_fdinfo_build,
>> +                  include_directories : inc)
>> i915_perf_files = [
>>   'igt_list.c',
>>   'i915/perf.c',
>> -- 
>> 2.32.0
>>

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [igt-dev] [Intel-gfx] [PATCH i-g-t 01/11] lib: Helper library for parsing i915 fdinfo output
@ 2022-03-31 13:22       ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-03-31 13:22 UTC (permalink / raw)
  To: Umesh Nerlige Ramappa; +Cc: igt-dev, Intel-gfx, dri-devel


On 30/03/2022 20:52, Umesh Nerlige Ramappa wrote:
> On Tue, Feb 22, 2022 at 01:55:55PM +0000, Tvrtko Ursulin wrote:
>> From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>>
>> Tests and intel_gpu_top will share common code for parsing this file.
>>
>> Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>> ---
>> lib/igt_drm_fdinfo.c | 183 +++++++++++++++++++++++++++++++++++++++++++
>> lib/igt_drm_fdinfo.h |  48 ++++++++++++
>> lib/meson.build      |   7 ++
>> 3 files changed, 238 insertions(+)
>> create mode 100644 lib/igt_drm_fdinfo.c
>> create mode 100644 lib/igt_drm_fdinfo.h
>>
>> diff --git a/lib/igt_drm_fdinfo.c b/lib/igt_drm_fdinfo.c
>> new file mode 100644
>> index 000000000000..28c1bdbda08e
>> --- /dev/null
>> +++ b/lib/igt_drm_fdinfo.c
>> @@ -0,0 +1,183 @@
>> +/*
>> + * 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 <ctype.h>
>> +#include <sys/types.h>
>> +#include <sys/stat.h>
>> +#include <fcntl.h>
>> +#include <stdio.h>
>> +#include <string.h>
>> +#include <stdlib.h>
>> +#include <unistd.h>
>> +
>> +#include "drmtest.h"
>> +
>> +#include "igt_drm_fdinfo.h"
>> +
>> +static size_t read_fdinfo(char *buf, const size_t sz, int at, const 
>> char *name)
>> +{
>> +    size_t count;
>> +    int fd;
>> +
>> +    fd = openat(at, name, O_RDONLY);
>> +    if (fd < 0)
>> +        return 0;
>> +
>> +    buf[sz - 1] = 0;
> 
> Wondering if this ^ should be after the read() in case 4096 bytes are read.

Yes, I guess even before and after so it's guaranteed to be null 
terminated no matter what.

> 
>> +    count = read(fd, buf, sz);
>> +    close(fd);
>> +
>> +    return count;
>> +}
>> +
>> +static int parse_engine(char *line, struct drm_client_fdinfo *info,
>> +            size_t prefix_len, uint64_t *val)
>> +{
>> +    static const char *e2class[] = {
>> +        "render",
>> +        "copy",
>> +        "video",
>> +        "video-enhance",
>> +    };
>> +    ssize_t name_len;
>> +    char *name, *p;
>> +    int found = -1;
>> +    unsigned int i;
>> +
>> +    p = index(line, ':');
>> +    if (!p || p == line)
>> +        return -1;
>> +
>> +    name_len = p - line - prefix_len;
>> +    if (name_len < 1)
>> +        return -1;
>> +
>> +    name = line + prefix_len;
>> +
>> +    for (i = 0; i < ARRAY_SIZE(e2class); i++) {
>> +        if (!strncmp(name, e2class[i], name_len)) {
>> +            found = i;
>> +            break;
>> +        }
>> +    }
>> +
>> +    if (found >= 0) {
>> +        while (*++p && isspace(*p));
>> +        *val = strtoull(p, NULL, 10);
>> +    }
>> +
>> +    return found;
>> +}
>> +
>> +static const char *find_kv(const char *buf, const char *key, size_t 
>> keylen)
>> +{
>> +    const char *p = buf;
>> +
>> +    p = index(buf, ':');
>> +    if (!p || p == buf)
>> +        return NULL;
>> +
>> +    if ((p - buf) != keylen)
>> +        return NULL;
>> +
>> +    while (*++p && isspace(*p));
>> +    if (*p && !strncmp(buf, key, keylen))
> 
> nit: why not just do the strncmp early in this function since buf, key, 
> keylen have not changed?

I see what you mean, first check that the key/prefix matches and do the 
other checks, right?

I honestly don't remember if there was a special reason why I laid it 
out like it is. I know it is an annoyingly hot path and I did do many 
tweaks to bring the CPU utilisation low.

But thinking about it now, the strncmp at the start should indeed be 
more efficient so I will change it.

> 
>> +        return p;
>> +
>> +    return NULL;
>> +}
>> +
>> +bool
>> +__igt_parse_drm_fdinfo(int dir, const char *fd, struct 
>> drm_client_fdinfo *info)
>> +{
>> +    char buf[4096], *_buf = buf;
>> +    char *l, *ctx = NULL;
>> +    unsigned int good = 0;
>> +    size_t count;
>> +
> 
> Should buf be zeroed out here?

Right, either here or in read_fdinfo as mentioned above.

>> +    count = read_fdinfo(buf, sizeof(buf), dir, fd);
>> +    if (!count)
>> +        return false;
>> +
>> +    while ((l = strtok_r(_buf, "\n", &ctx))) {
>> +        uint64_t val = 0;
>> +        const char *v;
>> +        int idx;
>> +
>> +        _buf = NULL;
>> +
>> +        if ((v = find_kv(l, "drm-driver", strlen("drm-driver")))) {
>> +            strncpy(info->driver, v, sizeof(info->driver) - 1);
>> +            good++;
>> +        } else if ((v = find_kv(l, "drm-pdev", strlen("drm-pdev")))) {
>> +            strncpy(info->pdev, v, sizeof(info->pdev) - 1);
>> +        }  else if ((v = find_kv(l, "drm-client-id",
>> +                     strlen("drm-client-id")))) {
>> +            info->id = atol(v);
>> +            good++;
>> +        } else if (!strncmp(l, "drm-engine-", 11) &&
>> +               strncmp(l, "drm-engine-capacity-", 20)) {
>> +            idx = parse_engine(l, info, strlen("drm-engine-"),
>> +                       &val);
>> +            if (idx >= 0) {
>> +                if (!info->capacity[idx])
>> +                    info->capacity[idx] = 1;
>> +                info->busy[idx] = val;
>> +                info->num_engines++;
>> +            }
>> +        } else if (!strncmp(l, "drm-engine-capacity-", 20)) {
>> +            idx = parse_engine(l, info,
>> +                       strlen("drm-engine-capacity-"),
>> +                       &val);
>> +            if (idx >= 0)
>> +                info->capacity[idx] = val;
>> +        }
>> +    }
>> +
>> +    if (good < 2 || !info->num_engines)
>> +        return false; /* fdinfo format not as expected */
>> +
>> +    return true;
>> +}
>> +
>> +bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info)
>> +{
>> +    char fd[64];
>> +    int dir, ret;
>> +    bool res;
>> +
>> +    ret = snprintf(fd, sizeof(fd), "%u", drm_fd);
>> +    if (ret < 0 || ret == sizeof(fd))
>> +        return false;
>> +
>> +    dir = open("/proc/self/fdinfo", O_DIRECTORY | O_RDONLY);
>> +    if (dir < 0)
>> +        return false;
>> +
>> +    res = __igt_parse_drm_fdinfo(dir, fd, info);
>> +
>> +    close(dir);
>> +
>> +    return res;
>> +}
>> diff --git a/lib/igt_drm_fdinfo.h b/lib/igt_drm_fdinfo.h
>> new file mode 100644
>> index 000000000000..c527bab9a204
>> --- /dev/null
>> +++ b/lib/igt_drm_fdinfo.h
>> @@ -0,0 +1,48 @@
>> +/*
>> + * 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.
>> + *
>> + */
>> +
>> +#ifndef IGT_DRM_FDINFO_H
>> +#define IGT_DRM_FDINFO_H
>> +
>> +#include <sys/types.h>
>> +#include <dirent.h>
>> +#include <stdint.h>
>> +#include <stdbool.h>
>> +
>> +struct drm_client_fdinfo {
>> +    char driver[128];
>> +    char pdev[128];
>> +    unsigned long id;
>> +
>> +    unsigned int num_engines;
>> +    unsigned int capacity[16];
>> +    uint64_t busy[16];
> 
> I see 16 in a lot of places, wondering if it could use a #define?

I can see them here and one mention in igt_drm_clients. The latter it 
looks I can un-export igt_drm_client_update and make it take struct 
drm_client_fdinfo to better hide this.

And for here I will add a define like DRM_CLIENT_FDINFO_MAX_ENGINES.

Regards,

Tvrtko

> 
> Thanks,
> Umesh
> 
>> +};
>> +
>> +bool igt_parse_drm_fdinfo(int drm_fd, struct drm_client_fdinfo *info);
>> +
>> +bool __igt_parse_drm_fdinfo(int dir, const char *fd,
>> +                struct drm_client_fdinfo *info);
>> +
>> +#endif /* IGT_DRM_FDINFO_H */
>> diff --git a/lib/meson.build b/lib/meson.build
>> index 3e43316d1e36..d00817dc58bc 100644
>> --- a/lib/meson.build
>> +++ b/lib/meson.build
>> @@ -16,6 +16,7 @@ lib_sources = [
>>     'igt_debugfs.c',
>>     'igt_device.c',
>>     'igt_device_scan.c',
>> +    'igt_drm_fdinfo.c',
>>     'igt_aux.c',
>>     'igt_gt.c',
>>     'igt_halffloat.c',
>> @@ -216,6 +217,12 @@ lib_igt_device_scan_build = 
>> static_library('igt_device_scan',
>> lib_igt_device_scan = declare_dependency(link_with : 
>> lib_igt_device_scan_build,
>>                   include_directories : inc)
>>
>> +lib_igt_drm_fdinfo_build = static_library('igt_drm_fdinfo',
>> +    ['igt_drm_fdinfo.c'],
>> +    include_directories : inc)
>> +
>> +lib_igt_drm_fdinfo = declare_dependency(link_with : 
>> lib_igt_drm_fdinfo_build,
>> +                  include_directories : inc)
>> i915_perf_files = [
>>   'igt_list.c',
>>   'i915/perf.c',
>> -- 
>> 2.32.0
>>

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [igt-dev] [PATCH i-g-t 02/11] tests/i915/drm_fdinfo: Basic and functional tests for GPU busyness exported via fdinfo
  2022-03-30 20:11     ` [Intel-gfx] " Umesh Nerlige Ramappa
@ 2022-03-31 13:23       ` Tvrtko Ursulin
  -1 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-03-31 13:23 UTC (permalink / raw)
  To: Umesh Nerlige Ramappa; +Cc: igt-dev, Intel-gfx, dri-devel, Tvrtko Ursulin


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@intel.com>

Thanks!

Regards,

Tvrtko

> 
> Umesh
> 
> On Tue, Feb 22, 2022 at 01:55:56PM +0000, Tvrtko Ursulin wrote:
>> From: Tvrtko Ursulin <tvrtko.ursulin@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@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
>>

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [Intel-gfx] [igt-dev] [PATCH i-g-t 02/11] tests/i915/drm_fdinfo: Basic and functional tests for GPU busyness exported via fdinfo
@ 2022-03-31 13:23       ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-03-31 13:23 UTC (permalink / raw)
  To: Umesh Nerlige Ramappa; +Cc: igt-dev, Intel-gfx, dri-devel


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@intel.com>

Thanks!

Regards,

Tvrtko

> 
> Umesh
> 
> On Tue, Feb 22, 2022 at 01:55:56PM +0000, Tvrtko Ursulin wrote:
>> From: Tvrtko Ursulin <tvrtko.ursulin@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@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
>>

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [igt-dev] [PATCH i-g-t 03/11] intel-gpu-top: Add support for per client stats
  2022-02-22 13:55   ` [Intel-gfx] " Tvrtko Ursulin
@ 2022-03-31 22:08     ` Umesh Nerlige Ramappa
  -1 siblings, 0 replies; 50+ messages in thread
From: Umesh Nerlige Ramappa @ 2022-03-31 22:08 UTC (permalink / raw)
  To: Tvrtko Ursulin; +Cc: igt-dev, Intel-gfx, dri-devel, Tvrtko Ursulin

lgtm, I just have a few nits and questions below:

Regardless, this is

Reviewed-by: Umesh Nerlige Ramappa <umesh.nerlige.ramappa@intel.com>

Umesh

On Tue, Feb 22, 2022 at 01:55:57PM +0000, Tvrtko Ursulin wrote:
>From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>
>Use the i915 exported data in /proc/<pid>/fdinfo to show GPU utilization
>per DRM client.
>
>Example of the output:
>
>intel-gpu-top: Intel Tigerlake (Gen12) @ /dev/dri/card0 -  220/ 221 MHz
>    70% RC6;  0.62/ 7.08 W;      760 irqs/s
>
>         ENGINES     BUSY                                 MI_SEMA MI_WAIT
>       Render/3D   23.06% |██████▊                      |      0%      0%
>         Blitter    0.00% |                             |      0%      0%
>           Video    5.40% |█▋                           |      0%      0%
>    VideoEnhance   20.67% |██████                       |      0%      0%
>
>   PID              NAME  Render/3D    Blitter      Video    VideoEnhance
>  3082               mpv |          ||          ||▌         ||██        |
>  3117         neverball |█▉        ||          ||          ||          |
>     1           systemd |▍         ||          ||          ||          |
>  2338       gnome-shell |          ||          ||          ||          |
>
>Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>---
> man/intel_gpu_top.rst |   4 +
> tools/intel_gpu_top.c | 801 +++++++++++++++++++++++++++++++++++++++++-
> tools/meson.build     |   2 +-
> 3 files changed, 804 insertions(+), 3 deletions(-)
>
>diff --git a/man/intel_gpu_top.rst b/man/intel_gpu_top.rst
>index b3b765b05feb..f4dbfc5b44d9 100644
>--- a/man/intel_gpu_top.rst
>+++ b/man/intel_gpu_top.rst
>@@ -56,6 +56,10 @@ Supported keys:
>     'q'    Exit from the tool.
>     'h'    Show interactive help.
>     '1'    Toggle between aggregated engine class and physical engine mode.
>+    'n'    Toggle display of numeric client busyness overlay.
>+    's'    Toggle between sort modes (runtime, total runtime, pid, client id).
>+    'i'    Toggle display of clients which used no GPU time.
>+    'H'    Toggle between per PID aggregation and individual clients.
>
> DEVICE SELECTION
> ================
>diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
>index bc11fce2bb1e..73815cdea8aa 100644
>--- a/tools/intel_gpu_top.c
>+++ b/tools/intel_gpu_top.c
>@@ -43,8 +43,10 @@
> #include <sys/types.h>
> #include <unistd.h>
> #include <termios.h>
>+#include <sys/sysmacros.h>
>
> #include "igt_perf.h"
>+#include "igt_drm_fdinfo.h"
>
> #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
>
>@@ -311,7 +313,8 @@ static int engine_cmp(const void *__a, const void *__b)
> 		return a->instance - b->instance;
> }
>
>-#define is_igpu_pci(x) (strcmp(x, "0000:00:02.0") == 0)
>+#define IGPU_PCI "0000:00:02.0"
>+#define is_igpu_pci(x) (strcmp(x, IGPU_PCI) == 0)
> #define is_igpu(x) (strcmp(x, "i915") == 0)
>
> static struct engines *discover_engines(char *device)
>@@ -635,6 +638,547 @@ static void pmu_sample(struct engines *engines)
> 	}
> }
>
>+enum client_status {
>+	FREE = 0, /* mbz */
>+	ALIVE,
>+	PROBE
>+};
>+
>+struct clients;
>+
>+struct client {
>+	struct clients *clients;
>+
>+	enum client_status status;
>+	unsigned int id;
>+	unsigned int pid;
>+	char name[24];
>+	char print_name[24];
>+	unsigned int samples;
>+	unsigned long total_runtime;
>+	unsigned long last_runtime;
>+	unsigned long *val;
>+	uint64_t *last;
>+};
>+
>+struct clients {
>+	unsigned int num_clients;
>+	unsigned int active_clients;
>+
>+	unsigned int num_classes;
>+	struct engine_class *class;
>+
>+	char pci_slot[64];
>+
>+	struct client *client;
>+};
>+
>+#define for_each_client(clients, c, tmp) \
>+	for ((tmp) = (clients)->num_clients, c = (clients)->client; \
>+	     (tmp > 0); (tmp)--, (c)++)
>+
>+static struct clients *init_clients(const char *pci_slot)
>+{
>+	struct clients *clients;
>+
>+	clients = malloc(sizeof(*clients));
>+	if (!clients)
>+		return NULL;
>+
>+	memset(clients, 0, sizeof(*clients));
>+
>+	strncpy(clients->pci_slot, pci_slot, sizeof(clients->pci_slot));
>+
>+	return clients;
>+}
>+
>+static struct client *
>+find_client(struct clients *clients, enum client_status status, unsigned int id)
>+{
>+	unsigned int start, num;
>+	struct client *c;
>+
>+	start = status == FREE ? clients->active_clients : 0; /* Free block at the end. */
>+	num = clients->num_clients - start;
>+
>+	for (c = &clients->client[start]; num; c++, num--) {
>+		if (status != c->status)
>+			continue;
>+
>+		if (status == FREE || c->id == id)
>+			return c;
>+	}
>+
>+	return NULL;
>+}
>+
>+static void
>+update_client(struct client *c, unsigned int pid, char *name, uint64_t val[16])
>+{
>+	unsigned int i;
>+
>+	if (c->pid != pid)
>+		c->pid = pid;
>+
>+	if (strcmp(c->name, name)) {
>+		char *p;
>+
>+		strncpy(c->name, name, sizeof(c->name) - 1);
>+		strncpy(c->print_name, name, sizeof(c->print_name) - 1);
>+
>+		p = c->print_name;
>+		while (*p) {
>+			if (!isprint(*p))
>+				*p = '*';
>+			p++;
>+		}
>+	}
>+
>+	c->last_runtime = 0;
>+	c->total_runtime = 0;
>+

assert(c->clients->num_classes <= 16);

>+	for (i = 0; i < c->clients->num_classes; i++) {
>+		if (val[i] < c->last[i])
>+			continue; /* It will catch up soon. */
>+
>+		c->total_runtime += val[i];
As far as busyness is concerned c->total_runtime += val[i] should be it.  

>+		c->val[i] = val[i] - c->last[i];
>+		c->last_runtime += c->val[i];
>+		c->last[i] = val[i];

Can you describe what these 3 lines are doing?

>+	}
>+
>+	c->samples++;
>+	c->status = ALIVE;
>+}
>+
>+static void
>+add_client(struct clients *clients, unsigned int id, unsigned int pid,
>+	   char *name, uint64_t busy[16])
>+{
>+	struct client *c;
>+
>+	assert(!find_client(clients, ALIVE, id));
>+
>+	c = find_client(clients, FREE, 0);
>+	if (!c) {
>+		unsigned int idx = clients->num_clients;
>+
>+		clients->num_clients += (clients->num_clients + 2) / 2;
>+		clients->client = realloc(clients->client,
>+					  clients->num_clients * sizeof(*c));
>+		assert(clients->client);
>+
>+		c = &clients->client[idx];
>+		memset(c, 0, (clients->num_clients - idx) * sizeof(*c));
>+	}
>+
>+	c->id = id;
>+	c->clients = clients;
>+	c->val = calloc(clients->num_classes, sizeof(c->val));
>+	c->last = calloc(clients->num_classes, sizeof(c->last));
>+	assert(c->val && c->last);
>+
>+	update_client(c, pid, name, busy);
>+}
>+
>+static void free_client(struct client *c)
>+{
>+	free(c->val);
>+	free(c->last);
>+	memset(c, 0, sizeof(*c));
>+}
>+
>+static int client_last_cmp(const void *_a, const void *_b)
>+{
>+	const struct client *a = _a;
>+	const struct client *b = _b;
>+	long tot_a, tot_b;
>+
>+	/*
>+	 * Sort clients in descending order of runtime in the previous sampling
>+	 * period for active ones, followed by inactive. Tie-breaker is client
>+	 * id.
>+	 */
>+
>+	tot_a = a->status == ALIVE ? a->last_runtime : -1;
>+	tot_b = b->status == ALIVE ? b->last_runtime : -1;
>+
>+	tot_b -= tot_a;
>+	if (tot_b > 0)
>+		return 1;
>+	if (tot_b < 0)
>+		return -1;
>+
>+	return (int)b->id - a->id;

nit: the compare functions could use a single if, if you agree on just 
returning tot_b.

	if (!tot_b)
		return (int)b->id - a->id;

	return tot_b;

>+}
>+
>+static int client_total_cmp(const void *_a, const void *_b)
>+{
>+	const struct client *a = _a;
>+	const struct client *b = _b;
>+	long tot_a, tot_b;
>+
>+	tot_a = a->status == ALIVE ? a->total_runtime : -1;
>+	tot_b = b->status == ALIVE ? b->total_runtime : -1;
>+
>+	tot_b -= tot_a;
>+	if (tot_b > 0)
>+		return 1;
>+	if (tot_b < 0)
>+		return -1;
>+
>+	return (int)b->id - a->id;
>+}
>+
>+static int client_id_cmp(const void *_a, const void *_b)
>+{
>+	const struct client *a = _a;
>+	const struct client *b = _b;
>+	int id_a, id_b;
>+
>+	id_a = a->status == ALIVE ? a->id : -1;
>+	id_b = b->status == ALIVE ? b->id : -1;
>+
>+	id_b -= id_a;
>+	if (id_b > 0)
>+		return 1;
>+	if (id_b < 0)
>+		return -1;
>+
>+	return (int)b->id - a->id;
>+}
>+
>+static int client_pid_cmp(const void *_a, const void *_b)
>+{
>+	const struct client *a = _a;
>+	const struct client *b = _b;
>+	int pid_a, pid_b;
>+
>+	pid_a = a->status == ALIVE ? a->pid : INT_MAX;
>+	pid_b = b->status == ALIVE ? b->pid : INT_MAX;
>+
>+	pid_b -= pid_a;
>+	if (pid_b > 0)
>+		return -1;
>+	if (pid_b < 0)
>+		return 1;
>+
>+	return (int)a->id - b->id;
>+}
>+
>+static int (*client_cmp)(const void *, const void *) = client_last_cmp;
>+
>+static struct clients *sort_clients(struct clients *clients,
>+				    int (*cmp)(const void *, const void *))
>+{
>+	unsigned int active, free;
>+	struct client *c;
>+	int tmp;
>+
>+	if (!clients)
>+		return clients;
>+
>+	qsort(clients->client, clients->num_clients, sizeof(*clients->client),
>+	      cmp);
>+
>+	/* Trim excessive array space. */
>+	active = 0;
>+	for_each_client(clients, c, tmp) {
>+		if (c->status != ALIVE)
>+			break; /* Active clients are first in the array. */
>+		active++;
>+	}
>+
>+	clients->active_clients = active;
>+
>+	free = clients->num_clients - active;
>+	if (free > clients->num_clients / 2) {
>+		active = clients->num_clients - free / 2;
>+		if (active != clients->num_clients) {
>+			clients->num_clients = active;
>+			clients->client = realloc(clients->client,
>+						  clients->num_clients *
>+						  sizeof(*c));
>+		}
>+	}
>+
>+	return clients;
>+}
>+
>+static bool aggregate_pids = true;
>+
>+static struct clients *display_clients(struct clients *clients)
>+{
>+	struct client *ac, *c, *cp = NULL;
>+	struct clients *aggregated;
>+	int tmp, num = 0;
>+
>+	if (!aggregate_pids)
>+		goto out;
>+
>+	/* Sort by pid first to make it easy to aggregate while walking. */
>+	sort_clients(clients, client_pid_cmp);
>+
>+	aggregated = calloc(1, sizeof(*clients));
>+	assert(aggregated);
>+
>+	ac = calloc(clients->num_clients, sizeof(*c));
>+	assert(ac);
>+
>+	aggregated->num_classes = clients->num_classes;
>+	aggregated->class = clients->class;
>+	aggregated->client = ac;
>+
>+	for_each_client(clients, c, tmp) {
>+		unsigned int i;
>+
>+		if (c->status == FREE)
>+			break;
>+
>+		assert(c->status == ALIVE);
>+
>+		if ((cp && c->pid != cp->pid) || !cp) {

same as: if (!cp || c->pid != cp->pid), fine either ways

>+			ac = &aggregated->client[num++];
>+
>+			/* New pid. */
>+			ac->clients = aggregated;
>+			ac->status = ALIVE;
>+			ac->id = -c->pid;
>+			ac->pid = c->pid;
>+			strcpy(ac->name, c->name);
>+			strcpy(ac->print_name, c->print_name);
>+			ac->val = calloc(clients->num_classes,
>+					 sizeof(ac->val[0]));
>+			assert(ac->val);
>+			ac->samples = 1;
>+		}
>+
>+		cp = c;
>+
>+		if (c->samples < 2)
>+			continue;
>+
>+		ac->samples = 2; /* All what matters for display. */
>+		ac->total_runtime += c->total_runtime;
>+		ac->last_runtime += c->last_runtime;
>+
>+		for (i = 0; i < clients->num_classes; i++)
>+			ac->val[i] += c->val[i];
>+	}
>+
>+	aggregated->num_clients = num;
>+	aggregated->active_clients = num;
>+
>+	clients = aggregated;
>+
>+out:
>+	return sort_clients(clients, client_cmp);
>+}
>+
>+static void free_clients(struct clients *clients)
>+{
>+	struct client *c;
>+	unsigned int tmp;
>+
>+	for_each_client(clients, c, tmp) {
>+		free(c->val);
>+		free(c->last);
>+	}
>+
>+	free(clients->client);
>+	free(clients);
>+}
>+
>+static bool is_drm_fd(DIR *fd_dir, const char *name)
>+{
>+	struct stat stat;
>+	int ret;
>+
>+	ret = fstatat(dirfd(fd_dir), name, &stat, 0);
>+
>+	return ret == 0 &&
>+	       (stat.st_mode & S_IFMT) == S_IFCHR &&
>+	       major(stat.st_rdev) == 226;
>+}
>+
>+static bool get_task_name(const char *buffer, char *out, unsigned long sz)
>+{
>+	char *s = index(buffer, '(');
>+	char *e = rindex(buffer, ')');
>+	unsigned int len;
>+
>+	if (!s || !e)
>+		return false;

maybe assert(e > s);

>+
>+	len = --e - ++s + 1;

len = e - ++s;

Thanks,
Umesh

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [Intel-gfx] [igt-dev] [PATCH i-g-t 03/11] intel-gpu-top: Add support for per client stats
@ 2022-03-31 22:08     ` Umesh Nerlige Ramappa
  0 siblings, 0 replies; 50+ messages in thread
From: Umesh Nerlige Ramappa @ 2022-03-31 22:08 UTC (permalink / raw)
  To: Tvrtko Ursulin; +Cc: igt-dev, Intel-gfx, dri-devel

lgtm, I just have a few nits and questions below:

Regardless, this is

Reviewed-by: Umesh Nerlige Ramappa <umesh.nerlige.ramappa@intel.com>

Umesh

On Tue, Feb 22, 2022 at 01:55:57PM +0000, Tvrtko Ursulin wrote:
>From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>
>Use the i915 exported data in /proc/<pid>/fdinfo to show GPU utilization
>per DRM client.
>
>Example of the output:
>
>intel-gpu-top: Intel Tigerlake (Gen12) @ /dev/dri/card0 -  220/ 221 MHz
>    70% RC6;  0.62/ 7.08 W;      760 irqs/s
>
>         ENGINES     BUSY                                 MI_SEMA MI_WAIT
>       Render/3D   23.06% |██████▊                      |      0%      0%
>         Blitter    0.00% |                             |      0%      0%
>           Video    5.40% |█▋                           |      0%      0%
>    VideoEnhance   20.67% |██████                       |      0%      0%
>
>   PID              NAME  Render/3D    Blitter      Video    VideoEnhance
>  3082               mpv |          ||          ||▌         ||██        |
>  3117         neverball |█▉        ||          ||          ||          |
>     1           systemd |▍         ||          ||          ||          |
>  2338       gnome-shell |          ||          ||          ||          |
>
>Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>---
> man/intel_gpu_top.rst |   4 +
> tools/intel_gpu_top.c | 801 +++++++++++++++++++++++++++++++++++++++++-
> tools/meson.build     |   2 +-
> 3 files changed, 804 insertions(+), 3 deletions(-)
>
>diff --git a/man/intel_gpu_top.rst b/man/intel_gpu_top.rst
>index b3b765b05feb..f4dbfc5b44d9 100644
>--- a/man/intel_gpu_top.rst
>+++ b/man/intel_gpu_top.rst
>@@ -56,6 +56,10 @@ Supported keys:
>     'q'    Exit from the tool.
>     'h'    Show interactive help.
>     '1'    Toggle between aggregated engine class and physical engine mode.
>+    'n'    Toggle display of numeric client busyness overlay.
>+    's'    Toggle between sort modes (runtime, total runtime, pid, client id).
>+    'i'    Toggle display of clients which used no GPU time.
>+    'H'    Toggle between per PID aggregation and individual clients.
>
> DEVICE SELECTION
> ================
>diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
>index bc11fce2bb1e..73815cdea8aa 100644
>--- a/tools/intel_gpu_top.c
>+++ b/tools/intel_gpu_top.c
>@@ -43,8 +43,10 @@
> #include <sys/types.h>
> #include <unistd.h>
> #include <termios.h>
>+#include <sys/sysmacros.h>
>
> #include "igt_perf.h"
>+#include "igt_drm_fdinfo.h"
>
> #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
>
>@@ -311,7 +313,8 @@ static int engine_cmp(const void *__a, const void *__b)
> 		return a->instance - b->instance;
> }
>
>-#define is_igpu_pci(x) (strcmp(x, "0000:00:02.0") == 0)
>+#define IGPU_PCI "0000:00:02.0"
>+#define is_igpu_pci(x) (strcmp(x, IGPU_PCI) == 0)
> #define is_igpu(x) (strcmp(x, "i915") == 0)
>
> static struct engines *discover_engines(char *device)
>@@ -635,6 +638,547 @@ static void pmu_sample(struct engines *engines)
> 	}
> }
>
>+enum client_status {
>+	FREE = 0, /* mbz */
>+	ALIVE,
>+	PROBE
>+};
>+
>+struct clients;
>+
>+struct client {
>+	struct clients *clients;
>+
>+	enum client_status status;
>+	unsigned int id;
>+	unsigned int pid;
>+	char name[24];
>+	char print_name[24];
>+	unsigned int samples;
>+	unsigned long total_runtime;
>+	unsigned long last_runtime;
>+	unsigned long *val;
>+	uint64_t *last;
>+};
>+
>+struct clients {
>+	unsigned int num_clients;
>+	unsigned int active_clients;
>+
>+	unsigned int num_classes;
>+	struct engine_class *class;
>+
>+	char pci_slot[64];
>+
>+	struct client *client;
>+};
>+
>+#define for_each_client(clients, c, tmp) \
>+	for ((tmp) = (clients)->num_clients, c = (clients)->client; \
>+	     (tmp > 0); (tmp)--, (c)++)
>+
>+static struct clients *init_clients(const char *pci_slot)
>+{
>+	struct clients *clients;
>+
>+	clients = malloc(sizeof(*clients));
>+	if (!clients)
>+		return NULL;
>+
>+	memset(clients, 0, sizeof(*clients));
>+
>+	strncpy(clients->pci_slot, pci_slot, sizeof(clients->pci_slot));
>+
>+	return clients;
>+}
>+
>+static struct client *
>+find_client(struct clients *clients, enum client_status status, unsigned int id)
>+{
>+	unsigned int start, num;
>+	struct client *c;
>+
>+	start = status == FREE ? clients->active_clients : 0; /* Free block at the end. */
>+	num = clients->num_clients - start;
>+
>+	for (c = &clients->client[start]; num; c++, num--) {
>+		if (status != c->status)
>+			continue;
>+
>+		if (status == FREE || c->id == id)
>+			return c;
>+	}
>+
>+	return NULL;
>+}
>+
>+static void
>+update_client(struct client *c, unsigned int pid, char *name, uint64_t val[16])
>+{
>+	unsigned int i;
>+
>+	if (c->pid != pid)
>+		c->pid = pid;
>+
>+	if (strcmp(c->name, name)) {
>+		char *p;
>+
>+		strncpy(c->name, name, sizeof(c->name) - 1);
>+		strncpy(c->print_name, name, sizeof(c->print_name) - 1);
>+
>+		p = c->print_name;
>+		while (*p) {
>+			if (!isprint(*p))
>+				*p = '*';
>+			p++;
>+		}
>+	}
>+
>+	c->last_runtime = 0;
>+	c->total_runtime = 0;
>+

assert(c->clients->num_classes <= 16);

>+	for (i = 0; i < c->clients->num_classes; i++) {
>+		if (val[i] < c->last[i])
>+			continue; /* It will catch up soon. */
>+
>+		c->total_runtime += val[i];
As far as busyness is concerned c->total_runtime += val[i] should be it.  

>+		c->val[i] = val[i] - c->last[i];
>+		c->last_runtime += c->val[i];
>+		c->last[i] = val[i];

Can you describe what these 3 lines are doing?

>+	}
>+
>+	c->samples++;
>+	c->status = ALIVE;
>+}
>+
>+static void
>+add_client(struct clients *clients, unsigned int id, unsigned int pid,
>+	   char *name, uint64_t busy[16])
>+{
>+	struct client *c;
>+
>+	assert(!find_client(clients, ALIVE, id));
>+
>+	c = find_client(clients, FREE, 0);
>+	if (!c) {
>+		unsigned int idx = clients->num_clients;
>+
>+		clients->num_clients += (clients->num_clients + 2) / 2;
>+		clients->client = realloc(clients->client,
>+					  clients->num_clients * sizeof(*c));
>+		assert(clients->client);
>+
>+		c = &clients->client[idx];
>+		memset(c, 0, (clients->num_clients - idx) * sizeof(*c));
>+	}
>+
>+	c->id = id;
>+	c->clients = clients;
>+	c->val = calloc(clients->num_classes, sizeof(c->val));
>+	c->last = calloc(clients->num_classes, sizeof(c->last));
>+	assert(c->val && c->last);
>+
>+	update_client(c, pid, name, busy);
>+}
>+
>+static void free_client(struct client *c)
>+{
>+	free(c->val);
>+	free(c->last);
>+	memset(c, 0, sizeof(*c));
>+}
>+
>+static int client_last_cmp(const void *_a, const void *_b)
>+{
>+	const struct client *a = _a;
>+	const struct client *b = _b;
>+	long tot_a, tot_b;
>+
>+	/*
>+	 * Sort clients in descending order of runtime in the previous sampling
>+	 * period for active ones, followed by inactive. Tie-breaker is client
>+	 * id.
>+	 */
>+
>+	tot_a = a->status == ALIVE ? a->last_runtime : -1;
>+	tot_b = b->status == ALIVE ? b->last_runtime : -1;
>+
>+	tot_b -= tot_a;
>+	if (tot_b > 0)
>+		return 1;
>+	if (tot_b < 0)
>+		return -1;
>+
>+	return (int)b->id - a->id;

nit: the compare functions could use a single if, if you agree on just 
returning tot_b.

	if (!tot_b)
		return (int)b->id - a->id;

	return tot_b;

>+}
>+
>+static int client_total_cmp(const void *_a, const void *_b)
>+{
>+	const struct client *a = _a;
>+	const struct client *b = _b;
>+	long tot_a, tot_b;
>+
>+	tot_a = a->status == ALIVE ? a->total_runtime : -1;
>+	tot_b = b->status == ALIVE ? b->total_runtime : -1;
>+
>+	tot_b -= tot_a;
>+	if (tot_b > 0)
>+		return 1;
>+	if (tot_b < 0)
>+		return -1;
>+
>+	return (int)b->id - a->id;
>+}
>+
>+static int client_id_cmp(const void *_a, const void *_b)
>+{
>+	const struct client *a = _a;
>+	const struct client *b = _b;
>+	int id_a, id_b;
>+
>+	id_a = a->status == ALIVE ? a->id : -1;
>+	id_b = b->status == ALIVE ? b->id : -1;
>+
>+	id_b -= id_a;
>+	if (id_b > 0)
>+		return 1;
>+	if (id_b < 0)
>+		return -1;
>+
>+	return (int)b->id - a->id;
>+}
>+
>+static int client_pid_cmp(const void *_a, const void *_b)
>+{
>+	const struct client *a = _a;
>+	const struct client *b = _b;
>+	int pid_a, pid_b;
>+
>+	pid_a = a->status == ALIVE ? a->pid : INT_MAX;
>+	pid_b = b->status == ALIVE ? b->pid : INT_MAX;
>+
>+	pid_b -= pid_a;
>+	if (pid_b > 0)
>+		return -1;
>+	if (pid_b < 0)
>+		return 1;
>+
>+	return (int)a->id - b->id;
>+}
>+
>+static int (*client_cmp)(const void *, const void *) = client_last_cmp;
>+
>+static struct clients *sort_clients(struct clients *clients,
>+				    int (*cmp)(const void *, const void *))
>+{
>+	unsigned int active, free;
>+	struct client *c;
>+	int tmp;
>+
>+	if (!clients)
>+		return clients;
>+
>+	qsort(clients->client, clients->num_clients, sizeof(*clients->client),
>+	      cmp);
>+
>+	/* Trim excessive array space. */
>+	active = 0;
>+	for_each_client(clients, c, tmp) {
>+		if (c->status != ALIVE)
>+			break; /* Active clients are first in the array. */
>+		active++;
>+	}
>+
>+	clients->active_clients = active;
>+
>+	free = clients->num_clients - active;
>+	if (free > clients->num_clients / 2) {
>+		active = clients->num_clients - free / 2;
>+		if (active != clients->num_clients) {
>+			clients->num_clients = active;
>+			clients->client = realloc(clients->client,
>+						  clients->num_clients *
>+						  sizeof(*c));
>+		}
>+	}
>+
>+	return clients;
>+}
>+
>+static bool aggregate_pids = true;
>+
>+static struct clients *display_clients(struct clients *clients)
>+{
>+	struct client *ac, *c, *cp = NULL;
>+	struct clients *aggregated;
>+	int tmp, num = 0;
>+
>+	if (!aggregate_pids)
>+		goto out;
>+
>+	/* Sort by pid first to make it easy to aggregate while walking. */
>+	sort_clients(clients, client_pid_cmp);
>+
>+	aggregated = calloc(1, sizeof(*clients));
>+	assert(aggregated);
>+
>+	ac = calloc(clients->num_clients, sizeof(*c));
>+	assert(ac);
>+
>+	aggregated->num_classes = clients->num_classes;
>+	aggregated->class = clients->class;
>+	aggregated->client = ac;
>+
>+	for_each_client(clients, c, tmp) {
>+		unsigned int i;
>+
>+		if (c->status == FREE)
>+			break;
>+
>+		assert(c->status == ALIVE);
>+
>+		if ((cp && c->pid != cp->pid) || !cp) {

same as: if (!cp || c->pid != cp->pid), fine either ways

>+			ac = &aggregated->client[num++];
>+
>+			/* New pid. */
>+			ac->clients = aggregated;
>+			ac->status = ALIVE;
>+			ac->id = -c->pid;
>+			ac->pid = c->pid;
>+			strcpy(ac->name, c->name);
>+			strcpy(ac->print_name, c->print_name);
>+			ac->val = calloc(clients->num_classes,
>+					 sizeof(ac->val[0]));
>+			assert(ac->val);
>+			ac->samples = 1;
>+		}
>+
>+		cp = c;
>+
>+		if (c->samples < 2)
>+			continue;
>+
>+		ac->samples = 2; /* All what matters for display. */
>+		ac->total_runtime += c->total_runtime;
>+		ac->last_runtime += c->last_runtime;
>+
>+		for (i = 0; i < clients->num_classes; i++)
>+			ac->val[i] += c->val[i];
>+	}
>+
>+	aggregated->num_clients = num;
>+	aggregated->active_clients = num;
>+
>+	clients = aggregated;
>+
>+out:
>+	return sort_clients(clients, client_cmp);
>+}
>+
>+static void free_clients(struct clients *clients)
>+{
>+	struct client *c;
>+	unsigned int tmp;
>+
>+	for_each_client(clients, c, tmp) {
>+		free(c->val);
>+		free(c->last);
>+	}
>+
>+	free(clients->client);
>+	free(clients);
>+}
>+
>+static bool is_drm_fd(DIR *fd_dir, const char *name)
>+{
>+	struct stat stat;
>+	int ret;
>+
>+	ret = fstatat(dirfd(fd_dir), name, &stat, 0);
>+
>+	return ret == 0 &&
>+	       (stat.st_mode & S_IFMT) == S_IFCHR &&
>+	       major(stat.st_rdev) == 226;
>+}
>+
>+static bool get_task_name(const char *buffer, char *out, unsigned long sz)
>+{
>+	char *s = index(buffer, '(');
>+	char *e = rindex(buffer, ')');
>+	unsigned int len;
>+
>+	if (!s || !e)
>+		return false;

maybe assert(e > s);

>+
>+	len = --e - ++s + 1;

len = e - ++s;

Thanks,
Umesh

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [igt-dev] [PATCH i-g-t 03/11] intel-gpu-top: Add support for per client stats
  2022-03-31 22:08     ` [Intel-gfx] " Umesh Nerlige Ramappa
@ 2022-04-01 14:19       ` Tvrtko Ursulin
  -1 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-04-01 14:19 UTC (permalink / raw)
  To: Umesh Nerlige Ramappa; +Cc: igt-dev, Intel-gfx, dri-devel, Tvrtko Ursulin


On 31/03/2022 23:08, Umesh Nerlige Ramappa wrote:
> lgtm, I just have a few nits and questions below:
> 
> Regardless, this is
> 
> Reviewed-by: Umesh Nerlige Ramappa <umesh.nerlige.ramappa@intel.com>

Thanks!

I've sent the updated series with only first three patches for first 
stage. Only first patch misses the r-b now.

Rest of the comments below.

> On Tue, Feb 22, 2022 at 01:55:57PM +0000, Tvrtko Ursulin wrote:
>> From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>>
>> Use the i915 exported data in /proc/<pid>/fdinfo to show GPU utilization
>> per DRM client.
>>
>> Example of the output:
>>
>> intel-gpu-top: Intel Tigerlake (Gen12) @ /dev/dri/card0 -  220/ 221 MHz
>>    70% RC6;  0.62/ 7.08 W;      760 irqs/s
>>
>>         ENGINES     BUSY                                 MI_SEMA MI_WAIT
>>       Render/3D   23.06% |██████▊                      |      0%      0%
>>         Blitter    0.00% |                             |      0%      0%
>>           Video    5.40% |█▋                           |      0%      0%
>>    VideoEnhance   20.67% |██████                       |      0%      0%
>>
>>   PID              NAME  Render/3D    Blitter      Video    VideoEnhance
>>  3082               mpv |          ||          ||▌         ||██        |
>>  3117         neverball |█▉        ||          ||          ||          |
>>     1           systemd |▍         ||          ||          ||          |
>>  2338       gnome-shell |          ||          ||          ||          |
>>
>> Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>> ---
>> man/intel_gpu_top.rst |   4 +
>> tools/intel_gpu_top.c | 801 +++++++++++++++++++++++++++++++++++++++++-
>> tools/meson.build     |   2 +-
>> 3 files changed, 804 insertions(+), 3 deletions(-)
>>
>> diff --git a/man/intel_gpu_top.rst b/man/intel_gpu_top.rst
>> index b3b765b05feb..f4dbfc5b44d9 100644
>> --- a/man/intel_gpu_top.rst
>> +++ b/man/intel_gpu_top.rst
>> @@ -56,6 +56,10 @@ Supported keys:
>>     'q'    Exit from the tool.
>>     'h'    Show interactive help.
>>     '1'    Toggle between aggregated engine class and physical engine 
>> mode.
>> +    'n'    Toggle display of numeric client busyness overlay.
>> +    's'    Toggle between sort modes (runtime, total runtime, pid, 
>> client id).
>> +    'i'    Toggle display of clients which used no GPU time.
>> +    'H'    Toggle between per PID aggregation and individual clients.
>>
>> DEVICE SELECTION
>> ================
>> diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
>> index bc11fce2bb1e..73815cdea8aa 100644
>> --- a/tools/intel_gpu_top.c
>> +++ b/tools/intel_gpu_top.c
>> @@ -43,8 +43,10 @@
>> #include <sys/types.h>
>> #include <unistd.h>
>> #include <termios.h>
>> +#include <sys/sysmacros.h>
>>
>> #include "igt_perf.h"
>> +#include "igt_drm_fdinfo.h"
>>
>> #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
>>
>> @@ -311,7 +313,8 @@ static int engine_cmp(const void *__a, const void 
>> *__b)
>>         return a->instance - b->instance;
>> }
>>
>> -#define is_igpu_pci(x) (strcmp(x, "0000:00:02.0") == 0)
>> +#define IGPU_PCI "0000:00:02.0"
>> +#define is_igpu_pci(x) (strcmp(x, IGPU_PCI) == 0)
>> #define is_igpu(x) (strcmp(x, "i915") == 0)
>>
>> static struct engines *discover_engines(char *device)
>> @@ -635,6 +638,547 @@ static void pmu_sample(struct engines *engines)
>>     }
>> }
>>
>> +enum client_status {
>> +    FREE = 0, /* mbz */
>> +    ALIVE,
>> +    PROBE
>> +};
>> +
>> +struct clients;
>> +
>> +struct client {
>> +    struct clients *clients;
>> +
>> +    enum client_status status;
>> +    unsigned int id;
>> +    unsigned int pid;
>> +    char name[24];
>> +    char print_name[24];
>> +    unsigned int samples;
>> +    unsigned long total_runtime;
>> +    unsigned long last_runtime;
>> +    unsigned long *val;
>> +    uint64_t *last;
>> +};
>> +
>> +struct clients {
>> +    unsigned int num_clients;
>> +    unsigned int active_clients;
>> +
>> +    unsigned int num_classes;
>> +    struct engine_class *class;
>> +
>> +    char pci_slot[64];
>> +
>> +    struct client *client;
>> +};
>> +
>> +#define for_each_client(clients, c, tmp) \
>> +    for ((tmp) = (clients)->num_clients, c = (clients)->client; \
>> +         (tmp > 0); (tmp)--, (c)++)
>> +
>> +static struct clients *init_clients(const char *pci_slot)
>> +{
>> +    struct clients *clients;
>> +
>> +    clients = malloc(sizeof(*clients));
>> +    if (!clients)
>> +        return NULL;
>> +
>> +    memset(clients, 0, sizeof(*clients));
>> +
>> +    strncpy(clients->pci_slot, pci_slot, sizeof(clients->pci_slot));
>> +
>> +    return clients;
>> +}
>> +
>> +static struct client *
>> +find_client(struct clients *clients, enum client_status status, 
>> unsigned int id)
>> +{
>> +    unsigned int start, num;
>> +    struct client *c;
>> +
>> +    start = status == FREE ? clients->active_clients : 0; /* Free 
>> block at the end. */
>> +    num = clients->num_clients - start;
>> +
>> +    for (c = &clients->client[start]; num; c++, num--) {
>> +        if (status != c->status)
>> +            continue;
>> +
>> +        if (status == FREE || c->id == id)
>> +            return c;
>> +    }
>> +
>> +    return NULL;
>> +}
>> +
>> +static void
>> +update_client(struct client *c, unsigned int pid, char *name, 
>> uint64_t val[16])
>> +{
>> +    unsigned int i;
>> +
>> +    if (c->pid != pid)
>> +        c->pid = pid;
>> +
>> +    if (strcmp(c->name, name)) {
>> +        char *p;
>> +
>> +        strncpy(c->name, name, sizeof(c->name) - 1);
>> +        strncpy(c->print_name, name, sizeof(c->print_name) - 1);
>> +
>> +        p = c->print_name;
>> +        while (*p) {
>> +            if (!isprint(*p))
>> +                *p = '*';
>> +            p++;
>> +        }
>> +    }
>> +
>> +    c->last_runtime = 0;
>> +    c->total_runtime = 0;
>> +
> 
> assert(c->clients->num_classes <= 16);

Done in a slightly different way - since I've changed the function 
signature to take struct drm_client_fdinfo I used ARRAY_SIZE on the busy 
array inside it.

> 
>> +    for (i = 0; i < c->clients->num_classes; i++) {
>> +        if (val[i] < c->last[i])
>> +            continue; /* It will catch up soon. */
>> +
>> +        c->total_runtime += val[i];
> As far as busyness is concerned c->total_runtime += val[i] should be it.
>> +        c->val[i] = val[i] - c->last[i];
>> +        c->last_runtime += c->val[i];
>> +        c->last[i] = val[i];
> 
> Can you describe what these 3 lines are doing?

For different sorting modes I need to be able to put at the top the most 
active client regardless of which engines.

c->total_runtime is cumulative across engine usage.

c->last_runtime is usage during the last display period, also all 
engines added together.

c->last[] is simply tracking the delta per engine, from last display 
period to current, so relative utilisation in the last display period 
can be displayed.

> 
>> +    }
>> +
>> +    c->samples++;
>> +    c->status = ALIVE;
>> +}
>> +
>> +static void
>> +add_client(struct clients *clients, unsigned int id, unsigned int pid,
>> +       char *name, uint64_t busy[16])
>> +{
>> +    struct client *c;
>> +
>> +    assert(!find_client(clients, ALIVE, id));
>> +
>> +    c = find_client(clients, FREE, 0);
>> +    if (!c) {
>> +        unsigned int idx = clients->num_clients;
>> +
>> +        clients->num_clients += (clients->num_clients + 2) / 2;
>> +        clients->client = realloc(clients->client,
>> +                      clients->num_clients * sizeof(*c));
>> +        assert(clients->client);
>> +
>> +        c = &clients->client[idx];
>> +        memset(c, 0, (clients->num_clients - idx) * sizeof(*c));
>> +    }
>> +
>> +    c->id = id;
>> +    c->clients = clients;
>> +    c->val = calloc(clients->num_classes, sizeof(c->val));
>> +    c->last = calloc(clients->num_classes, sizeof(c->last));
>> +    assert(c->val && c->last);
>> +
>> +    update_client(c, pid, name, busy);
>> +}
>> +
>> +static void free_client(struct client *c)
>> +{
>> +    free(c->val);
>> +    free(c->last);
>> +    memset(c, 0, sizeof(*c));
>> +}
>> +
>> +static int client_last_cmp(const void *_a, const void *_b)
>> +{
>> +    const struct client *a = _a;
>> +    const struct client *b = _b;
>> +    long tot_a, tot_b;
>> +
>> +    /*
>> +     * Sort clients in descending order of runtime in the previous 
>> sampling
>> +     * period for active ones, followed by inactive. Tie-breaker is 
>> client
>> +     * id.
>> +     */
>> +
>> +    tot_a = a->status == ALIVE ? a->last_runtime : -1;
>> +    tot_b = b->status == ALIVE ? b->last_runtime : -1;
>> +
>> +    tot_b -= tot_a;
>> +    if (tot_b > 0)
>> +        return 1;
>> +    if (tot_b < 0)
>> +        return -1;
>> +
>> +    return (int)b->id - a->id;
> 
> nit: the compare functions could use a single if, if you agree on just 
> returning tot_b.
> 
>      if (!tot_b)
>          return (int)b->id - a->id;
> 
>      return tot_b;

I chose not to touch this for now. It gets refactored later in the 
series and there are signed extending issues (compare function works 
with int result while my internal data are alongs) to think about so I'd 
rather not need to re-debug it.

>> +}
>> +
>> +static int client_total_cmp(const void *_a, const void *_b)
>> +{
>> +    const struct client *a = _a;
>> +    const struct client *b = _b;
>> +    long tot_a, tot_b;
>> +
>> +    tot_a = a->status == ALIVE ? a->total_runtime : -1;
>> +    tot_b = b->status == ALIVE ? b->total_runtime : -1;
>> +
>> +    tot_b -= tot_a;
>> +    if (tot_b > 0)
>> +        return 1;
>> +    if (tot_b < 0)
>> +        return -1;
>> +
>> +    return (int)b->id - a->id;
>> +}
>> +
>> +static int client_id_cmp(const void *_a, const void *_b)
>> +{
>> +    const struct client *a = _a;
>> +    const struct client *b = _b;
>> +    int id_a, id_b;
>> +
>> +    id_a = a->status == ALIVE ? a->id : -1;
>> +    id_b = b->status == ALIVE ? b->id : -1;
>> +
>> +    id_b -= id_a;
>> +    if (id_b > 0)
>> +        return 1;
>> +    if (id_b < 0)
>> +        return -1;
>> +
>> +    return (int)b->id - a->id;
>> +}
>> +
>> +static int client_pid_cmp(const void *_a, const void *_b)
>> +{
>> +    const struct client *a = _a;
>> +    const struct client *b = _b;
>> +    int pid_a, pid_b;
>> +
>> +    pid_a = a->status == ALIVE ? a->pid : INT_MAX;
>> +    pid_b = b->status == ALIVE ? b->pid : INT_MAX;
>> +
>> +    pid_b -= pid_a;
>> +    if (pid_b > 0)
>> +        return -1;
>> +    if (pid_b < 0)
>> +        return 1;
>> +
>> +    return (int)a->id - b->id;
>> +}
>> +
>> +static int (*client_cmp)(const void *, const void *) = client_last_cmp;
>> +
>> +static struct clients *sort_clients(struct clients *clients,
>> +                    int (*cmp)(const void *, const void *))
>> +{
>> +    unsigned int active, free;
>> +    struct client *c;
>> +    int tmp;
>> +
>> +    if (!clients)
>> +        return clients;
>> +
>> +    qsort(clients->client, clients->num_clients, 
>> sizeof(*clients->client),
>> +          cmp);
>> +
>> +    /* Trim excessive array space. */
>> +    active = 0;
>> +    for_each_client(clients, c, tmp) {
>> +        if (c->status != ALIVE)
>> +            break; /* Active clients are first in the array. */
>> +        active++;
>> +    }
>> +
>> +    clients->active_clients = active;
>> +
>> +    free = clients->num_clients - active;
>> +    if (free > clients->num_clients / 2) {
>> +        active = clients->num_clients - free / 2;
>> +        if (active != clients->num_clients) {
>> +            clients->num_clients = active;
>> +            clients->client = realloc(clients->client,
>> +                          clients->num_clients *
>> +                          sizeof(*c));
>> +        }
>> +    }
>> +
>> +    return clients;
>> +}
>> +
>> +static bool aggregate_pids = true;
>> +
>> +static struct clients *display_clients(struct clients *clients)
>> +{
>> +    struct client *ac, *c, *cp = NULL;
>> +    struct clients *aggregated;
>> +    int tmp, num = 0;
>> +
>> +    if (!aggregate_pids)
>> +        goto out;
>> +
>> +    /* Sort by pid first to make it easy to aggregate while walking. */
>> +    sort_clients(clients, client_pid_cmp);
>> +
>> +    aggregated = calloc(1, sizeof(*clients));
>> +    assert(aggregated);
>> +
>> +    ac = calloc(clients->num_clients, sizeof(*c));
>> +    assert(ac);
>> +
>> +    aggregated->num_classes = clients->num_classes;
>> +    aggregated->class = clients->class;
>> +    aggregated->client = ac;
>> +
>> +    for_each_client(clients, c, tmp) {
>> +        unsigned int i;
>> +
>> +        if (c->status == FREE)
>> +            break;
>> +
>> +        assert(c->status == ALIVE);
>> +
>> +        if ((cp && c->pid != cp->pid) || !cp) {
> 
> same as: if (!cp || c->pid != cp->pid), fine either ways

Yep thanks, done.

> 
>> +            ac = &aggregated->client[num++];
>> +
>> +            /* New pid. */
>> +            ac->clients = aggregated;
>> +            ac->status = ALIVE;
>> +            ac->id = -c->pid;
>> +            ac->pid = c->pid;
>> +            strcpy(ac->name, c->name);
>> +            strcpy(ac->print_name, c->print_name);
>> +            ac->val = calloc(clients->num_classes,
>> +                     sizeof(ac->val[0]));
>> +            assert(ac->val);
>> +            ac->samples = 1;
>> +        }
>> +
>> +        cp = c;
>> +
>> +        if (c->samples < 2)
>> +            continue;
>> +
>> +        ac->samples = 2; /* All what matters for display. */
>> +        ac->total_runtime += c->total_runtime;
>> +        ac->last_runtime += c->last_runtime;
>> +
>> +        for (i = 0; i < clients->num_classes; i++)
>> +            ac->val[i] += c->val[i];
>> +    }
>> +
>> +    aggregated->num_clients = num;
>> +    aggregated->active_clients = num;
>> +
>> +    clients = aggregated;
>> +
>> +out:
>> +    return sort_clients(clients, client_cmp);
>> +}
>> +
>> +static void free_clients(struct clients *clients)
>> +{
>> +    struct client *c;
>> +    unsigned int tmp;
>> +
>> +    for_each_client(clients, c, tmp) {
>> +        free(c->val);
>> +        free(c->last);
>> +    }
>> +
>> +    free(clients->client);
>> +    free(clients);
>> +}
>> +
>> +static bool is_drm_fd(DIR *fd_dir, const char *name)
>> +{
>> +    struct stat stat;
>> +    int ret;
>> +
>> +    ret = fstatat(dirfd(fd_dir), name, &stat, 0);
>> +
>> +    return ret == 0 &&
>> +           (stat.st_mode & S_IFMT) == S_IFCHR &&
>> +           major(stat.st_rdev) == 226;
>> +}
>> +
>> +static bool get_task_name(const char *buffer, char *out, unsigned 
>> long sz)
>> +{
>> +    char *s = index(buffer, '(');
>> +    char *e = rindex(buffer, ')');
>> +    unsigned int len;
>> +
>> +    if (!s || !e)
>> +        return false;
> 
> maybe assert(e > s);

Done.

> 
>> +
>> +    len = --e - ++s + 1;
> 
> len = e - ++s;

Not sure which one is more readable, depends how you think about it I 
guess. But done nevertheless.

Regards,

Tvrtko

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [Intel-gfx] [igt-dev] [PATCH i-g-t 03/11] intel-gpu-top: Add support for per client stats
@ 2022-04-01 14:19       ` Tvrtko Ursulin
  0 siblings, 0 replies; 50+ messages in thread
From: Tvrtko Ursulin @ 2022-04-01 14:19 UTC (permalink / raw)
  To: Umesh Nerlige Ramappa; +Cc: igt-dev, Intel-gfx, dri-devel


On 31/03/2022 23:08, Umesh Nerlige Ramappa wrote:
> lgtm, I just have a few nits and questions below:
> 
> Regardless, this is
> 
> Reviewed-by: Umesh Nerlige Ramappa <umesh.nerlige.ramappa@intel.com>

Thanks!

I've sent the updated series with only first three patches for first 
stage. Only first patch misses the r-b now.

Rest of the comments below.

> On Tue, Feb 22, 2022 at 01:55:57PM +0000, Tvrtko Ursulin wrote:
>> From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>>
>> Use the i915 exported data in /proc/<pid>/fdinfo to show GPU utilization
>> per DRM client.
>>
>> Example of the output:
>>
>> intel-gpu-top: Intel Tigerlake (Gen12) @ /dev/dri/card0 -  220/ 221 MHz
>>    70% RC6;  0.62/ 7.08 W;      760 irqs/s
>>
>>         ENGINES     BUSY                                 MI_SEMA MI_WAIT
>>       Render/3D   23.06% |██████▊                      |      0%      0%
>>         Blitter    0.00% |                             |      0%      0%
>>           Video    5.40% |█▋                           |      0%      0%
>>    VideoEnhance   20.67% |██████                       |      0%      0%
>>
>>   PID              NAME  Render/3D    Blitter      Video    VideoEnhance
>>  3082               mpv |          ||          ||▌         ||██        |
>>  3117         neverball |█▉        ||          ||          ||          |
>>     1           systemd |▍         ||          ||          ||          |
>>  2338       gnome-shell |          ||          ||          ||          |
>>
>> Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>> ---
>> man/intel_gpu_top.rst |   4 +
>> tools/intel_gpu_top.c | 801 +++++++++++++++++++++++++++++++++++++++++-
>> tools/meson.build     |   2 +-
>> 3 files changed, 804 insertions(+), 3 deletions(-)
>>
>> diff --git a/man/intel_gpu_top.rst b/man/intel_gpu_top.rst
>> index b3b765b05feb..f4dbfc5b44d9 100644
>> --- a/man/intel_gpu_top.rst
>> +++ b/man/intel_gpu_top.rst
>> @@ -56,6 +56,10 @@ Supported keys:
>>     'q'    Exit from the tool.
>>     'h'    Show interactive help.
>>     '1'    Toggle between aggregated engine class and physical engine 
>> mode.
>> +    'n'    Toggle display of numeric client busyness overlay.
>> +    's'    Toggle between sort modes (runtime, total runtime, pid, 
>> client id).
>> +    'i'    Toggle display of clients which used no GPU time.
>> +    'H'    Toggle between per PID aggregation and individual clients.
>>
>> DEVICE SELECTION
>> ================
>> diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
>> index bc11fce2bb1e..73815cdea8aa 100644
>> --- a/tools/intel_gpu_top.c
>> +++ b/tools/intel_gpu_top.c
>> @@ -43,8 +43,10 @@
>> #include <sys/types.h>
>> #include <unistd.h>
>> #include <termios.h>
>> +#include <sys/sysmacros.h>
>>
>> #include "igt_perf.h"
>> +#include "igt_drm_fdinfo.h"
>>
>> #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
>>
>> @@ -311,7 +313,8 @@ static int engine_cmp(const void *__a, const void 
>> *__b)
>>         return a->instance - b->instance;
>> }
>>
>> -#define is_igpu_pci(x) (strcmp(x, "0000:00:02.0") == 0)
>> +#define IGPU_PCI "0000:00:02.0"
>> +#define is_igpu_pci(x) (strcmp(x, IGPU_PCI) == 0)
>> #define is_igpu(x) (strcmp(x, "i915") == 0)
>>
>> static struct engines *discover_engines(char *device)
>> @@ -635,6 +638,547 @@ static void pmu_sample(struct engines *engines)
>>     }
>> }
>>
>> +enum client_status {
>> +    FREE = 0, /* mbz */
>> +    ALIVE,
>> +    PROBE
>> +};
>> +
>> +struct clients;
>> +
>> +struct client {
>> +    struct clients *clients;
>> +
>> +    enum client_status status;
>> +    unsigned int id;
>> +    unsigned int pid;
>> +    char name[24];
>> +    char print_name[24];
>> +    unsigned int samples;
>> +    unsigned long total_runtime;
>> +    unsigned long last_runtime;
>> +    unsigned long *val;
>> +    uint64_t *last;
>> +};
>> +
>> +struct clients {
>> +    unsigned int num_clients;
>> +    unsigned int active_clients;
>> +
>> +    unsigned int num_classes;
>> +    struct engine_class *class;
>> +
>> +    char pci_slot[64];
>> +
>> +    struct client *client;
>> +};
>> +
>> +#define for_each_client(clients, c, tmp) \
>> +    for ((tmp) = (clients)->num_clients, c = (clients)->client; \
>> +         (tmp > 0); (tmp)--, (c)++)
>> +
>> +static struct clients *init_clients(const char *pci_slot)
>> +{
>> +    struct clients *clients;
>> +
>> +    clients = malloc(sizeof(*clients));
>> +    if (!clients)
>> +        return NULL;
>> +
>> +    memset(clients, 0, sizeof(*clients));
>> +
>> +    strncpy(clients->pci_slot, pci_slot, sizeof(clients->pci_slot));
>> +
>> +    return clients;
>> +}
>> +
>> +static struct client *
>> +find_client(struct clients *clients, enum client_status status, 
>> unsigned int id)
>> +{
>> +    unsigned int start, num;
>> +    struct client *c;
>> +
>> +    start = status == FREE ? clients->active_clients : 0; /* Free 
>> block at the end. */
>> +    num = clients->num_clients - start;
>> +
>> +    for (c = &clients->client[start]; num; c++, num--) {
>> +        if (status != c->status)
>> +            continue;
>> +
>> +        if (status == FREE || c->id == id)
>> +            return c;
>> +    }
>> +
>> +    return NULL;
>> +}
>> +
>> +static void
>> +update_client(struct client *c, unsigned int pid, char *name, 
>> uint64_t val[16])
>> +{
>> +    unsigned int i;
>> +
>> +    if (c->pid != pid)
>> +        c->pid = pid;
>> +
>> +    if (strcmp(c->name, name)) {
>> +        char *p;
>> +
>> +        strncpy(c->name, name, sizeof(c->name) - 1);
>> +        strncpy(c->print_name, name, sizeof(c->print_name) - 1);
>> +
>> +        p = c->print_name;
>> +        while (*p) {
>> +            if (!isprint(*p))
>> +                *p = '*';
>> +            p++;
>> +        }
>> +    }
>> +
>> +    c->last_runtime = 0;
>> +    c->total_runtime = 0;
>> +
> 
> assert(c->clients->num_classes <= 16);

Done in a slightly different way - since I've changed the function 
signature to take struct drm_client_fdinfo I used ARRAY_SIZE on the busy 
array inside it.

> 
>> +    for (i = 0; i < c->clients->num_classes; i++) {
>> +        if (val[i] < c->last[i])
>> +            continue; /* It will catch up soon. */
>> +
>> +        c->total_runtime += val[i];
> As far as busyness is concerned c->total_runtime += val[i] should be it.
>> +        c->val[i] = val[i] - c->last[i];
>> +        c->last_runtime += c->val[i];
>> +        c->last[i] = val[i];
> 
> Can you describe what these 3 lines are doing?

For different sorting modes I need to be able to put at the top the most 
active client regardless of which engines.

c->total_runtime is cumulative across engine usage.

c->last_runtime is usage during the last display period, also all 
engines added together.

c->last[] is simply tracking the delta per engine, from last display 
period to current, so relative utilisation in the last display period 
can be displayed.

> 
>> +    }
>> +
>> +    c->samples++;
>> +    c->status = ALIVE;
>> +}
>> +
>> +static void
>> +add_client(struct clients *clients, unsigned int id, unsigned int pid,
>> +       char *name, uint64_t busy[16])
>> +{
>> +    struct client *c;
>> +
>> +    assert(!find_client(clients, ALIVE, id));
>> +
>> +    c = find_client(clients, FREE, 0);
>> +    if (!c) {
>> +        unsigned int idx = clients->num_clients;
>> +
>> +        clients->num_clients += (clients->num_clients + 2) / 2;
>> +        clients->client = realloc(clients->client,
>> +                      clients->num_clients * sizeof(*c));
>> +        assert(clients->client);
>> +
>> +        c = &clients->client[idx];
>> +        memset(c, 0, (clients->num_clients - idx) * sizeof(*c));
>> +    }
>> +
>> +    c->id = id;
>> +    c->clients = clients;
>> +    c->val = calloc(clients->num_classes, sizeof(c->val));
>> +    c->last = calloc(clients->num_classes, sizeof(c->last));
>> +    assert(c->val && c->last);
>> +
>> +    update_client(c, pid, name, busy);
>> +}
>> +
>> +static void free_client(struct client *c)
>> +{
>> +    free(c->val);
>> +    free(c->last);
>> +    memset(c, 0, sizeof(*c));
>> +}
>> +
>> +static int client_last_cmp(const void *_a, const void *_b)
>> +{
>> +    const struct client *a = _a;
>> +    const struct client *b = _b;
>> +    long tot_a, tot_b;
>> +
>> +    /*
>> +     * Sort clients in descending order of runtime in the previous 
>> sampling
>> +     * period for active ones, followed by inactive. Tie-breaker is 
>> client
>> +     * id.
>> +     */
>> +
>> +    tot_a = a->status == ALIVE ? a->last_runtime : -1;
>> +    tot_b = b->status == ALIVE ? b->last_runtime : -1;
>> +
>> +    tot_b -= tot_a;
>> +    if (tot_b > 0)
>> +        return 1;
>> +    if (tot_b < 0)
>> +        return -1;
>> +
>> +    return (int)b->id - a->id;
> 
> nit: the compare functions could use a single if, if you agree on just 
> returning tot_b.
> 
>      if (!tot_b)
>          return (int)b->id - a->id;
> 
>      return tot_b;

I chose not to touch this for now. It gets refactored later in the 
series and there are signed extending issues (compare function works 
with int result while my internal data are alongs) to think about so I'd 
rather not need to re-debug it.

>> +}
>> +
>> +static int client_total_cmp(const void *_a, const void *_b)
>> +{
>> +    const struct client *a = _a;
>> +    const struct client *b = _b;
>> +    long tot_a, tot_b;
>> +
>> +    tot_a = a->status == ALIVE ? a->total_runtime : -1;
>> +    tot_b = b->status == ALIVE ? b->total_runtime : -1;
>> +
>> +    tot_b -= tot_a;
>> +    if (tot_b > 0)
>> +        return 1;
>> +    if (tot_b < 0)
>> +        return -1;
>> +
>> +    return (int)b->id - a->id;
>> +}
>> +
>> +static int client_id_cmp(const void *_a, const void *_b)
>> +{
>> +    const struct client *a = _a;
>> +    const struct client *b = _b;
>> +    int id_a, id_b;
>> +
>> +    id_a = a->status == ALIVE ? a->id : -1;
>> +    id_b = b->status == ALIVE ? b->id : -1;
>> +
>> +    id_b -= id_a;
>> +    if (id_b > 0)
>> +        return 1;
>> +    if (id_b < 0)
>> +        return -1;
>> +
>> +    return (int)b->id - a->id;
>> +}
>> +
>> +static int client_pid_cmp(const void *_a, const void *_b)
>> +{
>> +    const struct client *a = _a;
>> +    const struct client *b = _b;
>> +    int pid_a, pid_b;
>> +
>> +    pid_a = a->status == ALIVE ? a->pid : INT_MAX;
>> +    pid_b = b->status == ALIVE ? b->pid : INT_MAX;
>> +
>> +    pid_b -= pid_a;
>> +    if (pid_b > 0)
>> +        return -1;
>> +    if (pid_b < 0)
>> +        return 1;
>> +
>> +    return (int)a->id - b->id;
>> +}
>> +
>> +static int (*client_cmp)(const void *, const void *) = client_last_cmp;
>> +
>> +static struct clients *sort_clients(struct clients *clients,
>> +                    int (*cmp)(const void *, const void *))
>> +{
>> +    unsigned int active, free;
>> +    struct client *c;
>> +    int tmp;
>> +
>> +    if (!clients)
>> +        return clients;
>> +
>> +    qsort(clients->client, clients->num_clients, 
>> sizeof(*clients->client),
>> +          cmp);
>> +
>> +    /* Trim excessive array space. */
>> +    active = 0;
>> +    for_each_client(clients, c, tmp) {
>> +        if (c->status != ALIVE)
>> +            break; /* Active clients are first in the array. */
>> +        active++;
>> +    }
>> +
>> +    clients->active_clients = active;
>> +
>> +    free = clients->num_clients - active;
>> +    if (free > clients->num_clients / 2) {
>> +        active = clients->num_clients - free / 2;
>> +        if (active != clients->num_clients) {
>> +            clients->num_clients = active;
>> +            clients->client = realloc(clients->client,
>> +                          clients->num_clients *
>> +                          sizeof(*c));
>> +        }
>> +    }
>> +
>> +    return clients;
>> +}
>> +
>> +static bool aggregate_pids = true;
>> +
>> +static struct clients *display_clients(struct clients *clients)
>> +{
>> +    struct client *ac, *c, *cp = NULL;
>> +    struct clients *aggregated;
>> +    int tmp, num = 0;
>> +
>> +    if (!aggregate_pids)
>> +        goto out;
>> +
>> +    /* Sort by pid first to make it easy to aggregate while walking. */
>> +    sort_clients(clients, client_pid_cmp);
>> +
>> +    aggregated = calloc(1, sizeof(*clients));
>> +    assert(aggregated);
>> +
>> +    ac = calloc(clients->num_clients, sizeof(*c));
>> +    assert(ac);
>> +
>> +    aggregated->num_classes = clients->num_classes;
>> +    aggregated->class = clients->class;
>> +    aggregated->client = ac;
>> +
>> +    for_each_client(clients, c, tmp) {
>> +        unsigned int i;
>> +
>> +        if (c->status == FREE)
>> +            break;
>> +
>> +        assert(c->status == ALIVE);
>> +
>> +        if ((cp && c->pid != cp->pid) || !cp) {
> 
> same as: if (!cp || c->pid != cp->pid), fine either ways

Yep thanks, done.

> 
>> +            ac = &aggregated->client[num++];
>> +
>> +            /* New pid. */
>> +            ac->clients = aggregated;
>> +            ac->status = ALIVE;
>> +            ac->id = -c->pid;
>> +            ac->pid = c->pid;
>> +            strcpy(ac->name, c->name);
>> +            strcpy(ac->print_name, c->print_name);
>> +            ac->val = calloc(clients->num_classes,
>> +                     sizeof(ac->val[0]));
>> +            assert(ac->val);
>> +            ac->samples = 1;
>> +        }
>> +
>> +        cp = c;
>> +
>> +        if (c->samples < 2)
>> +            continue;
>> +
>> +        ac->samples = 2; /* All what matters for display. */
>> +        ac->total_runtime += c->total_runtime;
>> +        ac->last_runtime += c->last_runtime;
>> +
>> +        for (i = 0; i < clients->num_classes; i++)
>> +            ac->val[i] += c->val[i];
>> +    }
>> +
>> +    aggregated->num_clients = num;
>> +    aggregated->active_clients = num;
>> +
>> +    clients = aggregated;
>> +
>> +out:
>> +    return sort_clients(clients, client_cmp);
>> +}
>> +
>> +static void free_clients(struct clients *clients)
>> +{
>> +    struct client *c;
>> +    unsigned int tmp;
>> +
>> +    for_each_client(clients, c, tmp) {
>> +        free(c->val);
>> +        free(c->last);
>> +    }
>> +
>> +    free(clients->client);
>> +    free(clients);
>> +}
>> +
>> +static bool is_drm_fd(DIR *fd_dir, const char *name)
>> +{
>> +    struct stat stat;
>> +    int ret;
>> +
>> +    ret = fstatat(dirfd(fd_dir), name, &stat, 0);
>> +
>> +    return ret == 0 &&
>> +           (stat.st_mode & S_IFMT) == S_IFCHR &&
>> +           major(stat.st_rdev) == 226;
>> +}
>> +
>> +static bool get_task_name(const char *buffer, char *out, unsigned 
>> long sz)
>> +{
>> +    char *s = index(buffer, '(');
>> +    char *e = rindex(buffer, ')');
>> +    unsigned int len;
>> +
>> +    if (!s || !e)
>> +        return false;
> 
> maybe assert(e > s);

Done.

> 
>> +
>> +    len = --e - ++s + 1;
> 
> len = e - ++s;

Not sure which one is more readable, depends how you think about it I 
guess. But done nevertheless.

Regards,

Tvrtko

^ permalink raw reply	[flat|nested] 50+ messages in thread

end of thread, other threads:[~2022-04-01 14:20 UTC | newest]

Thread overview: 50+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-02-22 13:55 [PATCH i-g-t 00/11] Per client GPU utilisation Tvrtko Ursulin
2022-02-22 13:55 ` [igt-dev] " Tvrtko Ursulin
2022-02-22 13:55 ` [Intel-gfx] " Tvrtko Ursulin
2022-02-22 13:55 ` [PATCH i-g-t 01/11] lib: Helper library for parsing i915 fdinfo output Tvrtko Ursulin
2022-02-22 13:55   ` [igt-dev] " Tvrtko Ursulin
2022-02-22 13:55   ` [Intel-gfx] " Tvrtko Ursulin
2022-03-30 19:52   ` Umesh Nerlige Ramappa
2022-03-30 19:52     ` [igt-dev] " Umesh Nerlige Ramappa
2022-03-31 13:22     ` Tvrtko Ursulin
2022-03-31 13:22       ` [igt-dev] " Tvrtko Ursulin
2022-02-22 13:55 ` [Intel-gfx] [PATCH i-g-t 02/11] tests/i915/drm_fdinfo: Basic and functional tests for GPU busyness exported via fdinfo Tvrtko Ursulin
2022-02-22 13:55   ` [igt-dev] " Tvrtko Ursulin
2022-02-22 13:55   ` Tvrtko Ursulin
2022-03-30 20:11   ` [igt-dev] " Umesh Nerlige Ramappa
2022-03-30 20:11     ` [Intel-gfx] " Umesh Nerlige Ramappa
2022-03-31 13:23     ` Tvrtko Ursulin
2022-03-31 13:23       ` [Intel-gfx] " Tvrtko Ursulin
2022-02-22 13:55 ` [PATCH i-g-t 03/11] intel-gpu-top: Add support for per client stats Tvrtko Ursulin
2022-02-22 13:55   ` [igt-dev] " Tvrtko Ursulin
2022-02-22 13:55   ` [Intel-gfx] " Tvrtko Ursulin
2022-03-31 22:08   ` [igt-dev] " Umesh Nerlige Ramappa
2022-03-31 22:08     ` [Intel-gfx] " Umesh Nerlige Ramappa
2022-04-01 14:19     ` Tvrtko Ursulin
2022-04-01 14:19       ` [Intel-gfx] " Tvrtko Ursulin
2022-02-22 13:55 ` [PATCH i-g-t 04/11] lib: Extract igt_drm_clients from intel_gpu_top Tvrtko Ursulin
2022-02-22 13:55   ` [igt-dev] " Tvrtko Ursulin
2022-02-22 13:55   ` [Intel-gfx] " Tvrtko Ursulin
2022-02-22 13:55 ` [PATCH i-g-t 05/11] libdrmfdinfo: Allow specifying custom engine map Tvrtko Ursulin
2022-02-22 13:55   ` [igt-dev] " Tvrtko Ursulin
2022-02-22 13:55   ` [Intel-gfx] " Tvrtko Ursulin
2022-02-22 13:56 ` [PATCH i-g-t 06/11] libdrmclients: Record client drm minor Tvrtko Ursulin
2022-02-22 13:56   ` [igt-dev] " Tvrtko Ursulin
2022-02-22 13:56   ` [Intel-gfx] " Tvrtko Ursulin
2022-02-22 13:56 ` [PATCH i-g-t 07/11] libdrmclient: Support multiple DRM cards Tvrtko Ursulin
2022-02-22 13:56   ` [igt-dev] " Tvrtko Ursulin
2022-02-22 13:56   ` [Intel-gfx] " Tvrtko Ursulin
2022-02-22 13:56 ` [PATCH i-g-t 08/11] libdrmfdinfo: Track largest engine index Tvrtko Ursulin
2022-02-22 13:56   ` [igt-dev] " Tvrtko Ursulin
2022-02-22 13:56   ` [Intel-gfx] " Tvrtko Ursulin
2022-02-22 13:56 ` [PATCH i-g-t 09/11] libdrmclient/intel_gpu_top: Decouple hardcoded engine assumptions Tvrtko Ursulin
2022-02-22 13:56   ` [igt-dev] " Tvrtko Ursulin
2022-02-22 13:56   ` [Intel-gfx] " Tvrtko Ursulin
2022-02-22 13:56 ` [PATCH i-g-t 10/11] libdrmclient: Enforce client status sort order in the library Tvrtko Ursulin
2022-02-22 13:56   ` [igt-dev] " Tvrtko Ursulin
2022-02-22 13:56   ` [Intel-gfx] " Tvrtko Ursulin
2022-02-22 13:56 ` [PATCH i-g-t 11/11] gputop: Basic vendor agnostic GPU top tool Tvrtko Ursulin
2022-02-22 13:56   ` [igt-dev] " Tvrtko Ursulin
2022-02-22 13:56   ` [Intel-gfx] " Tvrtko Ursulin
2022-02-22 17:06 ` [igt-dev] ✓ Fi.CI.BAT: success for Per client GPU utilisation Patchwork
2022-02-22 21:17 ` [igt-dev] ✓ Fi.CI.IGT: " Patchwork

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.