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
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',
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
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
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,
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
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
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],
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
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
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],
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;
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);
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,
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];
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;
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." },
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,
dri-devel@lists.freedesktop.org