| // SPDX-License-Identifier: MIT |
| /* |
| * Copyright © 2023 Intel Corporation |
| */ |
| |
| #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" |
| #include "drmtest.h" |
| |
| 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) |
| { |
| int ret, len; |
| |
| if (lines++ >= con_h) |
| return lines; |
| |
| printf("\033[7m"); |
| ret = printf("DRM minor %u", c->drm_minor); |
| n_spaces(con_w - ret); |
| |
| if (lines++ >= con_h) |
| return lines; |
| |
| putchar('\n'); |
| if (c->regions->num_regions) |
| len = printf("%*s MEM RSS ", |
| c->clients->max_pid_len, "PID"); |
| else |
| len = printf("%*s ", c->clients->max_pid_len, "PID"); |
| |
| if (c->engines->num_engines) { |
| unsigned int i; |
| int width; |
| |
| *engine_w = width = |
| (con_w - len - c->clients->max_name_len - 1) / |
| 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; |
| } |
| } |
| |
| printf(" %-*s\033[0m\n", con_w - len - 1, "NAME"); |
| |
| 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 int |
| print_size(uint64_t sz) |
| { |
| char units[] = {'B', 'K', 'M', 'G'}; |
| unsigned int u; |
| |
| for (u = 0; u < ARRAY_SIZE(units) - 1; u++) { |
| if (sz < 1024) |
| break; |
| sz /= 1024; |
| } |
| |
| return printf("%7"PRIu64"%c ", sz, units[u]); |
| } |
| |
| 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; |
| uint64_t sz; |
| int len; |
| |
| /* Filter out idle clients. */ |
| if (!c->total_runtime || c->samples < 2) |
| return lines; |
| |
| /* Print header when moving to a different DRM card. */ |
| if (newheader(c, *prevc)) { |
| lines = print_client_header(c, lines, con_w, con_h, engine_w); |
| if (lines >= con_h) |
| return lines; |
| } |
| |
| *prevc = c; |
| |
| len = printf("%*s ", c->clients->max_pid_len, c->pid_str); |
| |
| if (c->regions->num_regions) { |
| for (sz = 0, i = 0; i <= c->regions->max_region_id; i++) |
| sz += c->memory[i].total; |
| len += print_size(sz); |
| |
| for (sz = 0, i = 0; i <= c->regions->max_region_id; i++) |
| sz += c->memory[i].resident; |
| len += print_size(sz); |
| } |
| |
| 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 fluctuations between our scanning period and |
| * GPU times as exported by the kernel in fdinfo. |
| */ |
| if (pct > 100.0) |
| pct = 100.0; |
| |
| print_percentage_bar(pct, *engine_w); |
| len += *engine_w; |
| } |
| |
| printf(" %-*s\n", con_w - len - 1, c->print_name); |
| |
| 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; |
| |
| /* DRM cards into consecutive buckets first. */ |
| 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; |
| |
| /* |
| * Within buckets sort by last sampling period aggregated runtime, with |
| * client id as a tie-breaker. |
| */ |
| 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, 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, 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; |
| } |