2019-11-19 22:53:49 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2016 Rob Clark <robclark@freedesktop.org>
|
|
|
|
* All Rights Reserved.
|
|
|
|
*
|
|
|
|
* 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>
|
2021-04-16 19:59:30 +01:00
|
|
|
#include <curses.h>
|
2019-11-19 22:53:49 +00:00
|
|
|
#include <err.h>
|
2021-04-16 19:59:30 +01:00
|
|
|
#include <inttypes.h>
|
|
|
|
#include <libconfig.h>
|
2020-03-10 15:10:22 +00:00
|
|
|
#include <locale.h>
|
2021-04-16 19:59:30 +01:00
|
|
|
#include <stdint.h>
|
2019-11-19 22:53:49 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <time.h>
|
2022-05-12 20:32:12 +01:00
|
|
|
#include <unistd.h>
|
2019-12-09 21:08:33 +00:00
|
|
|
#include <xf86drm.h>
|
2019-11-19 22:53:49 +00:00
|
|
|
|
|
|
|
#include "drm/freedreno_drmif.h"
|
|
|
|
#include "drm/freedreno_ringbuffer.h"
|
|
|
|
|
2021-03-18 16:51:02 +00:00
|
|
|
#include "util/os_file.h"
|
|
|
|
|
2021-03-18 20:51:40 +00:00
|
|
|
#include "freedreno_dt.h"
|
2019-11-19 22:53:49 +00:00
|
|
|
#include "freedreno_perfcntr.h"
|
|
|
|
|
|
|
|
#define MAX_CNTR_PER_GROUP 24
|
2022-05-12 20:32:12 +01:00
|
|
|
#define REFRESH_MS 500
|
|
|
|
|
|
|
|
static struct {
|
|
|
|
int refresh_ms;
|
2022-05-12 21:04:19 +01:00
|
|
|
bool dump;
|
2022-05-12 20:32:12 +01:00
|
|
|
} options = {
|
|
|
|
.refresh_ms = REFRESH_MS,
|
2022-05-12 21:04:19 +01:00
|
|
|
.dump = false,
|
2022-05-12 20:32:12 +01:00
|
|
|
};
|
2019-11-19 22:53:49 +00:00
|
|
|
|
|
|
|
/* NOTE first counter group should always be CP, since we unconditionally
|
|
|
|
* use CP counter to measure the gpu freq.
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct counter_group {
|
2021-04-16 19:59:30 +01:00
|
|
|
const struct fd_perfcntr_group *group;
|
|
|
|
|
|
|
|
struct {
|
|
|
|
const struct fd_perfcntr_counter *counter;
|
|
|
|
uint16_t select_val;
|
|
|
|
volatile uint32_t *val_hi;
|
|
|
|
volatile uint32_t *val_lo;
|
|
|
|
} counter[MAX_CNTR_PER_GROUP];
|
|
|
|
|
|
|
|
/* last sample time: */
|
|
|
|
uint32_t stime[MAX_CNTR_PER_GROUP];
|
|
|
|
/* for now just care about the low 32b value.. at least then we don't
|
|
|
|
* have to really care that we can't sample both hi and lo regs at the
|
|
|
|
* same time:
|
|
|
|
*/
|
|
|
|
uint32_t last[MAX_CNTR_PER_GROUP];
|
|
|
|
/* current value, ie. by how many did the counter increase in last
|
|
|
|
* sampling period divided by the sampling period:
|
|
|
|
*/
|
|
|
|
float current[MAX_CNTR_PER_GROUP];
|
|
|
|
/* name of currently selected counters (for UI): */
|
|
|
|
const char *label[MAX_CNTR_PER_GROUP];
|
2019-11-19 22:53:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct {
|
2021-04-16 19:59:30 +01:00
|
|
|
void *io;
|
|
|
|
uint32_t chipid;
|
|
|
|
uint32_t min_freq;
|
|
|
|
uint32_t max_freq;
|
|
|
|
/* per-generation table of counters: */
|
|
|
|
unsigned ngroups;
|
|
|
|
struct counter_group *groups;
|
|
|
|
/* drm device (for writing select regs via ring): */
|
|
|
|
struct fd_device *dev;
|
|
|
|
struct fd_pipe *pipe;
|
|
|
|
struct fd_submit *submit;
|
|
|
|
struct fd_ringbuffer *ring;
|
2019-11-19 22:53:49 +00:00
|
|
|
} dev;
|
|
|
|
|
|
|
|
static void config_save(void);
|
|
|
|
static void config_restore(void);
|
2019-11-20 19:56:57 +00:00
|
|
|
static void restore_counter_groups(void);
|
2019-11-19 22:53:49 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* helpers
|
|
|
|
*/
|
|
|
|
|
|
|
|
static uint32_t
|
|
|
|
gettime_us(void)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
struct timespec ts;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
|
|
return (ts.tv_sec * 1000000) + (ts.tv_nsec / 1000);
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
2022-05-12 21:04:19 +01:00
|
|
|
static void
|
|
|
|
sleep_us(uint32_t us)
|
|
|
|
{
|
|
|
|
const struct timespec ts = {
|
|
|
|
.tv_sec = us / 1000000,
|
|
|
|
.tv_nsec = (us % 1000000) * 1000,
|
|
|
|
};
|
|
|
|
clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
|
|
|
|
}
|
|
|
|
|
2019-11-19 22:53:49 +00:00
|
|
|
static uint32_t
|
|
|
|
delta(uint32_t a, uint32_t b)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
/* deal with rollover: */
|
|
|
|
if (a > b)
|
|
|
|
return 0xffffffff - a + b;
|
|
|
|
else
|
|
|
|
return b - a;
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
find_device(void)
|
|
|
|
{
|
2022-02-02 17:46:55 +00:00
|
|
|
int ret;
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2022-02-02 17:46:55 +00:00
|
|
|
dev.dev = fd_device_open();
|
|
|
|
if (!dev.dev)
|
2021-04-16 19:59:30 +01:00
|
|
|
err(1, "could not open drm device");
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
dev.pipe = fd_pipe_new(dev.dev, FD_PIPE_3D);
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
uint64_t val;
|
|
|
|
ret = fd_pipe_get_param(dev.pipe, FD_CHIP_ID, &val);
|
|
|
|
if (ret) {
|
|
|
|
err(1, "could not get gpu-id");
|
|
|
|
}
|
|
|
|
dev.chipid = val;
|
2019-11-19 22:53:49 +00:00
|
|
|
|
|
|
|
#define CHIP_FMT "d%d%d.%d"
|
2021-04-16 19:59:30 +01:00
|
|
|
#define CHIP_ARGS(chipid) \
|
|
|
|
((chipid) >> 24) & 0xff, ((chipid) >> 16) & 0xff, ((chipid) >> 8) & 0xff, \
|
|
|
|
((chipid) >> 0) & 0xff
|
|
|
|
printf("device: a%" CHIP_FMT "\n", CHIP_ARGS(dev.chipid));
|
|
|
|
|
|
|
|
/* try MAX_FREQ first as that will work regardless of old dt
|
|
|
|
* dt bindings vs upstream bindings:
|
|
|
|
*/
|
|
|
|
ret = fd_pipe_get_param(dev.pipe, FD_MAX_FREQ, &val);
|
|
|
|
if (ret) {
|
|
|
|
printf("falling back to parsing DT bindings for freq\n");
|
|
|
|
if (!fd_dt_find_freqs(&dev.min_freq, &dev.max_freq))
|
|
|
|
err(1, "could not find GPU freqs");
|
|
|
|
} else {
|
|
|
|
dev.min_freq = 0;
|
|
|
|
dev.max_freq = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("min_freq=%u, max_freq=%u\n", dev.min_freq, dev.max_freq);
|
|
|
|
|
|
|
|
dev.io = fd_dt_find_io();
|
|
|
|
if (!dev.io) {
|
|
|
|
err(1, "could not map device");
|
|
|
|
}
|
2022-03-03 23:38:22 +00:00
|
|
|
|
|
|
|
fd_pipe_set_param(dev.pipe, FD_SYSPROF, 1);
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* perf-monitor
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
flush_ring(void)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
int ret;
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
if (!dev.submit)
|
|
|
|
return;
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-05-04 17:03:43 +01:00
|
|
|
struct fd_submit_fence fence = {};
|
|
|
|
util_queue_fence_init(&fence.ready);
|
|
|
|
|
2021-06-08 21:26:14 +01:00
|
|
|
ret = fd_submit_flush(dev.submit, -1, &fence);
|
2021-05-04 17:03:43 +01:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
if (ret)
|
|
|
|
errx(1, "submit failed: %d", ret);
|
2021-05-04 17:03:43 +01:00
|
|
|
util_queue_fence_wait(&fence.ready);
|
2021-04-16 19:59:30 +01:00
|
|
|
fd_ringbuffer_del(dev.ring);
|
|
|
|
fd_submit_del(dev.submit);
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
dev.ring = NULL;
|
|
|
|
dev.submit = NULL;
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
select_counter(struct counter_group *group, int ctr, int n)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
assert(n < group->group->num_countables);
|
|
|
|
assert(ctr < group->group->num_counters);
|
|
|
|
|
|
|
|
group->label[ctr] = group->group->countables[n].name;
|
|
|
|
group->counter[ctr].select_val = n;
|
|
|
|
|
|
|
|
if (!dev.submit) {
|
|
|
|
dev.submit = fd_submit_new(dev.pipe);
|
|
|
|
dev.ring = fd_submit_new_ringbuffer(
|
|
|
|
dev.submit, 0x1000, FD_RINGBUFFER_PRIMARY | FD_RINGBUFFER_GROWABLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* bashing select register directly while gpu is active will end
|
|
|
|
* in tears.. so we need to write it via the ring:
|
|
|
|
*
|
|
|
|
* TODO it would help startup time, if gpu is loaded, to batch
|
|
|
|
* all the initial writes and do a single flush.. although that
|
|
|
|
* makes things more complicated for capturing inital sample value
|
|
|
|
*/
|
|
|
|
struct fd_ringbuffer *ring = dev.ring;
|
|
|
|
switch (dev.chipid >> 24) {
|
|
|
|
case 2:
|
|
|
|
case 3:
|
|
|
|
case 4:
|
|
|
|
OUT_PKT3(ring, CP_WAIT_FOR_IDLE, 1);
|
|
|
|
OUT_RING(ring, 0x00000000);
|
|
|
|
|
|
|
|
if (group->group->counters[ctr].enable) {
|
|
|
|
OUT_PKT0(ring, group->group->counters[ctr].enable, 1);
|
|
|
|
OUT_RING(ring, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (group->group->counters[ctr].clear) {
|
|
|
|
OUT_PKT0(ring, group->group->counters[ctr].clear, 1);
|
|
|
|
OUT_RING(ring, 1);
|
|
|
|
|
|
|
|
OUT_PKT0(ring, group->group->counters[ctr].clear, 1);
|
|
|
|
OUT_RING(ring, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
OUT_PKT0(ring, group->group->counters[ctr].select_reg, 1);
|
|
|
|
OUT_RING(ring, n);
|
|
|
|
|
|
|
|
if (group->group->counters[ctr].enable) {
|
|
|
|
OUT_PKT0(ring, group->group->counters[ctr].enable, 1);
|
|
|
|
OUT_RING(ring, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
case 6:
|
|
|
|
OUT_PKT7(ring, CP_WAIT_FOR_IDLE, 0);
|
|
|
|
|
|
|
|
if (group->group->counters[ctr].enable) {
|
|
|
|
OUT_PKT4(ring, group->group->counters[ctr].enable, 1);
|
|
|
|
OUT_RING(ring, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (group->group->counters[ctr].clear) {
|
|
|
|
OUT_PKT4(ring, group->group->counters[ctr].clear, 1);
|
|
|
|
OUT_RING(ring, 1);
|
|
|
|
|
|
|
|
OUT_PKT4(ring, group->group->counters[ctr].clear, 1);
|
|
|
|
OUT_RING(ring, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
OUT_PKT4(ring, group->group->counters[ctr].select_reg, 1);
|
|
|
|
OUT_RING(ring, n);
|
|
|
|
|
|
|
|
if (group->group->counters[ctr].enable) {
|
|
|
|
OUT_PKT4(ring, group->group->counters[ctr].enable, 1);
|
|
|
|
OUT_RING(ring, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
group->last[ctr] = *group->counter[ctr].val_lo;
|
|
|
|
group->stime[ctr] = gettime_us();
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
resample_counter(struct counter_group *group, int ctr)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
uint32_t val = *group->counter[ctr].val_lo;
|
|
|
|
uint32_t t = gettime_us();
|
|
|
|
uint32_t dt = delta(group->stime[ctr], t);
|
|
|
|
uint32_t dval = delta(group->last[ctr], val);
|
|
|
|
group->current[ctr] = (float)dval * 1000000.0 / (float)dt;
|
|
|
|
group->last[ctr] = val;
|
|
|
|
group->stime[ctr] = t;
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* sample all the counters: */
|
|
|
|
static void
|
|
|
|
resample(void)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
static uint64_t last_time;
|
|
|
|
uint64_t current_time = gettime_us();
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2022-05-12 20:32:12 +01:00
|
|
|
if ((current_time - last_time) < (options.refresh_ms * 1000 / 2))
|
2021-04-16 19:59:30 +01:00
|
|
|
return;
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
last_time = current_time;
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
for (unsigned i = 0; i < dev.ngroups; i++) {
|
|
|
|
struct counter_group *group = &dev.groups[i];
|
|
|
|
for (unsigned j = 0; j < group->group->num_counters; j++) {
|
|
|
|
resample_counter(group, j);
|
|
|
|
}
|
|
|
|
}
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The UI
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define COLOR_GROUP_HEADER 1
|
|
|
|
#define COLOR_FOOTER 2
|
|
|
|
#define COLOR_INVERSE 3
|
|
|
|
|
|
|
|
static int w, h;
|
|
|
|
static int ctr_width;
|
|
|
|
static int max_rows, current_cntr = 1;
|
|
|
|
|
|
|
|
static void
|
|
|
|
redraw_footer(WINDOW *win)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
char *footer;
|
|
|
|
int n;
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
n = asprintf(&footer, " fdperf: a%" CHIP_FMT " (%.2fMHz..%.2fMHz)",
|
|
|
|
CHIP_ARGS(dev.chipid), ((float)dev.min_freq) / 1000000.0,
|
|
|
|
((float)dev.max_freq) / 1000000.0);
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
wmove(win, h - 1, 0);
|
|
|
|
wattron(win, COLOR_PAIR(COLOR_FOOTER));
|
|
|
|
waddstr(win, footer);
|
|
|
|
whline(win, ' ', w - n);
|
|
|
|
wattroff(win, COLOR_PAIR(COLOR_FOOTER));
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
free(footer);
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
redraw_group_header(WINDOW *win, int row, const char *name)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
wmove(win, row, 0);
|
|
|
|
wattron(win, A_BOLD);
|
|
|
|
wattron(win, COLOR_PAIR(COLOR_GROUP_HEADER));
|
|
|
|
waddstr(win, name);
|
|
|
|
whline(win, ' ', w - strlen(name));
|
|
|
|
wattroff(win, COLOR_PAIR(COLOR_GROUP_HEADER));
|
|
|
|
wattroff(win, A_BOLD);
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
redraw_counter_label(WINDOW *win, int row, const char *name, bool selected)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
int n = strlen(name);
|
|
|
|
assert(n <= ctr_width);
|
|
|
|
wmove(win, row, 0);
|
|
|
|
whline(win, ' ', ctr_width - n);
|
|
|
|
wmove(win, row, ctr_width - n);
|
|
|
|
if (selected)
|
|
|
|
wattron(win, COLOR_PAIR(COLOR_INVERSE));
|
|
|
|
waddstr(win, name);
|
|
|
|
if (selected)
|
|
|
|
wattroff(win, COLOR_PAIR(COLOR_INVERSE));
|
|
|
|
waddstr(win, ": ");
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
redraw_counter_value_cycles(WINDOW *win, float val)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
char *str;
|
|
|
|
int x = getcurx(win);
|
|
|
|
int valwidth = w - x;
|
|
|
|
int barwidth, n;
|
|
|
|
|
|
|
|
/* convert to fraction of max freq: */
|
|
|
|
val = val / (float)dev.max_freq;
|
|
|
|
|
|
|
|
/* figure out percentage-bar width: */
|
|
|
|
barwidth = (int)(val * valwidth);
|
|
|
|
|
|
|
|
/* sometimes things go over 100%.. idk why, could be
|
|
|
|
* things running faster than base clock, or counter
|
|
|
|
* summing up cycles in multiple cores?
|
|
|
|
*/
|
|
|
|
barwidth = MIN2(barwidth, valwidth - 1);
|
|
|
|
|
|
|
|
n = asprintf(&str, "%.2f%%", 100.0 * val);
|
|
|
|
wattron(win, COLOR_PAIR(COLOR_INVERSE));
|
|
|
|
waddnstr(win, str, barwidth);
|
|
|
|
if (barwidth > n) {
|
|
|
|
whline(win, ' ', barwidth - n);
|
|
|
|
wmove(win, getcury(win), x + barwidth);
|
|
|
|
}
|
|
|
|
wattroff(win, COLOR_PAIR(COLOR_INVERSE));
|
|
|
|
if (barwidth < n)
|
|
|
|
waddstr(win, str + barwidth);
|
|
|
|
whline(win, ' ', w - getcurx(win));
|
|
|
|
|
|
|
|
free(str);
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
redraw_counter_value_raw(WINDOW *win, float val)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
char *str;
|
|
|
|
(void)asprintf(&str, "%'.2f", val);
|
|
|
|
waddstr(win, str);
|
|
|
|
whline(win, ' ', w - getcurx(win));
|
|
|
|
free(str);
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2021-04-16 19:59:30 +01:00
|
|
|
redraw_counter(WINDOW *win, int row, struct counter_group *group, int ctr,
|
|
|
|
bool selected)
|
2019-11-19 22:53:49 +00:00
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
redraw_counter_label(win, row, group->label[ctr], selected);
|
|
|
|
|
|
|
|
/* quick hack, if the label has "CYCLE" in the name, it is
|
|
|
|
* probably a cycle counter ;-)
|
|
|
|
* Perhaps add more info in rnndb schema to know how to
|
|
|
|
* treat individual counters (ie. which are cycles, and
|
|
|
|
* for those we want to present as a percentage do we
|
|
|
|
* need to scale the result.. ie. is it running at some
|
|
|
|
* multiple or divisor of core clk, etc)
|
|
|
|
*
|
|
|
|
* TODO it would be much more clever to get this from xml
|
|
|
|
* Also.. in some cases I think we want to know how many
|
|
|
|
* units the counter is counting for, ie. if a320 has 2x
|
|
|
|
* shader as a306 we might need to scale the result..
|
|
|
|
*/
|
|
|
|
if (strstr(group->label[ctr], "CYCLE") ||
|
|
|
|
strstr(group->label[ctr], "BUSY") || strstr(group->label[ctr], "IDLE"))
|
|
|
|
redraw_counter_value_cycles(win, group->current[ctr]);
|
|
|
|
else
|
|
|
|
redraw_counter_value_raw(win, group->current[ctr]);
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
redraw(WINDOW *win)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
static int scroll = 0;
|
|
|
|
int max, row = 0;
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
w = getmaxx(win);
|
|
|
|
h = getmaxy(win);
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
max = h - 3;
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
if ((current_cntr - scroll) > (max - 1)) {
|
|
|
|
scroll = current_cntr - (max - 1);
|
|
|
|
} else if ((current_cntr - 1) < scroll) {
|
|
|
|
scroll = current_cntr - 1;
|
|
|
|
}
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
for (unsigned i = 0; i < dev.ngroups; i++) {
|
|
|
|
struct counter_group *group = &dev.groups[i];
|
|
|
|
unsigned j = 0;
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
/* NOTE skip CP the first CP counter */
|
|
|
|
if (i == 0)
|
|
|
|
j++;
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
if (j < group->group->num_counters) {
|
|
|
|
if ((scroll <= row) && ((row - scroll) < max))
|
|
|
|
redraw_group_header(win, row - scroll, group->group->name);
|
|
|
|
row++;
|
|
|
|
}
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
for (; j < group->group->num_counters; j++) {
|
|
|
|
if ((scroll <= row) && ((row - scroll) < max))
|
|
|
|
redraw_counter(win, row - scroll, group, j, row == current_cntr);
|
|
|
|
row++;
|
|
|
|
}
|
|
|
|
}
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
/* convert back to physical (unscrolled) offset: */
|
|
|
|
row = max;
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
redraw_group_header(win, row, "Status");
|
|
|
|
row++;
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
/* Draw GPU freq row: */
|
|
|
|
redraw_counter_label(win, row, "Freq (MHz)", false);
|
|
|
|
redraw_counter_value_raw(win, dev.groups[0].current[0] / 1000000.0);
|
|
|
|
row++;
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
redraw_footer(win);
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
refresh();
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct counter_group *
|
|
|
|
current_counter(int *ctr)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < dev.ngroups; i++) {
|
|
|
|
struct counter_group *group = &dev.groups[i];
|
|
|
|
unsigned j = 0;
|
|
|
|
|
|
|
|
/* NOTE skip the first CP counter (CP_ALWAYS_COUNT) */
|
|
|
|
if (i == 0)
|
|
|
|
j++;
|
|
|
|
|
|
|
|
/* account for group header: */
|
|
|
|
if (j < group->group->num_counters) {
|
|
|
|
/* cannot select group header.. return null to indicate this
|
|
|
|
* main_ui():
|
|
|
|
*/
|
|
|
|
if (n == current_cntr)
|
|
|
|
return NULL;
|
|
|
|
n++;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (; j < group->group->num_counters; j++) {
|
|
|
|
if (n == current_cntr) {
|
|
|
|
if (ctr)
|
|
|
|
*ctr = j;
|
|
|
|
return group;
|
|
|
|
}
|
|
|
|
n++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(0);
|
|
|
|
return NULL;
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
counter_dialog(void)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
WINDOW *dialog;
|
|
|
|
struct counter_group *group;
|
|
|
|
int cnt = 0, current = 0, scroll;
|
|
|
|
|
|
|
|
/* figure out dialog size: */
|
|
|
|
int dh = h / 2;
|
|
|
|
int dw = ctr_width + 2;
|
|
|
|
|
|
|
|
group = current_counter(&cnt);
|
|
|
|
|
|
|
|
/* find currently selected idx (note there can be discontinuities
|
|
|
|
* so the selected value does not map 1:1 to current idx)
|
|
|
|
*/
|
|
|
|
uint32_t selected = group->counter[cnt].select_val;
|
|
|
|
for (int i = 0; i < group->group->num_countables; i++) {
|
|
|
|
if (group->group->countables[i].selector == selected) {
|
|
|
|
current = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* scrolling offset, if dialog is too small for all the choices: */
|
|
|
|
scroll = 0;
|
|
|
|
|
|
|
|
dialog = newwin(dh, dw, (h - dh) / 2, (w - dw) / 2);
|
|
|
|
box(dialog, 0, 0);
|
|
|
|
wrefresh(dialog);
|
|
|
|
keypad(dialog, TRUE);
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
int max = MIN2(dh - 2, group->group->num_countables);
|
|
|
|
int selector = -1;
|
|
|
|
|
|
|
|
if ((current - scroll) >= (dh - 3)) {
|
|
|
|
scroll = current - (dh - 3);
|
|
|
|
} else if (current < scroll) {
|
|
|
|
scroll = current;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < max; i++) {
|
|
|
|
int n = scroll + i;
|
|
|
|
wmove(dialog, i + 1, 1);
|
|
|
|
if (n == current) {
|
|
|
|
assert(n < group->group->num_countables);
|
|
|
|
selector = group->group->countables[n].selector;
|
|
|
|
wattron(dialog, COLOR_PAIR(COLOR_INVERSE));
|
|
|
|
}
|
|
|
|
if (n < group->group->num_countables)
|
|
|
|
waddstr(dialog, group->group->countables[n].name);
|
|
|
|
whline(dialog, ' ', dw - getcurx(dialog) - 1);
|
|
|
|
if (n == current)
|
|
|
|
wattroff(dialog, COLOR_PAIR(COLOR_INVERSE));
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(selector >= 0);
|
|
|
|
|
|
|
|
switch (wgetch(dialog)) {
|
|
|
|
case KEY_UP:
|
|
|
|
current = MAX2(0, current - 1);
|
|
|
|
break;
|
|
|
|
case KEY_DOWN:
|
|
|
|
current = MIN2(group->group->num_countables - 1, current + 1);
|
|
|
|
break;
|
|
|
|
case KEY_LEFT:
|
|
|
|
case KEY_ENTER:
|
|
|
|
/* select new sampler */
|
|
|
|
select_counter(group, cnt, selector);
|
|
|
|
flush_ring();
|
|
|
|
config_save();
|
|
|
|
goto out;
|
|
|
|
case 'q':
|
|
|
|
goto out;
|
|
|
|
default:
|
|
|
|
/* ignore */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
resample();
|
|
|
|
}
|
2019-11-19 22:53:49 +00:00
|
|
|
|
|
|
|
out:
|
2021-04-16 19:59:30 +01:00
|
|
|
wborder(dialog, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ');
|
|
|
|
delwin(dialog);
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
scroll_cntr(int amount)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
if (amount < 0) {
|
|
|
|
current_cntr = MAX2(1, current_cntr + amount);
|
|
|
|
if (current_counter(NULL) == NULL) {
|
|
|
|
current_cntr = MAX2(1, current_cntr - 1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
current_cntr = MIN2(max_rows - 1, current_cntr + amount);
|
|
|
|
if (current_counter(NULL) == NULL)
|
|
|
|
current_cntr = MIN2(max_rows - 1, current_cntr + 1);
|
|
|
|
}
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
main_ui(void)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
WINDOW *mainwin;
|
|
|
|
uint32_t last_time = gettime_us();
|
|
|
|
|
|
|
|
/* curses setup: */
|
|
|
|
mainwin = initscr();
|
|
|
|
if (!mainwin)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
cbreak();
|
2022-05-12 20:32:12 +01:00
|
|
|
wtimeout(mainwin, options.refresh_ms);
|
2021-04-16 19:59:30 +01:00
|
|
|
noecho();
|
|
|
|
keypad(mainwin, TRUE);
|
|
|
|
curs_set(0);
|
|
|
|
start_color();
|
|
|
|
init_pair(COLOR_GROUP_HEADER, COLOR_WHITE, COLOR_GREEN);
|
|
|
|
init_pair(COLOR_FOOTER, COLOR_WHITE, COLOR_BLUE);
|
|
|
|
init_pair(COLOR_INVERSE, COLOR_BLACK, COLOR_WHITE);
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
switch (wgetch(mainwin)) {
|
|
|
|
case KEY_UP:
|
|
|
|
scroll_cntr(-1);
|
|
|
|
break;
|
|
|
|
case KEY_DOWN:
|
|
|
|
scroll_cntr(+1);
|
|
|
|
break;
|
|
|
|
case KEY_NPAGE: /* page-down */
|
|
|
|
/* TODO figure out # of rows visible? */
|
|
|
|
scroll_cntr(+15);
|
|
|
|
break;
|
|
|
|
case KEY_PPAGE: /* page-up */
|
|
|
|
/* TODO figure out # of rows visible? */
|
|
|
|
scroll_cntr(-15);
|
|
|
|
break;
|
|
|
|
case KEY_RIGHT:
|
|
|
|
counter_dialog();
|
|
|
|
break;
|
|
|
|
case 'q':
|
|
|
|
goto out;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* ignore */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
resample();
|
|
|
|
redraw(mainwin);
|
|
|
|
|
|
|
|
/* restore the counters every 0.5s in case the GPU has suspended,
|
|
|
|
* in which case the current selected countables will have reset:
|
|
|
|
*/
|
|
|
|
uint32_t t = gettime_us();
|
|
|
|
if (delta(last_time, t) > 500000) {
|
|
|
|
restore_counter_groups();
|
|
|
|
flush_ring();
|
|
|
|
last_time = t;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* restore settings.. maybe we need an atexit()??*/
|
2019-11-19 22:53:49 +00:00
|
|
|
out:
|
2021-04-16 19:59:30 +01:00
|
|
|
delwin(mainwin);
|
|
|
|
endwin();
|
|
|
|
refresh();
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
2022-05-12 21:04:19 +01:00
|
|
|
static void
|
|
|
|
dump_counters(void)
|
|
|
|
{
|
|
|
|
resample();
|
|
|
|
sleep_us(options.refresh_ms * 1000);
|
|
|
|
resample();
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < dev.ngroups; i++) {
|
|
|
|
const struct counter_group *group = &dev.groups[i];
|
|
|
|
for (unsigned j = 0; j < group->group->num_counters; j++) {
|
|
|
|
const char *label = group->label[j];
|
|
|
|
float val = group->current[j];
|
|
|
|
|
|
|
|
/* we did not config the first CP counter */
|
|
|
|
if (i == 0 && j == 0)
|
|
|
|
label = group->group->countables[0].name;
|
|
|
|
|
|
|
|
int n = printf("%s: ", label) - 2;
|
|
|
|
while (n++ < ctr_width)
|
|
|
|
fputc(' ', stdout);
|
|
|
|
|
|
|
|
if (strstr(label, "CYCLE") ||
|
|
|
|
strstr(label, "BUSY") ||
|
|
|
|
strstr(label, "IDLE")) {
|
|
|
|
val = val / dev.max_freq * 100.0f;
|
|
|
|
printf("%.2f%%\n", val);
|
|
|
|
} else {
|
|
|
|
printf("%'.2f\n", val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-20 19:56:57 +00:00
|
|
|
static void
|
|
|
|
restore_counter_groups(void)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
for (unsigned i = 0; i < dev.ngroups; i++) {
|
|
|
|
struct counter_group *group = &dev.groups[i];
|
|
|
|
unsigned j = 0;
|
|
|
|
|
|
|
|
/* NOTE skip CP the first CP counter */
|
|
|
|
if (i == 0)
|
|
|
|
j++;
|
|
|
|
|
|
|
|
for (; j < group->group->num_counters; j++) {
|
|
|
|
select_counter(group, j, group->counter[j].select_val);
|
|
|
|
}
|
|
|
|
}
|
2019-11-20 19:56:57 +00:00
|
|
|
}
|
|
|
|
|
2019-11-19 22:53:49 +00:00
|
|
|
static void
|
|
|
|
setup_counter_groups(const struct fd_perfcntr_group *groups)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
for (unsigned i = 0; i < dev.ngroups; i++) {
|
|
|
|
struct counter_group *group = &dev.groups[i];
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
group->group = &groups[i];
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
max_rows += group->group->num_counters + 1;
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
/* the first CP counter is hidden: */
|
|
|
|
if (i == 0) {
|
|
|
|
max_rows--;
|
|
|
|
if (group->group->num_counters <= 1)
|
|
|
|
max_rows--;
|
|
|
|
}
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
for (unsigned j = 0; j < group->group->num_counters; j++) {
|
|
|
|
group->counter[j].counter = &group->group->counters[j];
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
group->counter[j].val_hi =
|
|
|
|
dev.io + (group->counter[j].counter->counter_reg_hi * 4);
|
|
|
|
group->counter[j].val_lo =
|
|
|
|
dev.io + (group->counter[j].counter->counter_reg_lo * 4);
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
group->counter[j].select_val = j;
|
|
|
|
}
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
for (unsigned j = 0; j < group->group->num_countables; j++) {
|
|
|
|
ctr_width =
|
|
|
|
MAX2(ctr_width, strlen(group->group->countables[j].name) + 1);
|
|
|
|
}
|
|
|
|
}
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* configuration / persistence
|
|
|
|
*/
|
|
|
|
|
|
|
|
static config_t cfg;
|
|
|
|
static config_setting_t *setting;
|
|
|
|
|
|
|
|
static void
|
|
|
|
config_save(void)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
for (unsigned i = 0; i < dev.ngroups; i++) {
|
|
|
|
struct counter_group *group = &dev.groups[i];
|
|
|
|
unsigned j = 0;
|
|
|
|
|
|
|
|
/* NOTE skip CP the first CP counter */
|
|
|
|
if (i == 0)
|
|
|
|
j++;
|
|
|
|
|
|
|
|
config_setting_t *sect =
|
|
|
|
config_setting_get_member(setting, group->group->name);
|
|
|
|
|
|
|
|
for (; j < group->group->num_counters; j++) {
|
|
|
|
char name[] = "counter0000";
|
|
|
|
sprintf(name, "counter%d", j);
|
|
|
|
config_setting_t *s = config_setting_lookup(sect, name);
|
|
|
|
config_setting_set_int(s, group->counter[j].select_val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
config_write_file(&cfg, "fdperf.cfg");
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
config_restore(void)
|
|
|
|
{
|
2021-04-16 19:59:30 +01:00
|
|
|
char *str;
|
|
|
|
|
|
|
|
config_init(&cfg);
|
|
|
|
|
|
|
|
/* Read the file. If there is an error, report it and exit. */
|
|
|
|
if (!config_read_file(&cfg, "fdperf.cfg")) {
|
|
|
|
warn("could not restore settings");
|
|
|
|
}
|
|
|
|
|
|
|
|
config_setting_t *root = config_root_setting(&cfg);
|
|
|
|
|
|
|
|
/* per device settings: */
|
|
|
|
(void)asprintf(&str, "a%dxx", dev.chipid >> 24);
|
|
|
|
setting = config_setting_get_member(root, str);
|
|
|
|
if (!setting)
|
|
|
|
setting = config_setting_add(root, str, CONFIG_TYPE_GROUP);
|
|
|
|
free(str);
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < dev.ngroups; i++) {
|
|
|
|
struct counter_group *group = &dev.groups[i];
|
|
|
|
unsigned j = 0;
|
|
|
|
|
|
|
|
/* NOTE skip CP the first CP counter */
|
|
|
|
if (i == 0)
|
|
|
|
j++;
|
|
|
|
|
|
|
|
config_setting_t *sect =
|
|
|
|
config_setting_get_member(setting, group->group->name);
|
|
|
|
|
|
|
|
if (!sect) {
|
|
|
|
sect =
|
|
|
|
config_setting_add(setting, group->group->name, CONFIG_TYPE_GROUP);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (; j < group->group->num_counters; j++) {
|
|
|
|
char name[] = "counter0000";
|
|
|
|
sprintf(name, "counter%d", j);
|
|
|
|
config_setting_t *s = config_setting_lookup(sect, name);
|
|
|
|
if (!s) {
|
|
|
|
config_setting_add(sect, name, CONFIG_TYPE_INT);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
select_counter(group, j, config_setting_get_int(s));
|
|
|
|
}
|
|
|
|
}
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|
|
|
|
|
2022-05-12 20:32:12 +01:00
|
|
|
static void
|
|
|
|
print_usage(const char *argv0)
|
|
|
|
{
|
|
|
|
fprintf(stderr,
|
|
|
|
"Usage: %s [OPTION]...\n"
|
|
|
|
"\n"
|
|
|
|
" -r <N> refresh every N milliseconds\n"
|
2022-05-12 21:04:19 +01:00
|
|
|
" -d dump counters and exit\n"
|
2022-05-12 20:32:12 +01:00
|
|
|
" -h show this message\n",
|
|
|
|
argv0);
|
|
|
|
exit(2);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
parse_options(int argc, char **argv)
|
|
|
|
{
|
|
|
|
int c;
|
|
|
|
|
2022-05-12 21:04:19 +01:00
|
|
|
while ((c = getopt(argc, argv, "r:d")) != -1) {
|
2022-05-12 20:32:12 +01:00
|
|
|
switch (c) {
|
|
|
|
case 'r':
|
|
|
|
options.refresh_ms = atoi(optarg);
|
|
|
|
break;
|
2022-05-12 21:04:19 +01:00
|
|
|
case 'd':
|
|
|
|
options.dump = true;
|
|
|
|
break;
|
2022-05-12 20:32:12 +01:00
|
|
|
default:
|
|
|
|
print_usage(argv[0]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-19 22:53:49 +00:00
|
|
|
/*
|
|
|
|
* main
|
|
|
|
*/
|
|
|
|
|
|
|
|
int
|
|
|
|
main(int argc, char **argv)
|
|
|
|
{
|
2022-05-12 20:32:12 +01:00
|
|
|
parse_options(argc, argv);
|
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
find_device();
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
const struct fd_perfcntr_group *groups;
|
2021-07-31 21:46:50 +01:00
|
|
|
struct fd_dev_id dev_id = {
|
|
|
|
.gpu_id = (dev.chipid >> 24) * 100,
|
|
|
|
};
|
|
|
|
groups = fd_perfcntrs(&dev_id, &dev.ngroups);
|
2021-04-16 19:59:30 +01:00
|
|
|
if (!groups) {
|
|
|
|
errx(1, "no perfcntr support");
|
|
|
|
}
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
dev.groups = calloc(dev.ngroups, sizeof(struct counter_group));
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
setlocale(LC_NUMERIC, "en_US.UTF-8");
|
2020-03-10 15:10:22 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
setup_counter_groups(groups);
|
|
|
|
restore_counter_groups();
|
|
|
|
config_restore();
|
|
|
|
flush_ring();
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2022-05-12 21:04:19 +01:00
|
|
|
if (options.dump)
|
|
|
|
dump_counters();
|
|
|
|
else
|
|
|
|
main_ui();
|
2019-11-19 22:53:49 +00:00
|
|
|
|
2021-04-16 19:59:30 +01:00
|
|
|
return 0;
|
2019-11-19 22:53:49 +00:00
|
|
|
}
|