diff options
author | Tvrtko Ursulin <tvrtko.ursulin@intel.com> | 2020-12-17 12:47:54 +0000 |
---|---|---|
committer | Tvrtko Ursulin <tvrtko.ursulin@intel.com> | 2020-12-18 17:18:37 +0000 |
commit | fb5a44c3ea57621bc79a21cc97c29f18665adc58 (patch) | |
tree | 2930eb2a487902d734ad6a69bac0d6933eac9798 | |
parent | 8c134d69f46a90fe149ac67798cc208091448e3d (diff) |
intel-gpu-top: Support for client statscontext-stats
Adds support for per-client engine busyness stats i915 exports in sysfs
and produces output like the below:
==========================================================================
intel-gpu-top - 935/ 935 MHz; 0% RC6; 14.73 Watts; 1097 irqs/s
IMC reads: 1401 MiB/s
IMC writes: 4 MiB/s
ENGINE BUSY MI_SEMA MI_WAIT
Render/3D/0 63.73% |███████████████████ | 3% 0%
Blitter/0 9.53% |██▊ | 6% 0%
Video/0 39.32% |███████████▊ | 16% 0%
Video/1 15.62% |████▋ | 0% 0%
VideoEnhance/0 0.00% | | 0% 0%
PID NAME RCS BCS VCS VECS
4084 gem_wsim |█████▌ ||█ || || |
4086 gem_wsim |█▌ || ||███ || |
==========================================================================
Apart from the existing physical engine utilization it now also shows
utilization per client and per engine class.
v2:
* Version to match removal of global enable_stats toggle.
* Plus various fixes.
v3:
* Support brief backward jumps in client stats.
v4:
* Support device selection.
v5:
* Rebase for class aggregation.
Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
-rw-r--r-- | tools/intel_gpu_top.c | 534 |
1 files changed, 523 insertions, 11 deletions
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c index 72ad7cbe..b2fd7371 100644 --- a/tools/intel_gpu_top.c +++ b/tools/intel_gpu_top.c @@ -424,6 +424,51 @@ static struct engines *discover_engines(char *device) return engines; } +static int +filename_to_buf(const char *filename, char *buf, unsigned int bufsize) +{ + int fd, err; + ssize_t ret; + + fd = open(filename, O_RDONLY); + if (fd < 0) + return -1; + + ret = read(fd, buf, bufsize - 1); + err = errno; + close(fd); + if (ret < 1) { + errno = ret < 0 ? err : ENOMSG; + + return -1; + } + + if (ret > 1 && buf[ret - 1] == '\n') + buf[ret - 1] = '\0'; + else + buf[ret] = '\0'; + + return 0; +} + +static uint64_t filename_to_u64(const char *filename, int base) +{ + char buf[64], *b; + + if (filename_to_buf(filename, buf, sizeof(buf))) + return 0; + + /* + * Handle both single integer and key=value formats by skipping + * leading non-digits. + */ + b = buf; + while (*b && !isdigit(*b)) + b++; + + return strtoull(b, NULL, base); +} + #define _open_pmu(type, cnt, pmu, fd) \ ({ \ int fd__; \ @@ -625,23 +670,290 @@ 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[128]; + unsigned int samples; + unsigned long total; + struct engines *engines; + unsigned long *val; + uint64_t *last; +}; + +struct clients { + unsigned int num_clients; + + unsigned int num_classes; + struct engine_class *class; + + char sysfs_root[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 *drm_card) +{ + struct clients *clients = malloc(sizeof(*clients)); + const char *slash; + ssize_t ret; + + memset(clients, 0, sizeof(*clients)); + + if (drm_card) { + slash = rindex(drm_card, '/'); + assert(slash); + } else { + slash = "card0"; + } + + ret = snprintf(clients->sysfs_root, sizeof(clients->sysfs_root), + "/sys/class/drm/%s/clients/", slash); + assert(ret > 0 && ret < sizeof(clients->sysfs_root)); + + return clients; +} + +static uint64_t +read_client_busy(const struct client *client, unsigned int class) +{ + char buf[256]; + ssize_t ret; + + ret = snprintf(buf, sizeof(buf), "%s/%u/busy/%u", + client->clients->sysfs_root, client->id, class); + assert(ret > 0 && ret < sizeof(buf)); + if (ret <= 0 || ret == sizeof(buf)) + return 0; + + return filename_to_u64(buf, 10); +} + +static struct client * +find_client(struct clients *clients, enum client_status status, unsigned int id) +{ + struct client *c; + int tmp; + + for_each_client(clients, c, tmp) { + if ((status == FREE && c->status == FREE) || + (status == c->status && c->id == id)) + return c; + } + + return NULL; +} + +static void update_client(struct client *c, unsigned int pid, char *name) +{ + uint64_t val[c->clients->num_classes]; + unsigned int i; + + if (c->pid != pid) + c->pid = pid; + + if (strcmp(c->name, name)) + strncpy(c->name, name, sizeof(c->name) - 1); + + for (i = 0; i < c->clients->num_classes; i++) + val[i] = read_client_busy(c, c->clients->class[i].class); + + c->total = 0; + + for (i = 0; i < c->clients->num_classes; i++) { + if (val[i] < c->last[i]) + continue; /* It will catch up soon. */ + + c->val[i] = val[i] - c->last[i]; + c->total += 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) +{ + struct client *c; + + if (find_client(clients, ALIVE, id)) + return; + + 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); +} + +static void free_client(struct client *c) +{ + free(c->val); + free(c->last); + memset(c, 0, sizeof(*c)); +} + +static char * +read_client_sysfs(const char *sysfs_root, unsigned int id, const char *field) +{ + char buf[256]; + ssize_t ret; + + ret = snprintf(buf, sizeof(buf), "%s/%u/%s", sysfs_root, id, field); + assert(ret > 0 && ret < sizeof(buf)); + if (ret <= 0 || ret == sizeof(buf)) + return NULL; + + ret = filename_to_buf(buf, buf, sizeof(buf)); + if (ret) + return NULL; /* Client exited. */ + + return strdup(buf); +} + +static void scan_clients(struct clients *clients) +{ + struct dirent *dent; + struct client *c; + char *pid, *name; + unsigned int id; + int tmp; + DIR *d; + + if (!clients) + return; + + for_each_client(clients, c, tmp) { + if (c->status == ALIVE) + c->status = PROBE; + } + + d = opendir(clients->sysfs_root); + if (!d) + return; + + while ((dent = readdir(d)) != NULL) { + if (dent->d_type != DT_DIR) + continue; + if (!isdigit(dent->d_name[0])) + continue; + + id = atoi(dent->d_name); + + c = find_client(clients, PROBE, id); + + name = read_client_sysfs(clients->sysfs_root, id, "name"); + pid = read_client_sysfs(clients->sysfs_root, id, "pid"); + + if (name && pid) { + if (!c) + add_client(clients, id, atoi(pid), name); + else + update_client(c, atoi(pid), name); + } else if (c) { + c->status = PROBE; /* Will be deleted below. */ + } + + if (name) + free(name); + if (pid) + free(pid); + } + + closedir(d); + + for_each_client(clients, c, tmp) { + if (c->status == PROBE) + free_client(c); + } +} + +static int cmp(const void *_a, const void *_b) +{ + const struct client *a = _a; + const struct client *b = _b; + long tot_a = a->total; + long tot_b = b->total; + + tot_a *= a->status == ALIVE && a->samples > 1; + tot_b *= b->status == ALIVE && b->samples > 1; + + tot_b -= tot_a; + + if (!tot_b) + return (int)b->id - a->id; + + while (tot_b > INT_MAX || tot_b < INT_MIN) + tot_b /= 2; + + return tot_b; +} + 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 = percent * (8 * (max_len - 2)) / 100.0; - int i; + int bar_len, i, len = max_len - 2; + const int w = 8; + + assert(max_len > 0); + + bar_len = percent * len / 100.0; + if (bar_len > len) + bar_len = len; + bar_len *= w; putchar('|'); - for (i = bar_len; i >= 8; i -= 8) - printf("%s", bars[8]); + for (i = bar_len; i >= w; i -= w) + printf("%s", bars[w]); if (i) printf("%s", bars[i]); - for (i = 0; i < (max_len - 2 - (bar_len + 7) / 8); i++) - putchar(' '); + len -= (bar_len + (w - 1)) / w; + n_spaces(len); putchar('|'); } @@ -744,6 +1056,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) @@ -1046,8 +1370,6 @@ print_header(const struct igt_device_card *card, memmove(&groups[0], &groups[1], sizeof(groups) - sizeof(groups[0])); - pops->open_struct(NULL); - *consumed = print_groups(groups); if (output_mode == INTERACTIVE) { @@ -1204,7 +1526,7 @@ print_engine(struct engines *engines, unsigned int i, double t, engine->display_name, engine_items[0].buf); val = pmu_calc(&engine->busy.val, 1e9, t, 100); - print_percentage_bar(val, max_w - len); + print_percentage_bar(val, max_w > len ? max_w - len : 0); printf("%s\n", buf); @@ -1219,7 +1541,6 @@ print_engines_footer(struct engines *engines, double t, int lines, int con_w, int con_h) { pops->close_struct(); - pops->close_struct(); if (output_mode == INTERACTIVE) { if (lines++ < con_h) @@ -1243,6 +1564,9 @@ static void init_engine_classes(struct engines *engines) unsigned int i, num; int max = -1; + if (engines->num_classes) + return; + for (i = 0; i < engines->num_engines; i++) { struct engine *engine = engine_ptr(engines, i); @@ -1404,6 +1728,148 @@ 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 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) { + printf("%6u %17s ", c->pid, c->name); + + for (i = 0; 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); + + lines++; + } + + 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->name); + + snprintf(buf, sizeof(buf), "%u", c->pid); + __json_add_member("pid", buf); + + 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 bool stop_top; static void sigint_handler(int sig) @@ -1492,6 +1958,7 @@ static void process_stdin(unsigned int timeout_us) 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; @@ -1625,13 +2092,20 @@ int main(int argc, char **argv) ret = EXIT_SUCCESS; + clients = init_clients(card.pci_slot_name[0] ? card.card : NULL); + init_engine_classes(engines); + 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) { bool consumed = false; - int lines = 0; + int j, lines = 0; struct winsize ws; + struct client *c; double t; /* Update terminal size. */ @@ -1650,10 +2124,18 @@ int main(int argc, char **argv) pmu_sample(engines); t = (double)(engines->ts.cur - engines->ts.prev) / 1e9; + scan_clients(clients); + if (clients) { + qsort(clients->client, clients->num_clients, + sizeof(*clients->client), cmp); + } + if (stop_top) break; while (!consumed) { + pops->open_struct(NULL); + lines = print_header(&card, codename, engines, t, lines, con_w, con_h, &consumed); @@ -1661,6 +2143,36 @@ int main(int argc, char **argv) lines = print_imc(engines, t, lines, con_w, con_h); lines = print_engines(engines, t, lines, con_w, con_h); + + if (clients) { + int class_w; + + lines = print_clients_header(clients, lines, + con_w, con_h, + &class_w); + + for_each_client(clients, c, j) { + if (lines++ > con_h) + break; + + assert(c->status != PROBE); + if (c->status != ALIVE) + break; + + if (c->samples < 2) + continue; + + lines = print_client(c, engines, t, + lines, con_w, + con_h, period_us, + &class_w); + } + + lines = print_clients_footer(clients, t, lines, + con_w, con_h); + } + + pops->close_struct(); } if (stop_top) |