blob: cc8db7c539ed495e50944891da5e36cbfc3c87e0 [file] [log] [blame]
/*
* Copyright © 2007-2019 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 <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdint.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <math.h>
#include <locale.h>
#include <limits.h>
#include <signal.h>
#include "igt_perf.h"
struct pmu_pair {
uint64_t cur;
uint64_t prev;
};
struct pmu_counter {
bool present;
uint64_t config;
unsigned int idx;
struct pmu_pair val;
};
struct engine {
const char *name;
char *display_name;
char *short_name;
unsigned int class;
unsigned int instance;
unsigned int num_counters;
struct pmu_counter busy;
struct pmu_counter wait;
struct pmu_counter sema;
};
struct engines {
unsigned int num_engines;
unsigned int num_counters;
DIR *root;
int fd;
struct pmu_pair ts;
int rapl_fd;
double rapl_scale;
const char *rapl_unit;
int imc_fd;
double imc_reads_scale;
const char *imc_reads_unit;
double imc_writes_scale;
const char *imc_writes_unit;
struct pmu_counter freq_req;
struct pmu_counter freq_act;
struct pmu_counter irq;
struct pmu_counter rc6;
struct pmu_counter rapl;
struct pmu_counter imc_reads;
struct pmu_counter imc_writes;
struct engine engine;
};
static uint64_t
get_pmu_config(int dirfd, const char *name, const char *counter)
{
char buf[128], *p;
int fd, ret;
ret = snprintf(buf, sizeof(buf), "%s-%s", name, counter);
if (ret < 0 || ret == sizeof(buf))
return -1;
fd = openat(dirfd, buf, O_RDONLY);
if (fd < 0)
return -1;
ret = read(fd, buf, sizeof(buf));
close(fd);
if (ret <= 0)
return -1;
p = index(buf, '0');
if (!p)
return -1;
return strtoul(p, NULL, 0);
}
#define engine_ptr(engines, n) (&engines->engine + (n))
static const char *class_display_name(unsigned int class)
{
switch (class) {
case I915_ENGINE_CLASS_RENDER:
return "Render/3D";
case I915_ENGINE_CLASS_COPY:
return "Blitter";
case I915_ENGINE_CLASS_VIDEO:
return "Video";
case I915_ENGINE_CLASS_VIDEO_ENHANCE:
return "VideoEnhance";
default:
return "[unknown]";
}
}
static const char *class_short_name(unsigned int class)
{
switch (class) {
case I915_ENGINE_CLASS_RENDER:
return "RCS";
case I915_ENGINE_CLASS_COPY:
return "BCS";
case I915_ENGINE_CLASS_VIDEO:
return "VCS";
case I915_ENGINE_CLASS_VIDEO_ENHANCE:
return "VECS";
default:
return "UNKN";
}
}
static int engine_cmp(const void *__a, const void *__b)
{
const struct engine *a = (struct engine *)__a;
const struct engine *b = (struct engine *)__b;
if (a->class != b->class)
return a->class - b->class;
else
return a->instance - b->instance;
}
static struct engines *discover_engines(void)
{
const char *sysfs_root = "/sys/devices/i915/events";
struct engines *engines;
struct dirent *dent;
int ret = 0;
DIR *d;
engines = malloc(sizeof(struct engines));
if (!engines)
return NULL;
memset(engines, 0, sizeof(*engines));
engines->num_engines = 0;
d = opendir(sysfs_root);
if (!d)
return NULL;
while ((dent = readdir(d)) != NULL) {
const char *endswith = "-busy";
const unsigned int endlen = strlen(endswith);
struct engine *engine =
engine_ptr(engines, engines->num_engines);
char buf[256];
if (dent->d_type != DT_REG)
continue;
if (strlen(dent->d_name) >= sizeof(buf)) {
ret = ENAMETOOLONG;
break;
}
strcpy(buf, dent->d_name);
/* xxxN-busy */
if (strlen(buf) < (endlen + 4))
continue;
if (strcmp(&buf[strlen(buf) - endlen], endswith))
continue;
memset(engine, 0, sizeof(*engine));
buf[strlen(buf) - endlen] = 0;
engine->name = strdup(buf);
if (!engine->name) {
ret = errno;
break;
}
engine->busy.config = get_pmu_config(dirfd(d), engine->name,
"busy");
if (engine->busy.config == -1) {
ret = ENOENT;
break;
}
engine->class = (engine->busy.config &
(__I915_PMU_OTHER(0) - 1)) >>
I915_PMU_CLASS_SHIFT;
engine->instance = (engine->busy.config >>
I915_PMU_SAMPLE_BITS) &
((1 << I915_PMU_SAMPLE_INSTANCE_BITS) - 1);
ret = asprintf(&engine->display_name, "%s/%u",
class_display_name(engine->class),
engine->instance);
if (ret <= 0) {
ret = errno;
break;
}
ret = asprintf(&engine->short_name, "%s/%u",
class_short_name(engine->class),
engine->instance);
if (ret <= 0) {
ret = errno;
break;
}
engines->num_engines++;
engines = realloc(engines, sizeof(struct engines) +
engines->num_engines * sizeof(struct engine));
if (!engines) {
ret = errno;
break;
}
ret = 0;
}
if (ret) {
free(engines);
errno = ret;
return NULL;
}
qsort(engine_ptr(engines, 0), engines->num_engines,
sizeof(struct engine), engine_cmp);
engines->root = d;
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);
}
static double filename_to_double(const char *filename)
{
char *oldlocale;
char buf[80];
double v;
if (filename_to_buf(filename, buf, sizeof(buf)))
return 0;
oldlocale = setlocale(LC_ALL, "C");
v = strtod(buf, NULL);
setlocale(LC_ALL, oldlocale);
return v;
}
#define RAPL_ROOT "/sys/devices/power/"
#define RAPL_EVENT "/sys/devices/power/events/"
static uint64_t rapl_type_id(void)
{
return filename_to_u64(RAPL_ROOT "type", 10);
}
static uint64_t rapl_gpu_power(void)
{
return filename_to_u64(RAPL_EVENT "energy-gpu", 0);
}
static double rapl_gpu_power_scale(void)
{
return filename_to_double(RAPL_EVENT "energy-gpu.scale");
}
static const char *rapl_gpu_power_unit(void)
{
char buf[32];
if (filename_to_buf(RAPL_EVENT "energy-gpu.unit",
buf, sizeof(buf)) == 0)
if (!strcmp(buf, "Joules"))
return strdup("Watts");
else
return strdup(buf);
else
return NULL;
}
#define IMC_ROOT "/sys/devices/uncore_imc/"
#define IMC_EVENT "/sys/devices/uncore_imc/events/"
static uint64_t imc_type_id(void)
{
return filename_to_u64(IMC_ROOT "type", 10);
}
static uint64_t imc_data_reads(void)
{
return filename_to_u64(IMC_EVENT "data_reads", 0);
}
static double imc_data_reads_scale(void)
{
return filename_to_double(IMC_EVENT "data_reads.scale");
}
static const char *imc_data_reads_unit(void)
{
char buf[32];
if (filename_to_buf(IMC_EVENT "data_reads.unit", buf, sizeof(buf)) == 0)
return strdup(buf);
else
return NULL;
}
static uint64_t imc_data_writes(void)
{
return filename_to_u64(IMC_EVENT "data_writes", 0);
}
static double imc_data_writes_scale(void)
{
return filename_to_double(IMC_EVENT "data_writes.scale");
}
static const char *imc_data_writes_unit(void)
{
char buf[32];
if (filename_to_buf(IMC_EVENT "data_writes.unit",
buf, sizeof(buf)) == 0)
return strdup(buf);
else
return NULL;
}
#define _open_pmu(cnt, pmu, fd) \
({ \
int fd__; \
\
fd__ = perf_i915_open_group((pmu)->config, (fd)); \
if (fd__ >= 0) { \
if ((fd) == -1) \
(fd) = fd__; \
(pmu)->present = true; \
(pmu)->idx = (cnt)++; \
} \
\
fd__; \
})
#define _open_imc(cnt, pmu, fd) \
({ \
int fd__; \
\
fd__ = igt_perf_open_group(imc_type_id(), (pmu)->config, (fd)); \
if (fd__ >= 0) { \
if ((fd) == -1) \
(fd) = fd__; \
(pmu)->present = true; \
(pmu)->idx = (cnt)++; \
} \
\
fd__; \
})
static int pmu_init(struct engines *engines)
{
unsigned int i;
int fd;
engines->fd = -1;
engines->num_counters = 0;
engines->irq.config = I915_PMU_INTERRUPTS;
fd = _open_pmu(engines->num_counters, &engines->irq, engines->fd);
if (fd < 0)
return -1;
engines->freq_req.config = I915_PMU_REQUESTED_FREQUENCY;
_open_pmu(engines->num_counters, &engines->freq_req, engines->fd);
engines->freq_act.config = I915_PMU_ACTUAL_FREQUENCY;
_open_pmu(engines->num_counters, &engines->freq_act, engines->fd);
engines->rc6.config = I915_PMU_RC6_RESIDENCY;
_open_pmu(engines->num_counters, &engines->rc6, engines->fd);
for (i = 0; i < engines->num_engines; i++) {
struct engine *engine = engine_ptr(engines, i);
struct {
struct pmu_counter *pmu;
const char *counter;
} *cnt, counters[] = {
{ .pmu = &engine->busy, .counter = "busy" },
{ .pmu = &engine->wait, .counter = "wait" },
{ .pmu = &engine->sema, .counter = "sema" },
{ .pmu = NULL, .counter = NULL },
};
for (cnt = counters; cnt->pmu; cnt++) {
if (!cnt->pmu->config)
cnt->pmu->config =
get_pmu_config(dirfd(engines->root),
engine->name,
cnt->counter);
fd = _open_pmu(engines->num_counters, cnt->pmu,
engines->fd);
if (fd >= 0)
engine->num_counters++;
}
}
engines->rapl_fd = -1;
if (rapl_type_id()) {
engines->rapl_scale = rapl_gpu_power_scale();
engines->rapl_unit = rapl_gpu_power_unit();
if (!engines->rapl_unit)
return -1;
engines->rapl.config = rapl_gpu_power();
if (!engines->rapl.config)
return -1;
engines->rapl_fd = igt_perf_open(rapl_type_id(),
engines->rapl.config);
if (engines->rapl_fd < 0)
return -1;
engines->rapl.present = true;
}
engines->imc_fd = -1;
if (imc_type_id()) {
unsigned int num = 0;
engines->imc_reads_scale = imc_data_reads_scale();
engines->imc_writes_scale = imc_data_writes_scale();
engines->imc_reads_unit = imc_data_reads_unit();
if (!engines->imc_reads_unit)
return -1;
engines->imc_writes_unit = imc_data_writes_unit();
if (!engines->imc_writes_unit)
return -1;
engines->imc_reads.config = imc_data_reads();
if (!engines->imc_reads.config)
return -1;
engines->imc_writes.config = imc_data_writes();
if (!engines->imc_writes.config)
return -1;
fd = _open_imc(num, &engines->imc_reads, engines->imc_fd);
if (fd < 0)
return -1;
fd = _open_imc(num, &engines->imc_writes, engines->imc_fd);
if (fd < 0)
return -1;
engines->imc_reads.present = true;
engines->imc_writes.present = true;
}
return 0;
}
static uint64_t pmu_read_multi(int fd, unsigned int num, uint64_t *val)
{
uint64_t buf[2 + num];
unsigned int i;
ssize_t len;
memset(buf, 0, sizeof(buf));
len = read(fd, buf, sizeof(buf));
assert(len == sizeof(buf));
for (i = 0; i < num; i++)
val[i] = buf[2 + i];
return buf[1];
}
static double pmu_calc(struct pmu_pair *p, double d, double t, double s)
{
double v;
v = p->cur - p->prev;
v /= d;
v /= t;
v *= s;
if (s == 100.0 && v > 100.0)
v = 100.0;
return v;
}
static void fill_str(char *buf, unsigned int bufsz, char c, unsigned int num)
{
unsigned int i;
for (i = 0; i < num && i < (bufsz - 1); i++)
*buf++ = c;
*buf = 0;
}
static uint64_t __pmu_read_single(int fd, uint64_t *ts)
{
uint64_t data[2] = { };
ssize_t len;
len = read(fd, data, sizeof(data));
assert(len == sizeof(data));
if (ts)
*ts = data[1];
return data[0];
}
static uint64_t pmu_read_single(int fd)
{
return __pmu_read_single(fd, NULL);
}
static void __update_sample(struct pmu_counter *counter, uint64_t val)
{
counter->val.prev = counter->val.cur;
counter->val.cur = val;
}
static void update_sample(struct pmu_counter *counter, uint64_t *val)
{
if (counter->present)
__update_sample(counter, val[counter->idx]);
}
static void pmu_sample(struct engines *engines)
{
const int num_val = engines->num_counters;
uint64_t val[2 + num_val];
unsigned int i;
engines->ts.prev = engines->ts.cur;
if (engines->rapl_fd >= 0)
__update_sample(&engines->rapl,
pmu_read_single(engines->rapl_fd));
if (engines->imc_fd >= 0) {
pmu_read_multi(engines->imc_fd, 2, val);
update_sample(&engines->imc_reads, val);
update_sample(&engines->imc_writes, val);
}
engines->ts.cur = pmu_read_multi(engines->fd, num_val, val);
update_sample(&engines->freq_req, val);
update_sample(&engines->freq_act, val);
update_sample(&engines->irq, val);
update_sample(&engines->rc6, val);
for (i = 0; i < engines->num_engines; i++) {
struct engine *engine = engine_ptr(engines, i);
update_sample(&engine->busy, val);
update_sample(&engine->sema, val);
update_sample(&engine->wait, val);
}
}
static const char *bars[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" };
static void
print_percentage_bar(double percent, int max_len)
{
int bar_len = percent * (8 * (max_len - 2)) / 100.0;
int i;
putchar('|');
for (i = bar_len; i >= 8; i -= 8)
printf("%s", bars[8]);
if (i)
printf("%s", bars[i]);
for (i = 0; i < (max_len - 2 - (bar_len + 7) / 8); i++)
putchar(' ');
putchar('|');
}
#define DEFAULT_PERIOD_MS (1000)
static void
usage(const char *appname)
{
printf("intel_gpu_top - Display a top-like summary of Intel GPU usage\n"
"\n"
"Usage: %s [parameters]\n"
"\n"
"\tThe following parameters are optional:\n\n"
"\t[-h] Show this help text.\n"
"\t[-J] Output JSON formatted data.\n"
"\t[-l] List plain text data.\n"
"\t[-o <file|->] Output to specified file or '-' for standard out.\n"
"\t[-s <ms>] Refresh period in milliseconds (default %ums).\n"
"\n",
appname, DEFAULT_PERIOD_MS);
}
static enum {
INTERACTIVE,
STDOUT,
JSON
} output_mode;
struct cnt_item {
struct pmu_counter *pmu;
unsigned int fmt_width;
unsigned int fmt_precision;
double d;
double t;
double s;
const char *name;
const char *unit;
/* Internal fields. */
char buf[16];
};
struct cnt_group {
const char *name;
const char *display_name;
struct cnt_item *items;
};
static unsigned int json_indent_level;
static const char *json_indent[] = {
"",
"\t",
"\t\t",
"\t\t\t",
"\t\t\t\t",
"\t\t\t\t\t",
};
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
static unsigned int json_prev_struct_members;
static unsigned int json_struct_members;
FILE *out;
static void
json_open_struct(const char *name)
{
assert(json_indent_level < ARRAY_SIZE(json_indent));
json_prev_struct_members = json_struct_members;
json_struct_members = 0;
if (name)
fprintf(out, "%s%s\"%s\": {\n",
json_prev_struct_members ? ",\n" : "",
json_indent[json_indent_level],
name);
else
fprintf(out, "%s\n%s{\n",
json_prev_struct_members ? "," : "",
json_indent[json_indent_level]);
json_indent_level++;
}
static void
json_close_struct(void)
{
assert(json_indent_level > 0);
fprintf(out, "\n%s}", json_indent[--json_indent_level]);
if (json_indent_level == 0)
fflush(stdout);
}
static unsigned int
json_add_member(const struct cnt_group *parent, struct cnt_item *item,
unsigned int headers)
{
assert(json_indent_level < ARRAY_SIZE(json_indent));
fprintf(out, "%s%s\"%s\": ",
json_struct_members ? ",\n" : "",
json_indent[json_indent_level], item->name);
json_struct_members++;
if (!strcmp(item->name, "unit"))
fprintf(out, "\"%s\"", item->unit);
else
fprintf(out, "%f",
pmu_calc(&item->pmu->val, item->d, item->t, item->s));
return 1;
}
static unsigned int stdout_level;
#define STDOUT_HEADER_REPEAT 20
static unsigned int stdout_lines = STDOUT_HEADER_REPEAT;
static void
stdout_open_struct(const char *name)
{
stdout_level++;
assert(stdout_level > 0);
}
static void
stdout_close_struct(void)
{
assert(stdout_level > 0);
if (--stdout_level == 0) {
stdout_lines++;
fputs("\n", out);
fflush(out);
}
}
static unsigned int
stdout_add_member(const struct cnt_group *parent, struct cnt_item *item,
unsigned int headers)
{
unsigned int fmt_tot = item->fmt_width + (item->fmt_precision ? 1 : 0);
char buf[fmt_tot + 1];
double val;
int len;
if (!item->pmu)
return 0;
else if (!item->pmu->present)
return 0;
if (headers == 1) {
unsigned int grp_tot = 0;
struct cnt_item *it;
if (item != parent->items)
return 0;
for (it = parent->items; it->pmu; it++) {
if (!it->pmu->present)
continue;
grp_tot += 1 + it->fmt_width +
(it->fmt_precision ? 1 : 0);
}
fprintf(out, "%*s ", grp_tot - 1, parent->display_name);
return 0;
} else if (headers == 2) {
fprintf(out, "%*s ", fmt_tot, item->unit ?: item->name);
return 0;
}
val = pmu_calc(&item->pmu->val, item->d, item->t, item->s);
len = snprintf(buf, sizeof(buf), "%*.*f",
fmt_tot, item->fmt_precision, val);
if (len < 0 || len == sizeof(buf))
fill_str(buf, sizeof(buf), 'X', fmt_tot);
len = fprintf(out, "%s ", buf);
return len > 0 ? len : 0;
}
static void
term_open_struct(const char *name)
{
}
static void
term_close_struct(void)
{
}
static unsigned int
term_add_member(const struct cnt_group *parent, struct cnt_item *item,
unsigned int headers)
{
unsigned int fmt_tot = item->fmt_width + (item->fmt_precision ? 1 : 0);
double val;
int len;
if (!item->pmu)
return 0;
assert(fmt_tot <= sizeof(item->buf));
if (!item->pmu->present) {
fill_str(item->buf, sizeof(item->buf), '-', fmt_tot);
return 1;
}
val = pmu_calc(&item->pmu->val, item->d, item->t, item->s);
len = snprintf(item->buf, sizeof(item->buf),
"%*.*f",
fmt_tot, item->fmt_precision, val);
if (len < 0 || len == sizeof(item->buf))
fill_str(item->buf, sizeof(item->buf), 'X', fmt_tot);
return 1;
}
struct print_operations {
void (*open_struct)(const char *name);
void (*close_struct)(void);
unsigned int (*add_member)(const struct cnt_group *parent,
struct cnt_item *item,
unsigned int headers);
bool (*print_group)(struct cnt_group *group, unsigned int headers);
};
static const struct print_operations *pops;
static unsigned int
present_in_group(const struct cnt_group *grp)
{
unsigned int present = 0;
struct cnt_item *item;
for (item = grp->items; item->name; item++) {
if (item->pmu && item->pmu->present)
present++;
}
return present;
}
static bool
print_group(struct cnt_group *grp, unsigned int headers)
{
unsigned int consumed = 0;
struct cnt_item *item;
if (!present_in_group(grp))
return false;
pops->open_struct(grp->name);
for (item = grp->items; item->name; item++)
consumed += pops->add_member(grp, item, headers);
pops->close_struct();
return consumed;
}
static bool
term_print_group(struct cnt_group *grp, unsigned int headers)
{
unsigned int consumed = 0;
struct cnt_item *item;
pops->open_struct(grp->name);
for (item = grp->items; item->name; item++)
consumed += pops->add_member(grp, item, headers);
pops->close_struct();
return consumed;
}
static const struct print_operations json_pops = {
.open_struct = json_open_struct,
.close_struct = json_close_struct,
.add_member = json_add_member,
.print_group = print_group,
};
static const struct print_operations stdout_pops = {
.open_struct = stdout_open_struct,
.close_struct = stdout_close_struct,
.add_member = stdout_add_member,
.print_group = print_group,
};
static const struct print_operations term_pops = {
.open_struct = term_open_struct,
.close_struct = term_close_struct,
.add_member = term_add_member,
.print_group = term_print_group,
};
static bool print_groups(struct cnt_group **groups)
{
unsigned int headers = stdout_lines % STDOUT_HEADER_REPEAT + 1;
bool print_data = true;
if (output_mode == STDOUT && (headers == 1 || headers == 2)) {
for (struct cnt_group **grp = groups; *grp; grp++)
print_data = pops->print_group(*grp, headers);
}
for (struct cnt_group **grp = groups; print_data && *grp; grp++)
pops->print_group(*grp, false);
return print_data;
}
static int
print_header(struct engines *engines, double t,
int lines, int con_w, int con_h, bool *consumed)
{
struct pmu_counter fake_pmu = {
.present = true,
.val.cur = 1,
};
struct cnt_item period_items[] = {
{ &fake_pmu, 0, 0, 1.0, 1.0, t * 1e3, "duration" },
{ NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "ms" },
{ },
};
struct cnt_group period_group = {
.name = "period",
.items = period_items,
};
struct cnt_item freq_items[] = {
{ &engines->freq_req, 4, 0, 1.0, t, 1, "requested", "req" },
{ &engines->freq_act, 4, 0, 1.0, t, 1, "actual", "act" },
{ NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "MHz" },
{ },
};
struct cnt_group freq_group = {
.name = "frequency",
.display_name = "Freq MHz",
.items = freq_items,
};
struct cnt_item irq_items[] = {
{ &engines->irq, 8, 0, 1.0, t, 1, "count", "/s" },
{ NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "irq/s" },
{ },
};
struct cnt_group irq_group = {
.name = "interrupts",
.display_name = "IRQ",
.items = irq_items,
};
struct cnt_item rc6_items[] = {
{ &engines->rc6, 3, 0, 1e9, t, 100, "value", "%" },
{ NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "%" },
{ },
};
struct cnt_group rc6_group = {
.name = "rc6",
.display_name = "RC6",
.items = rc6_items,
};
struct cnt_item power_items[] = {
{ &engines->rapl, 4, 2, 1.0, t, engines->rapl_scale, "value",
"W" },
{ NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "W" },
{ },
};
struct cnt_group power_group = {
.name = "power",
.display_name = "Power",
.items = power_items,
};
struct cnt_group *groups[] = {
&period_group,
&freq_group,
&irq_group,
&rc6_group,
&power_group,
NULL
};
if (output_mode != JSON)
memmove(&groups[0], &groups[1],
sizeof(groups) - sizeof(groups[0]));
pops->open_struct(NULL);
*consumed = print_groups(groups);
if (output_mode == INTERACTIVE) {
printf("\033[H\033[J");
if (lines++ < con_h)
printf("intel-gpu-top - %s/%s MHz; %s%% RC6; %s %s; %s irqs/s\n",
freq_items[1].buf, freq_items[0].buf,
rc6_items[0].buf, power_items[0].buf,
engines->rapl_unit,
irq_items[0].buf);
if (lines++ < con_h)
printf("\n");
}
return lines;
}
static int
print_imc(struct engines *engines, double t, int lines, int con_w, int con_h)
{
struct cnt_item imc_items[] = {
{ &engines->imc_reads, 6, 0, 1.0, t, engines->imc_reads_scale,
"reads", "rd" },
{ &engines->imc_writes, 6, 0, 1.0, t, engines->imc_writes_scale,
"writes", "wr" },
{ NULL, 0, 0, 0.0, 0.0, 0.0, "unit" },
{ },
};
struct cnt_group imc_group = {
.name = "imc-bandwidth",
.items = imc_items,
};
struct cnt_group *groups[] = {
&imc_group,
NULL
};
int ret;
ret = asprintf((char **)&imc_group.display_name, "IMC %s/s",
engines->imc_reads_unit);
assert(ret >= 0);
ret = asprintf((char **)&imc_items[2].unit, "%s/s",
engines->imc_reads_unit);
assert(ret >= 0);
print_groups(groups);
free((void *)imc_group.display_name);
free((void *)imc_items[2].unit);
if (output_mode == INTERACTIVE) {
if (lines++ < con_h)
printf(" IMC reads: %s %s/s\n",
imc_items[0].buf, engines->imc_reads_unit);
if (lines++ < con_h)
printf(" IMC writes: %s %s/s\n",
imc_items[1].buf, engines->imc_writes_unit);
if (lines++ < con_h)
printf("\n");
}
return lines;
}
static int
print_engines_header(struct engines *engines, double t,
int lines, int con_w, int con_h)
{
for (unsigned int i = 0;
i < engines->num_engines && lines < con_h;
i++) {
struct engine *engine = engine_ptr(engines, i);
if (!engine->num_counters)
continue;
pops->open_struct("engines");
if (output_mode == INTERACTIVE) {
const char *a = " ENGINE BUSY ";
const char *b = " MI_SEMA MI_WAIT";
printf("\033[7m%s%*s%s\033[0m\n",
a, (int)(con_w - 1 - strlen(a) - strlen(b)),
" ", b);
lines++;
}
break;
}
return lines;
}
static int
print_engine(struct engines *engines, unsigned int i, double t,
int lines, int con_w, int con_h)
{
struct engine *engine = engine_ptr(engines, i);
struct cnt_item engine_items[] = {
{ &engine->busy, 6, 2, 1e9, t, 100, "busy", "%" },
{ &engine->sema, 3, 0, 1e9, t, 100, "sema", "se" },
{ &engine->wait, 3, 0, 1e9, t, 100, "wait", "wa" },
{ NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "%" },
{ },
};
struct cnt_group engine_group = {
.name = engine->display_name,
.display_name = engine->short_name,
.items = engine_items,
};
struct cnt_group *groups[] = {
&engine_group,
NULL
};
if (!engine->num_counters)
return lines;
print_groups(groups);
if (output_mode == INTERACTIVE) {
unsigned int max_w = con_w - 1;
unsigned int len;
char buf[128];
double val;
len = snprintf(buf, sizeof(buf), " %s%% %s%%",
engine_items[1].buf, engine_items[2].buf);
len += printf("%16s %s%% ",
engine->display_name, engine_items[0].buf);
val = pmu_calc(&engine->busy.val, 1e9, t, 100);
print_percentage_bar(val, max_w - len);
printf("%s\n", buf);
lines++;
}
return lines;
}
static int
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)
printf("\n");
}
return lines;
}
static bool stop_top;
static void sigint_handler(int sig)
{
stop_top = true;
}
int main(int argc, char **argv)
{
unsigned int period_us = DEFAULT_PERIOD_MS * 1000;
int con_w = -1, con_h = -1;
char *output_path = NULL;
struct engines *engines;
unsigned int i;
int ret, ch;
/* Parse options */
while ((ch = getopt(argc, argv, "o:s:Jlh")) != -1) {
switch (ch) {
case 'o':
output_path = optarg;
break;
case 's':
period_us = atoi(optarg) * 1000;
break;
case 'J':
output_mode = JSON;
break;
case 'l':
output_mode = STDOUT;
break;
case 'h':
usage(argv[0]);
exit(0);
default:
fprintf(stderr, "Invalid option %c!\n", (char)optopt);
usage(argv[0]);
exit(1);
}
}
if (output_mode == INTERACTIVE && (output_path || isatty(1) != 1))
output_mode = STDOUT;
if (output_path && strcmp(output_path, "-")) {
out = fopen(output_path, "w");
if (!out) {
fprintf(stderr, "Failed to open output file - '%s'!\n",
strerror(errno));
exit(1);
}
} else {
out = stdout;
}
if (output_mode != INTERACTIVE) {
sighandler_t sig = signal(SIGINT, sigint_handler);
if (sig == SIG_ERR)
fprintf(stderr, "Failed to install signal handler!\n");
}
switch (output_mode) {
case INTERACTIVE:
pops = &term_pops;
break;
case STDOUT:
pops = &stdout_pops;
break;
case JSON:
pops = &json_pops;
break;
default:
assert(0);
break;
};
engines = discover_engines();
if (!engines) {
fprintf(stderr,
"Failed to detect engines! (%s)\n(Kernel 4.16 or newer is required for i915 PMU support.)\n",
strerror(errno));
return 1;
}
ret = pmu_init(engines);
if (ret) {
fprintf(stderr,
"Failed to initialize PMU! (%s)\n", strerror(errno));
return 1;
}
pmu_sample(engines);
while (!stop_top) {
bool consumed = false;
int lines = 0;
struct winsize ws;
double t;
/* Update terminal size. */
if (output_mode != INTERACTIVE) {
con_w = con_h = INT_MAX;
} else if (ioctl(0, TIOCGWINSZ, &ws) != -1) {
con_w = ws.ws_col;
con_h = ws.ws_row;
}
pmu_sample(engines);
t = (double)(engines->ts.cur - engines->ts.prev) / 1e9;
if (stop_top)
break;
while (!consumed) {
lines = print_header(engines, t, lines, con_w, con_h,
&consumed);
if (engines->imc_fd)
lines = print_imc(engines, t, lines, con_w,
con_h);
lines = print_engines_header(engines, t, lines, con_w,
con_h);
for (i = 0;
i < engines->num_engines && lines < con_h;
i++)
lines = print_engine(engines, i, t, lines,
con_w, con_h);
lines = print_engines_footer(engines, t, lines, con_w,
con_h);
}
if (stop_top)
break;
usleep(period_us);
}
return 0;
}