perf drm_pmu: Add a tool like PMU to expose DRM information
DRM clients expose information through usage stats as documented in
Documentation/gpu/drm-usage-stats.rst (available online at
https://docs.kernel.org/gpu/drm-usage-stats.html). Add a tool like
PMU, similar to the hwmon PMU, that exposes DRM information. For
example on a tigerlake laptop:
```
$ perf list drm
List of pre-defined events (to be used in -e or -M):
drm:
drm-active-stolen-system0
[Total memory active in one or more engines. Unit: drm_i915]
drm-active-system0
[Total memory active in one or more engines. Unit: drm_i915]
drm-engine-capacity-video
[Engine capacity. Unit: drm_i915]
drm-engine-copy
[Utilization in ns. Unit: drm_i915]
drm-engine-render
[Utilization in ns. Unit: drm_i915]
drm-engine-video
[Utilization in ns. Unit: drm_i915]
drm-engine-video-enhance
[Utilization in ns. Unit: drm_i915]
drm-purgeable-stolen-system0
[Size of resident and purgeable memory bufers. Unit: drm_i915]
drm-purgeable-system0
[Size of resident and purgeable memory bufers. Unit: drm_i915]
drm-resident-stolen-system0
[Size of resident memory bufers. Unit: drm_i915]
drm-resident-system0
[Size of resident memory bufers. Unit: drm_i915]
drm-shared-stolen-system0
[Size of shared memory bufers. Unit: drm_i915]
drm-shared-system0
[Size of shared memory bufers. Unit: drm_i915]
drm-total-stolen-system0
[Size of shared and private memory. Unit: drm_i915]
drm-total-system0
[Size of shared and private memory. Unit: drm_i915]
```
System wide data can be gathered:
```
$ perf stat -x, -I 1000 -e drm-active-stolen-system0,drm-active-system0,drm-engine-capacity-video,drm-engine-copy,drm-engine-render,drm-engine-video,drm-engine-video-enhance,drm-purgeable-stolen-system0,drm-purgeable-system0,drm-resident-stolen-system0,drm-resident-system0,drm-shared-stolen-system0,drm-shared-system0,drm-total-stolen-system0,drm-total-system0
1.000904910,0,bytes,drm-active-stolen-system0,1,100.00,,
1.000904910,0,bytes,drm-active-system0,1,100.00,,
1.000904910,36,capacity,drm-engine-capacity-video,1,100.00,,
1.000904910,0,ns,drm-engine-copy,1,100.00,,
1.000904910,1472970566175,ns,drm-engine-render,1,100.00,,
1.000904910,0,ns,drm-engine-video,1,100.00,,
1.000904910,0,ns,drm-engine-video-enhance,1,100.00,,
1.000904910,0,bytes,drm-purgeable-stolen-system0,1,100.00,,
1.000904910,38199296,bytes,drm-purgeable-system0,1,100.00,,
1.000904910,0,bytes,drm-resident-stolen-system0,1,100.00,,
1.000904910,4643196928,bytes,drm-resident-system0,1,100.00,,
1.000904910,0,bytes,drm-shared-stolen-system0,1,100.00,,
1.000904910,1886871552,bytes,drm-shared-system0,1,100.00,,
1.000904910,0,bytes,drm-total-stolen-system0,1,100.00,,
1.000904910,4643196928,bytes,drm-total-system0,1,100.00,,
2.264426839,0,bytes,drm-active-stolen-system0,1,100.00,,
```
Or for a particular process:
```
$ perf stat -x, -I 1000 -e drm-active-stolen-system0,drm-active-system0,drm-engine-capacity-video,drm-engine-copy,drm-engine-render,drm-engine-video,drm-engine-video-enhance,drm-purgeable-stolen-system0,drm-purgeable-system0,drm-resident-stolen-system0,drm-resident-system0,drm-shared-stolen-system0,drm-shared-system0,drm-total-stolen-system0,drm-total-system0 -p 200027
1.001040274,0,bytes,drm-active-stolen-system0,6,100.00,,
1.001040274,0,bytes,drm-active-system0,6,100.00,,
1.001040274,12,capacity,drm-engine-capacity-video,6,100.00,,
1.001040274,0,ns,drm-engine-copy,6,100.00,,
1.001040274,1542300,ns,drm-engine-render,6,100.00,,
1.001040274,0,ns,drm-engine-video,6,100.00,,
1.001040274,0,ns,drm-engine-video-enhance,6,100.00,,
1.001040274,0,bytes,drm-purgeable-stolen-system0,6,100.00,,
1.001040274,13516800,bytes,drm-purgeable-system0,6,100.00,,
1.001040274,0,bytes,drm-resident-stolen-system0,6,100.00,,
1.001040274,27746304,bytes,drm-resident-system0,6,100.00,,
1.001040274,0,bytes,drm-shared-stolen-system0,6,100.00,,
1.001040274,0,bytes,drm-shared-system0,6,100.00,,
1.001040274,0,bytes,drm-total-stolen-system0,6,100.00,,
1.001040274,27746304,bytes,drm-total-system0,6,100.00,,
2.016629075,0,bytes,drm-active-stolen-system0,6,100.00,,
```
As with the hwmon PMU, high numbered PMU types are used to encode
multiple possible "DRM" PMUs. The appropriate fdinfo is found by
scanning /proc and filtering which fdinfos to read with stat. To avoid
some unneeding scanning, events not starting with "drm-" are
ignored. The patch builds on commit 57e13264dcea ("perf pmus:
Restructure pmu_read_sysfs to scan fewer PMUs") and later so that only
if full wild carding is being done, the PMU starts with "drm_" or the
event starts with "drm-" will /proc be scanned. That is there should
be little to no cost in this PMU unless DRM events are requested.
Signed-off-by: Ian Rogers <irogers@google.com>
Link: https://lore.kernel.org/r/20250624231837.179536-3-irogers@google.com
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
2025-06-24 16:18:36 -07:00
|
|
|
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
|
|
|
#include "drm_pmu.h"
|
|
|
|
#include "counts.h"
|
|
|
|
#include "cpumap.h"
|
|
|
|
#include "debug.h"
|
|
|
|
#include "evsel.h"
|
|
|
|
#include "pmu.h"
|
|
|
|
#include <perf/threadmap.h>
|
|
|
|
#include <api/fs/fs.h>
|
|
|
|
#include <api/io.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <dirent.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <linux/unistd.h>
|
|
|
|
#include <linux/kcmp.h>
|
|
|
|
#include <linux/zalloc.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/syscall.h>
|
|
|
|
#include <sys/sysmacros.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
|
|
|
enum drm_pmu_unit {
|
|
|
|
DRM_PMU_UNIT_BYTES,
|
|
|
|
DRM_PMU_UNIT_CAPACITY,
|
|
|
|
DRM_PMU_UNIT_CYCLES,
|
|
|
|
DRM_PMU_UNIT_HZ,
|
|
|
|
DRM_PMU_UNIT_NS,
|
|
|
|
|
|
|
|
DRM_PMU_UNIT_MAX,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct drm_pmu_event {
|
|
|
|
const char *name;
|
|
|
|
const char *desc;
|
|
|
|
enum drm_pmu_unit unit;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct drm_pmu {
|
|
|
|
struct perf_pmu pmu;
|
|
|
|
struct drm_pmu_event *events;
|
|
|
|
int num_events;
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char * const drm_pmu_unit_strs[DRM_PMU_UNIT_MAX] = {
|
|
|
|
"bytes",
|
|
|
|
"capacity",
|
|
|
|
"cycles",
|
|
|
|
"hz",
|
|
|
|
"ns",
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char * const drm_pmu_scale_unit_strs[DRM_PMU_UNIT_MAX] = {
|
|
|
|
"1bytes",
|
|
|
|
"1capacity",
|
|
|
|
"1cycles",
|
|
|
|
"1hz",
|
|
|
|
"1ns",
|
|
|
|
};
|
|
|
|
|
|
|
|
bool perf_pmu__is_drm(const struct perf_pmu *pmu)
|
|
|
|
{
|
|
|
|
return pmu && pmu->type >= PERF_PMU_TYPE_DRM_START &&
|
|
|
|
pmu->type <= PERF_PMU_TYPE_DRM_END;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool evsel__is_drm(const struct evsel *evsel)
|
|
|
|
{
|
|
|
|
return perf_pmu__is_drm(evsel->pmu);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct drm_pmu *add_drm_pmu(struct list_head *pmus, char *line, size_t line_len)
|
|
|
|
{
|
|
|
|
struct drm_pmu *drm;
|
|
|
|
struct perf_pmu *pmu;
|
|
|
|
const char *name;
|
|
|
|
__u32 max_drm_pmu_type = 0, type;
|
|
|
|
int i = 12;
|
|
|
|
|
|
|
|
if (line[line_len - 1] == '\n')
|
|
|
|
line[line_len - 1] = '\0';
|
|
|
|
while (isspace(line[i]))
|
|
|
|
i++;
|
|
|
|
|
|
|
|
line[--i] = '_';
|
|
|
|
line[--i] = 'm';
|
|
|
|
line[--i] = 'r';
|
|
|
|
line[--i] = 'd';
|
|
|
|
name = &line[i];
|
|
|
|
|
|
|
|
list_for_each_entry(pmu, pmus, list) {
|
|
|
|
if (!perf_pmu__is_drm(pmu))
|
|
|
|
continue;
|
|
|
|
if (pmu->type > max_drm_pmu_type)
|
|
|
|
max_drm_pmu_type = pmu->type;
|
|
|
|
if (!strcmp(pmu->name, name)) {
|
|
|
|
/* PMU already exists. */
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (max_drm_pmu_type != 0)
|
|
|
|
type = max_drm_pmu_type + 1;
|
|
|
|
else
|
|
|
|
type = PERF_PMU_TYPE_DRM_START;
|
|
|
|
|
|
|
|
if (type > PERF_PMU_TYPE_DRM_END) {
|
|
|
|
zfree(&drm);
|
|
|
|
pr_err("Unable to encode DRM PMU type for %s\n", name);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
drm = zalloc(sizeof(*drm));
|
|
|
|
if (!drm)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (perf_pmu__init(&drm->pmu, type, name) != 0) {
|
|
|
|
perf_pmu__delete(&drm->pmu);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
drm->pmu.cpus = perf_cpu_map__new("0");
|
|
|
|
if (!drm->pmu.cpus) {
|
|
|
|
perf_pmu__delete(&drm->pmu);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return drm;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static bool starts_with(const char *str, const char *prefix)
|
|
|
|
{
|
|
|
|
return !strncmp(prefix, str, strlen(prefix));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int add_event(struct drm_pmu_event **events, int *num_events,
|
|
|
|
const char *line, enum drm_pmu_unit unit, const char *desc)
|
|
|
|
{
|
|
|
|
const char *colon = strchr(line, ':');
|
|
|
|
struct drm_pmu_event *tmp;
|
|
|
|
|
|
|
|
if (!colon)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
tmp = reallocarray(*events, *num_events + 1, sizeof(struct drm_pmu_event));
|
|
|
|
if (!tmp)
|
|
|
|
return -ENOMEM;
|
|
|
|
tmp[*num_events].unit = unit;
|
|
|
|
tmp[*num_events].desc = desc;
|
|
|
|
tmp[*num_events].name = strndup(line, colon - line);
|
|
|
|
if (!tmp[*num_events].name)
|
|
|
|
return -ENOMEM;
|
|
|
|
(*num_events)++;
|
|
|
|
*events = tmp;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int read_drm_pmus_cb(void *args, int fdinfo_dir_fd, const char *fd_name)
|
|
|
|
{
|
|
|
|
struct list_head *pmus = args;
|
|
|
|
char buf[640];
|
|
|
|
struct io io;
|
|
|
|
char *line = NULL;
|
|
|
|
size_t line_len;
|
|
|
|
struct drm_pmu *drm = NULL;
|
|
|
|
struct drm_pmu_event *events = NULL;
|
|
|
|
int num_events = 0;
|
|
|
|
|
|
|
|
io__init(&io, openat(fdinfo_dir_fd, fd_name, O_RDONLY), buf, sizeof(buf));
|
|
|
|
if (io.fd == -1) {
|
|
|
|
/* Failed to open file, ignore. */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (io__getline(&io, &line, &line_len) > 0) {
|
|
|
|
if (starts_with(line, "drm-driver:")) {
|
|
|
|
drm = add_drm_pmu(pmus, line, line_len);
|
|
|
|
if (!drm)
|
|
|
|
break;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Note the string matching below is alphabetical, with more
|
|
|
|
* specific matches appearing before less specific.
|
|
|
|
*/
|
|
|
|
if (starts_with(line, "drm-active-")) {
|
|
|
|
add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
|
|
|
|
"Total memory active in one or more engines");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (starts_with(line, "drm-cycles-")) {
|
|
|
|
add_event(&events, &num_events, line, DRM_PMU_UNIT_CYCLES,
|
|
|
|
"Busy cycles");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (starts_with(line, "drm-engine-capacity-")) {
|
|
|
|
add_event(&events, &num_events, line, DRM_PMU_UNIT_CAPACITY,
|
|
|
|
"Engine capacity");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (starts_with(line, "drm-engine-")) {
|
|
|
|
add_event(&events, &num_events, line, DRM_PMU_UNIT_NS,
|
|
|
|
"Utilization in ns");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (starts_with(line, "drm-maxfreq-")) {
|
|
|
|
add_event(&events, &num_events, line, DRM_PMU_UNIT_HZ,
|
|
|
|
"Maximum frequency");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (starts_with(line, "drm-purgeable-")) {
|
|
|
|
add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
|
2025-06-30 13:51:28 +01:00
|
|
|
"Size of resident and purgeable memory buffers");
|
perf drm_pmu: Add a tool like PMU to expose DRM information
DRM clients expose information through usage stats as documented in
Documentation/gpu/drm-usage-stats.rst (available online at
https://docs.kernel.org/gpu/drm-usage-stats.html). Add a tool like
PMU, similar to the hwmon PMU, that exposes DRM information. For
example on a tigerlake laptop:
```
$ perf list drm
List of pre-defined events (to be used in -e or -M):
drm:
drm-active-stolen-system0
[Total memory active in one or more engines. Unit: drm_i915]
drm-active-system0
[Total memory active in one or more engines. Unit: drm_i915]
drm-engine-capacity-video
[Engine capacity. Unit: drm_i915]
drm-engine-copy
[Utilization in ns. Unit: drm_i915]
drm-engine-render
[Utilization in ns. Unit: drm_i915]
drm-engine-video
[Utilization in ns. Unit: drm_i915]
drm-engine-video-enhance
[Utilization in ns. Unit: drm_i915]
drm-purgeable-stolen-system0
[Size of resident and purgeable memory bufers. Unit: drm_i915]
drm-purgeable-system0
[Size of resident and purgeable memory bufers. Unit: drm_i915]
drm-resident-stolen-system0
[Size of resident memory bufers. Unit: drm_i915]
drm-resident-system0
[Size of resident memory bufers. Unit: drm_i915]
drm-shared-stolen-system0
[Size of shared memory bufers. Unit: drm_i915]
drm-shared-system0
[Size of shared memory bufers. Unit: drm_i915]
drm-total-stolen-system0
[Size of shared and private memory. Unit: drm_i915]
drm-total-system0
[Size of shared and private memory. Unit: drm_i915]
```
System wide data can be gathered:
```
$ perf stat -x, -I 1000 -e drm-active-stolen-system0,drm-active-system0,drm-engine-capacity-video,drm-engine-copy,drm-engine-render,drm-engine-video,drm-engine-video-enhance,drm-purgeable-stolen-system0,drm-purgeable-system0,drm-resident-stolen-system0,drm-resident-system0,drm-shared-stolen-system0,drm-shared-system0,drm-total-stolen-system0,drm-total-system0
1.000904910,0,bytes,drm-active-stolen-system0,1,100.00,,
1.000904910,0,bytes,drm-active-system0,1,100.00,,
1.000904910,36,capacity,drm-engine-capacity-video,1,100.00,,
1.000904910,0,ns,drm-engine-copy,1,100.00,,
1.000904910,1472970566175,ns,drm-engine-render,1,100.00,,
1.000904910,0,ns,drm-engine-video,1,100.00,,
1.000904910,0,ns,drm-engine-video-enhance,1,100.00,,
1.000904910,0,bytes,drm-purgeable-stolen-system0,1,100.00,,
1.000904910,38199296,bytes,drm-purgeable-system0,1,100.00,,
1.000904910,0,bytes,drm-resident-stolen-system0,1,100.00,,
1.000904910,4643196928,bytes,drm-resident-system0,1,100.00,,
1.000904910,0,bytes,drm-shared-stolen-system0,1,100.00,,
1.000904910,1886871552,bytes,drm-shared-system0,1,100.00,,
1.000904910,0,bytes,drm-total-stolen-system0,1,100.00,,
1.000904910,4643196928,bytes,drm-total-system0,1,100.00,,
2.264426839,0,bytes,drm-active-stolen-system0,1,100.00,,
```
Or for a particular process:
```
$ perf stat -x, -I 1000 -e drm-active-stolen-system0,drm-active-system0,drm-engine-capacity-video,drm-engine-copy,drm-engine-render,drm-engine-video,drm-engine-video-enhance,drm-purgeable-stolen-system0,drm-purgeable-system0,drm-resident-stolen-system0,drm-resident-system0,drm-shared-stolen-system0,drm-shared-system0,drm-total-stolen-system0,drm-total-system0 -p 200027
1.001040274,0,bytes,drm-active-stolen-system0,6,100.00,,
1.001040274,0,bytes,drm-active-system0,6,100.00,,
1.001040274,12,capacity,drm-engine-capacity-video,6,100.00,,
1.001040274,0,ns,drm-engine-copy,6,100.00,,
1.001040274,1542300,ns,drm-engine-render,6,100.00,,
1.001040274,0,ns,drm-engine-video,6,100.00,,
1.001040274,0,ns,drm-engine-video-enhance,6,100.00,,
1.001040274,0,bytes,drm-purgeable-stolen-system0,6,100.00,,
1.001040274,13516800,bytes,drm-purgeable-system0,6,100.00,,
1.001040274,0,bytes,drm-resident-stolen-system0,6,100.00,,
1.001040274,27746304,bytes,drm-resident-system0,6,100.00,,
1.001040274,0,bytes,drm-shared-stolen-system0,6,100.00,,
1.001040274,0,bytes,drm-shared-system0,6,100.00,,
1.001040274,0,bytes,drm-total-stolen-system0,6,100.00,,
1.001040274,27746304,bytes,drm-total-system0,6,100.00,,
2.016629075,0,bytes,drm-active-stolen-system0,6,100.00,,
```
As with the hwmon PMU, high numbered PMU types are used to encode
multiple possible "DRM" PMUs. The appropriate fdinfo is found by
scanning /proc and filtering which fdinfos to read with stat. To avoid
some unneeding scanning, events not starting with "drm-" are
ignored. The patch builds on commit 57e13264dcea ("perf pmus:
Restructure pmu_read_sysfs to scan fewer PMUs") and later so that only
if full wild carding is being done, the PMU starts with "drm_" or the
event starts with "drm-" will /proc be scanned. That is there should
be little to no cost in this PMU unless DRM events are requested.
Signed-off-by: Ian Rogers <irogers@google.com>
Link: https://lore.kernel.org/r/20250624231837.179536-3-irogers@google.com
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
2025-06-24 16:18:36 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (starts_with(line, "drm-resident-")) {
|
|
|
|
add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
|
2025-06-30 13:51:28 +01:00
|
|
|
"Size of resident memory buffers");
|
perf drm_pmu: Add a tool like PMU to expose DRM information
DRM clients expose information through usage stats as documented in
Documentation/gpu/drm-usage-stats.rst (available online at
https://docs.kernel.org/gpu/drm-usage-stats.html). Add a tool like
PMU, similar to the hwmon PMU, that exposes DRM information. For
example on a tigerlake laptop:
```
$ perf list drm
List of pre-defined events (to be used in -e or -M):
drm:
drm-active-stolen-system0
[Total memory active in one or more engines. Unit: drm_i915]
drm-active-system0
[Total memory active in one or more engines. Unit: drm_i915]
drm-engine-capacity-video
[Engine capacity. Unit: drm_i915]
drm-engine-copy
[Utilization in ns. Unit: drm_i915]
drm-engine-render
[Utilization in ns. Unit: drm_i915]
drm-engine-video
[Utilization in ns. Unit: drm_i915]
drm-engine-video-enhance
[Utilization in ns. Unit: drm_i915]
drm-purgeable-stolen-system0
[Size of resident and purgeable memory bufers. Unit: drm_i915]
drm-purgeable-system0
[Size of resident and purgeable memory bufers. Unit: drm_i915]
drm-resident-stolen-system0
[Size of resident memory bufers. Unit: drm_i915]
drm-resident-system0
[Size of resident memory bufers. Unit: drm_i915]
drm-shared-stolen-system0
[Size of shared memory bufers. Unit: drm_i915]
drm-shared-system0
[Size of shared memory bufers. Unit: drm_i915]
drm-total-stolen-system0
[Size of shared and private memory. Unit: drm_i915]
drm-total-system0
[Size of shared and private memory. Unit: drm_i915]
```
System wide data can be gathered:
```
$ perf stat -x, -I 1000 -e drm-active-stolen-system0,drm-active-system0,drm-engine-capacity-video,drm-engine-copy,drm-engine-render,drm-engine-video,drm-engine-video-enhance,drm-purgeable-stolen-system0,drm-purgeable-system0,drm-resident-stolen-system0,drm-resident-system0,drm-shared-stolen-system0,drm-shared-system0,drm-total-stolen-system0,drm-total-system0
1.000904910,0,bytes,drm-active-stolen-system0,1,100.00,,
1.000904910,0,bytes,drm-active-system0,1,100.00,,
1.000904910,36,capacity,drm-engine-capacity-video,1,100.00,,
1.000904910,0,ns,drm-engine-copy,1,100.00,,
1.000904910,1472970566175,ns,drm-engine-render,1,100.00,,
1.000904910,0,ns,drm-engine-video,1,100.00,,
1.000904910,0,ns,drm-engine-video-enhance,1,100.00,,
1.000904910,0,bytes,drm-purgeable-stolen-system0,1,100.00,,
1.000904910,38199296,bytes,drm-purgeable-system0,1,100.00,,
1.000904910,0,bytes,drm-resident-stolen-system0,1,100.00,,
1.000904910,4643196928,bytes,drm-resident-system0,1,100.00,,
1.000904910,0,bytes,drm-shared-stolen-system0,1,100.00,,
1.000904910,1886871552,bytes,drm-shared-system0,1,100.00,,
1.000904910,0,bytes,drm-total-stolen-system0,1,100.00,,
1.000904910,4643196928,bytes,drm-total-system0,1,100.00,,
2.264426839,0,bytes,drm-active-stolen-system0,1,100.00,,
```
Or for a particular process:
```
$ perf stat -x, -I 1000 -e drm-active-stolen-system0,drm-active-system0,drm-engine-capacity-video,drm-engine-copy,drm-engine-render,drm-engine-video,drm-engine-video-enhance,drm-purgeable-stolen-system0,drm-purgeable-system0,drm-resident-stolen-system0,drm-resident-system0,drm-shared-stolen-system0,drm-shared-system0,drm-total-stolen-system0,drm-total-system0 -p 200027
1.001040274,0,bytes,drm-active-stolen-system0,6,100.00,,
1.001040274,0,bytes,drm-active-system0,6,100.00,,
1.001040274,12,capacity,drm-engine-capacity-video,6,100.00,,
1.001040274,0,ns,drm-engine-copy,6,100.00,,
1.001040274,1542300,ns,drm-engine-render,6,100.00,,
1.001040274,0,ns,drm-engine-video,6,100.00,,
1.001040274,0,ns,drm-engine-video-enhance,6,100.00,,
1.001040274,0,bytes,drm-purgeable-stolen-system0,6,100.00,,
1.001040274,13516800,bytes,drm-purgeable-system0,6,100.00,,
1.001040274,0,bytes,drm-resident-stolen-system0,6,100.00,,
1.001040274,27746304,bytes,drm-resident-system0,6,100.00,,
1.001040274,0,bytes,drm-shared-stolen-system0,6,100.00,,
1.001040274,0,bytes,drm-shared-system0,6,100.00,,
1.001040274,0,bytes,drm-total-stolen-system0,6,100.00,,
1.001040274,27746304,bytes,drm-total-system0,6,100.00,,
2.016629075,0,bytes,drm-active-stolen-system0,6,100.00,,
```
As with the hwmon PMU, high numbered PMU types are used to encode
multiple possible "DRM" PMUs. The appropriate fdinfo is found by
scanning /proc and filtering which fdinfos to read with stat. To avoid
some unneeding scanning, events not starting with "drm-" are
ignored. The patch builds on commit 57e13264dcea ("perf pmus:
Restructure pmu_read_sysfs to scan fewer PMUs") and later so that only
if full wild carding is being done, the PMU starts with "drm_" or the
event starts with "drm-" will /proc be scanned. That is there should
be little to no cost in this PMU unless DRM events are requested.
Signed-off-by: Ian Rogers <irogers@google.com>
Link: https://lore.kernel.org/r/20250624231837.179536-3-irogers@google.com
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
2025-06-24 16:18:36 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (starts_with(line, "drm-shared-")) {
|
|
|
|
add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
|
2025-06-30 13:51:28 +01:00
|
|
|
"Size of shared memory buffers");
|
perf drm_pmu: Add a tool like PMU to expose DRM information
DRM clients expose information through usage stats as documented in
Documentation/gpu/drm-usage-stats.rst (available online at
https://docs.kernel.org/gpu/drm-usage-stats.html). Add a tool like
PMU, similar to the hwmon PMU, that exposes DRM information. For
example on a tigerlake laptop:
```
$ perf list drm
List of pre-defined events (to be used in -e or -M):
drm:
drm-active-stolen-system0
[Total memory active in one or more engines. Unit: drm_i915]
drm-active-system0
[Total memory active in one or more engines. Unit: drm_i915]
drm-engine-capacity-video
[Engine capacity. Unit: drm_i915]
drm-engine-copy
[Utilization in ns. Unit: drm_i915]
drm-engine-render
[Utilization in ns. Unit: drm_i915]
drm-engine-video
[Utilization in ns. Unit: drm_i915]
drm-engine-video-enhance
[Utilization in ns. Unit: drm_i915]
drm-purgeable-stolen-system0
[Size of resident and purgeable memory bufers. Unit: drm_i915]
drm-purgeable-system0
[Size of resident and purgeable memory bufers. Unit: drm_i915]
drm-resident-stolen-system0
[Size of resident memory bufers. Unit: drm_i915]
drm-resident-system0
[Size of resident memory bufers. Unit: drm_i915]
drm-shared-stolen-system0
[Size of shared memory bufers. Unit: drm_i915]
drm-shared-system0
[Size of shared memory bufers. Unit: drm_i915]
drm-total-stolen-system0
[Size of shared and private memory. Unit: drm_i915]
drm-total-system0
[Size of shared and private memory. Unit: drm_i915]
```
System wide data can be gathered:
```
$ perf stat -x, -I 1000 -e drm-active-stolen-system0,drm-active-system0,drm-engine-capacity-video,drm-engine-copy,drm-engine-render,drm-engine-video,drm-engine-video-enhance,drm-purgeable-stolen-system0,drm-purgeable-system0,drm-resident-stolen-system0,drm-resident-system0,drm-shared-stolen-system0,drm-shared-system0,drm-total-stolen-system0,drm-total-system0
1.000904910,0,bytes,drm-active-stolen-system0,1,100.00,,
1.000904910,0,bytes,drm-active-system0,1,100.00,,
1.000904910,36,capacity,drm-engine-capacity-video,1,100.00,,
1.000904910,0,ns,drm-engine-copy,1,100.00,,
1.000904910,1472970566175,ns,drm-engine-render,1,100.00,,
1.000904910,0,ns,drm-engine-video,1,100.00,,
1.000904910,0,ns,drm-engine-video-enhance,1,100.00,,
1.000904910,0,bytes,drm-purgeable-stolen-system0,1,100.00,,
1.000904910,38199296,bytes,drm-purgeable-system0,1,100.00,,
1.000904910,0,bytes,drm-resident-stolen-system0,1,100.00,,
1.000904910,4643196928,bytes,drm-resident-system0,1,100.00,,
1.000904910,0,bytes,drm-shared-stolen-system0,1,100.00,,
1.000904910,1886871552,bytes,drm-shared-system0,1,100.00,,
1.000904910,0,bytes,drm-total-stolen-system0,1,100.00,,
1.000904910,4643196928,bytes,drm-total-system0,1,100.00,,
2.264426839,0,bytes,drm-active-stolen-system0,1,100.00,,
```
Or for a particular process:
```
$ perf stat -x, -I 1000 -e drm-active-stolen-system0,drm-active-system0,drm-engine-capacity-video,drm-engine-copy,drm-engine-render,drm-engine-video,drm-engine-video-enhance,drm-purgeable-stolen-system0,drm-purgeable-system0,drm-resident-stolen-system0,drm-resident-system0,drm-shared-stolen-system0,drm-shared-system0,drm-total-stolen-system0,drm-total-system0 -p 200027
1.001040274,0,bytes,drm-active-stolen-system0,6,100.00,,
1.001040274,0,bytes,drm-active-system0,6,100.00,,
1.001040274,12,capacity,drm-engine-capacity-video,6,100.00,,
1.001040274,0,ns,drm-engine-copy,6,100.00,,
1.001040274,1542300,ns,drm-engine-render,6,100.00,,
1.001040274,0,ns,drm-engine-video,6,100.00,,
1.001040274,0,ns,drm-engine-video-enhance,6,100.00,,
1.001040274,0,bytes,drm-purgeable-stolen-system0,6,100.00,,
1.001040274,13516800,bytes,drm-purgeable-system0,6,100.00,,
1.001040274,0,bytes,drm-resident-stolen-system0,6,100.00,,
1.001040274,27746304,bytes,drm-resident-system0,6,100.00,,
1.001040274,0,bytes,drm-shared-stolen-system0,6,100.00,,
1.001040274,0,bytes,drm-shared-system0,6,100.00,,
1.001040274,0,bytes,drm-total-stolen-system0,6,100.00,,
1.001040274,27746304,bytes,drm-total-system0,6,100.00,,
2.016629075,0,bytes,drm-active-stolen-system0,6,100.00,,
```
As with the hwmon PMU, high numbered PMU types are used to encode
multiple possible "DRM" PMUs. The appropriate fdinfo is found by
scanning /proc and filtering which fdinfos to read with stat. To avoid
some unneeding scanning, events not starting with "drm-" are
ignored. The patch builds on commit 57e13264dcea ("perf pmus:
Restructure pmu_read_sysfs to scan fewer PMUs") and later so that only
if full wild carding is being done, the PMU starts with "drm_" or the
event starts with "drm-" will /proc be scanned. That is there should
be little to no cost in this PMU unless DRM events are requested.
Signed-off-by: Ian Rogers <irogers@google.com>
Link: https://lore.kernel.org/r/20250624231837.179536-3-irogers@google.com
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
2025-06-24 16:18:36 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (starts_with(line, "drm-total-cycles-")) {
|
|
|
|
add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
|
|
|
|
"Total busy cycles");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (starts_with(line, "drm-total-")) {
|
|
|
|
add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
|
|
|
|
"Size of shared and private memory");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (verbose > 1 && starts_with(line, "drm-") &&
|
|
|
|
!starts_with(line, "drm-client-id:") &&
|
|
|
|
!starts_with(line, "drm-pdev:"))
|
|
|
|
pr_debug("Unhandled DRM PMU fdinfo line match '%s'\n", line);
|
|
|
|
}
|
|
|
|
if (drm) {
|
|
|
|
drm->events = events;
|
|
|
|
drm->num_events = num_events;
|
|
|
|
list_add_tail(&drm->pmu.list, pmus);
|
|
|
|
}
|
|
|
|
free(line);
|
|
|
|
if (io.fd != -1)
|
|
|
|
close(io.fd);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void drm_pmu__exit(struct perf_pmu *pmu)
|
|
|
|
{
|
|
|
|
struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
|
|
|
|
|
|
|
|
free(drm->events);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool drm_pmu__have_event(const struct perf_pmu *pmu, const char *name)
|
|
|
|
{
|
|
|
|
struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
|
|
|
|
|
|
|
|
if (!starts_with(name, "drm-"))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
for (int i = 0; i < drm->num_events; i++) {
|
|
|
|
if (!strcasecmp(drm->events[i].name, name))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int drm_pmu__for_each_event(const struct perf_pmu *pmu, void *state, pmu_event_callback cb)
|
|
|
|
{
|
|
|
|
struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
|
|
|
|
|
|
|
|
for (int i = 0; i < drm->num_events; i++) {
|
|
|
|
char encoding_buf[128];
|
|
|
|
struct pmu_event_info info = {
|
|
|
|
.pmu = pmu,
|
|
|
|
.name = drm->events[i].name,
|
|
|
|
.alias = NULL,
|
|
|
|
.scale_unit = drm_pmu_scale_unit_strs[drm->events[i].unit],
|
|
|
|
.desc = drm->events[i].desc,
|
|
|
|
.long_desc = NULL,
|
|
|
|
.encoding_desc = encoding_buf,
|
|
|
|
.topic = "drm",
|
|
|
|
.pmu_name = pmu->name,
|
|
|
|
.event_type_desc = "DRM event",
|
|
|
|
};
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
snprintf(encoding_buf, sizeof(encoding_buf), "%s/config=0x%x/", pmu->name, i);
|
|
|
|
|
|
|
|
ret = cb(state, &info);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t drm_pmu__num_events(const struct perf_pmu *pmu)
|
|
|
|
{
|
|
|
|
const struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
|
|
|
|
|
|
|
|
return drm->num_events;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int drm_pmu__index_for_event(const struct drm_pmu *drm, const char *name)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < drm->num_events; i++) {
|
|
|
|
if (!strcmp(drm->events[i].name, name))
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int drm_pmu__config_term(const struct drm_pmu *drm,
|
|
|
|
struct perf_event_attr *attr,
|
|
|
|
struct parse_events_term *term,
|
|
|
|
struct parse_events_error *err)
|
|
|
|
{
|
|
|
|
if (term->type_term == PARSE_EVENTS__TERM_TYPE_USER) {
|
|
|
|
int i = drm_pmu__index_for_event(drm, term->config);
|
|
|
|
|
|
|
|
if (i >= 0) {
|
|
|
|
attr->config = i;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (err) {
|
|
|
|
char *err_str;
|
|
|
|
|
|
|
|
parse_events_error__handle(err, term->err_val,
|
|
|
|
asprintf(&err_str,
|
|
|
|
"unexpected drm event term (%s) %s",
|
|
|
|
parse_events__term_type_str(term->type_term),
|
|
|
|
term->config) < 0
|
|
|
|
? strdup("unexpected drm event term")
|
|
|
|
: err_str,
|
|
|
|
NULL);
|
|
|
|
}
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int drm_pmu__config_terms(const struct perf_pmu *pmu,
|
|
|
|
struct perf_event_attr *attr,
|
|
|
|
struct parse_events_terms *terms,
|
|
|
|
struct parse_events_error *err)
|
|
|
|
{
|
|
|
|
struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
|
|
|
|
struct parse_events_term *term;
|
|
|
|
|
|
|
|
list_for_each_entry(term, &terms->terms, list) {
|
|
|
|
if (drm_pmu__config_term(drm, attr, term, err))
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int drm_pmu__check_alias(const struct perf_pmu *pmu, struct parse_events_terms *terms,
|
|
|
|
struct perf_pmu_info *info, struct parse_events_error *err)
|
|
|
|
{
|
|
|
|
struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
|
|
|
|
struct parse_events_term *term =
|
|
|
|
list_first_entry(&terms->terms, struct parse_events_term, list);
|
|
|
|
|
|
|
|
if (term->type_term == PARSE_EVENTS__TERM_TYPE_USER) {
|
|
|
|
int i = drm_pmu__index_for_event(drm, term->config);
|
|
|
|
|
|
|
|
if (i >= 0) {
|
|
|
|
info->unit = drm_pmu_unit_strs[drm->events[i].unit];
|
|
|
|
info->scale = 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (err) {
|
|
|
|
char *err_str;
|
|
|
|
|
|
|
|
parse_events_error__handle(err, term->err_val,
|
|
|
|
asprintf(&err_str,
|
|
|
|
"unexpected drm event term (%s) %s",
|
|
|
|
parse_events__term_type_str(term->type_term),
|
|
|
|
term->config) < 0
|
|
|
|
? strdup("unexpected drm event term")
|
|
|
|
: err_str,
|
|
|
|
NULL);
|
|
|
|
}
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct minor_info {
|
|
|
|
unsigned int *minors;
|
|
|
|
int minors_num, minors_len;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int for_each_drm_fdinfo_in_dir(int (*cb)(void *args, int fdinfo_dir_fd, const char *fd_name),
|
|
|
|
void *args, int proc_dir, const char *pid_name,
|
|
|
|
struct minor_info *minors)
|
|
|
|
{
|
|
|
|
char buf[256];
|
|
|
|
DIR *fd_dir;
|
|
|
|
struct dirent *fd_entry;
|
|
|
|
int fd_dir_fd, fdinfo_dir_fd = -1;
|
|
|
|
|
|
|
|
|
|
|
|
scnprintf(buf, sizeof(buf), "%s/fd", pid_name);
|
|
|
|
fd_dir_fd = openat(proc_dir, buf, O_DIRECTORY);
|
|
|
|
if (fd_dir_fd == -1)
|
|
|
|
return 0; /* Presumably lost race to open. */
|
|
|
|
fd_dir = fdopendir(fd_dir_fd);
|
|
|
|
if (!fd_dir) {
|
|
|
|
close(fd_dir_fd);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
while ((fd_entry = readdir(fd_dir)) != NULL) {
|
|
|
|
struct stat stat;
|
|
|
|
unsigned int minor;
|
|
|
|
bool is_dup = false;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (fd_entry->d_type != DT_LNK)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (fstatat(fd_dir_fd, fd_entry->d_name, &stat, 0) != 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if ((stat.st_mode & S_IFMT) != S_IFCHR || major(stat.st_rdev) != 226)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
minor = minor(stat.st_rdev);
|
|
|
|
for (int i = 0; i < minors->minors_num; i++) {
|
|
|
|
if (minor(stat.st_rdev) == minors->minors[i]) {
|
|
|
|
is_dup = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (is_dup)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (minors->minors_num == minors->minors_len) {
|
|
|
|
unsigned int *tmp = reallocarray(minors->minors, minors->minors_len + 4,
|
|
|
|
sizeof(unsigned int));
|
|
|
|
|
|
|
|
if (tmp) {
|
|
|
|
minors->minors = tmp;
|
|
|
|
minors->minors_len += 4;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
minors->minors[minors->minors_num++] = minor;
|
|
|
|
if (fdinfo_dir_fd == -1) {
|
|
|
|
/* Open fdinfo dir if we have a DRM fd. */
|
|
|
|
scnprintf(buf, sizeof(buf), "%s/fdinfo", pid_name);
|
|
|
|
fdinfo_dir_fd = openat(proc_dir, buf, O_DIRECTORY);
|
|
|
|
if (fdinfo_dir_fd == -1)
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
ret = cb(args, fdinfo_dir_fd, fd_entry->d_name);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
if (fdinfo_dir_fd != -1)
|
|
|
|
close(fdinfo_dir_fd);
|
|
|
|
closedir(fd_dir);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int for_each_drm_fdinfo(bool skip_all_duplicates,
|
|
|
|
int (*cb)(void *args, int fdinfo_dir_fd, const char *fd_name),
|
|
|
|
void *args)
|
|
|
|
{
|
|
|
|
DIR *proc_dir;
|
|
|
|
struct dirent *proc_entry;
|
|
|
|
int ret;
|
|
|
|
/*
|
|
|
|
* minors maintains an array of DRM minor device numbers seen for a pid,
|
|
|
|
* or for all pids if skip_all_duplicates is true, so that duplicates
|
|
|
|
* are ignored.
|
|
|
|
*/
|
|
|
|
struct minor_info minors = {
|
|
|
|
.minors = NULL,
|
|
|
|
.minors_num = 0,
|
|
|
|
.minors_len = 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
proc_dir = opendir(procfs__mountpoint());
|
|
|
|
if (!proc_dir)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Walk through the /proc directory. */
|
|
|
|
while ((proc_entry = readdir(proc_dir)) != NULL) {
|
|
|
|
if (proc_entry->d_type != DT_DIR ||
|
|
|
|
!isdigit(proc_entry->d_name[0]))
|
|
|
|
continue;
|
|
|
|
if (!skip_all_duplicates) {
|
|
|
|
/* Reset the seen minor numbers for each pid. */
|
|
|
|
minors.minors_num = 0;
|
|
|
|
}
|
|
|
|
ret = for_each_drm_fdinfo_in_dir(cb, args,
|
|
|
|
dirfd(proc_dir), proc_entry->d_name,
|
|
|
|
&minors);
|
|
|
|
if (ret)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
free(minors.minors);
|
|
|
|
closedir(proc_dir);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int perf_pmus__read_drm_pmus(struct list_head *pmus)
|
|
|
|
{
|
|
|
|
return for_each_drm_fdinfo(/*skip_all_duplicates=*/true, read_drm_pmus_cb, pmus);
|
|
|
|
}
|
|
|
|
|
|
|
|
int evsel__drm_pmu_open(struct evsel *evsel,
|
|
|
|
struct perf_thread_map *threads,
|
|
|
|
int start_cpu_map_idx, int end_cpu_map_idx)
|
|
|
|
{
|
|
|
|
(void)evsel;
|
|
|
|
(void)threads;
|
|
|
|
(void)start_cpu_map_idx;
|
|
|
|
(void)end_cpu_map_idx;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint64_t read_count_and_apply_unit(const char *count_and_unit, enum drm_pmu_unit unit)
|
|
|
|
{
|
|
|
|
char *unit_ptr = NULL;
|
|
|
|
uint64_t count = strtoul(count_and_unit, &unit_ptr, 10);
|
|
|
|
|
|
|
|
if (!unit_ptr)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
while (isblank(*unit_ptr))
|
|
|
|
unit_ptr++;
|
|
|
|
|
|
|
|
switch (unit) {
|
|
|
|
case DRM_PMU_UNIT_BYTES:
|
|
|
|
if (*unit_ptr == '\0')
|
|
|
|
assert(count == 0); /* Generally undocumented, happens for 0. */
|
|
|
|
else if (!strcmp(unit_ptr, "KiB"))
|
|
|
|
count *= 1024;
|
|
|
|
else if (!strcmp(unit_ptr, "MiB"))
|
|
|
|
count *= 1024 * 1024;
|
|
|
|
else
|
|
|
|
pr_err("Unexpected bytes unit '%s'\n", unit_ptr);
|
|
|
|
break;
|
|
|
|
case DRM_PMU_UNIT_CAPACITY:
|
|
|
|
/* No units expected. */
|
|
|
|
break;
|
|
|
|
case DRM_PMU_UNIT_CYCLES:
|
|
|
|
/* No units expected. */
|
|
|
|
break;
|
|
|
|
case DRM_PMU_UNIT_HZ:
|
|
|
|
if (!strcmp(unit_ptr, "Hz"))
|
|
|
|
count *= 1;
|
|
|
|
else if (!strcmp(unit_ptr, "KHz"))
|
|
|
|
count *= 1000;
|
|
|
|
else if (!strcmp(unit_ptr, "MHz"))
|
|
|
|
count *= 1000000;
|
|
|
|
else
|
|
|
|
pr_err("Unexpected hz unit '%s'\n", unit_ptr);
|
|
|
|
break;
|
|
|
|
case DRM_PMU_UNIT_NS:
|
|
|
|
/* Only unit ns expected. */
|
|
|
|
break;
|
|
|
|
case DRM_PMU_UNIT_MAX:
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint64_t read_drm_event(int fdinfo_dir_fd, const char *fd_name,
|
|
|
|
const char *match, enum drm_pmu_unit unit)
|
|
|
|
{
|
|
|
|
char buf[640];
|
|
|
|
struct io io;
|
|
|
|
char *line = NULL;
|
|
|
|
size_t line_len;
|
|
|
|
uint64_t count = 0;
|
|
|
|
|
|
|
|
io__init(&io, openat(fdinfo_dir_fd, fd_name, O_RDONLY), buf, sizeof(buf));
|
|
|
|
if (io.fd == -1) {
|
|
|
|
/* Failed to open file, ignore. */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
while (io__getline(&io, &line, &line_len) > 0) {
|
|
|
|
size_t i = strlen(match);
|
|
|
|
|
|
|
|
if (strncmp(line, match, i))
|
|
|
|
continue;
|
|
|
|
if (line[i] != ':')
|
|
|
|
continue;
|
|
|
|
while (isblank(line[++i]))
|
|
|
|
;
|
|
|
|
if (line[line_len - 1] == '\n')
|
|
|
|
line[line_len - 1] = '\0';
|
|
|
|
count = read_count_and_apply_unit(&line[i], unit);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
free(line);
|
|
|
|
close(io.fd);
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct read_drm_event_cb_args {
|
|
|
|
const char *match;
|
|
|
|
uint64_t count;
|
|
|
|
enum drm_pmu_unit unit;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int read_drm_event_cb(void *vargs, int fdinfo_dir_fd, const char *fd_name)
|
|
|
|
{
|
|
|
|
struct read_drm_event_cb_args *args = vargs;
|
|
|
|
|
|
|
|
args->count += read_drm_event(fdinfo_dir_fd, fd_name, args->match, args->unit);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint64_t drm_pmu__read_system_wide(struct drm_pmu *drm, struct evsel *evsel)
|
|
|
|
{
|
|
|
|
struct read_drm_event_cb_args args = {
|
|
|
|
.count = 0,
|
|
|
|
.match = drm->events[evsel->core.attr.config].name,
|
|
|
|
.unit = drm->events[evsel->core.attr.config].unit,
|
|
|
|
};
|
|
|
|
|
|
|
|
for_each_drm_fdinfo(/*skip_all_duplicates=*/false, read_drm_event_cb, &args);
|
|
|
|
return args.count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint64_t drm_pmu__read_for_pid(struct drm_pmu *drm, struct evsel *evsel, int pid)
|
|
|
|
{
|
|
|
|
struct read_drm_event_cb_args args = {
|
|
|
|
.count = 0,
|
|
|
|
.match = drm->events[evsel->core.attr.config].name,
|
|
|
|
.unit = drm->events[evsel->core.attr.config].unit,
|
|
|
|
};
|
|
|
|
struct minor_info minors = {
|
|
|
|
.minors = NULL,
|
|
|
|
.minors_num = 0,
|
|
|
|
.minors_len = 0,
|
|
|
|
};
|
|
|
|
int proc_dir = open(procfs__mountpoint(), O_DIRECTORY);
|
|
|
|
char pid_name[12];
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (proc_dir < 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
snprintf(pid_name, sizeof(pid_name), "%d", pid);
|
|
|
|
ret = for_each_drm_fdinfo_in_dir(read_drm_event_cb, &args, proc_dir, pid_name, &minors);
|
|
|
|
free(minors.minors);
|
|
|
|
close(proc_dir);
|
|
|
|
return ret == 0 ? args.count : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int evsel__drm_pmu_read(struct evsel *evsel, int cpu_map_idx, int thread)
|
|
|
|
{
|
|
|
|
struct drm_pmu *drm = container_of(evsel->pmu, struct drm_pmu, pmu);
|
|
|
|
struct perf_counts_values *count, *old_count = NULL;
|
|
|
|
int pid = perf_thread_map__pid(evsel->core.threads, thread);
|
|
|
|
uint64_t counter;
|
|
|
|
|
|
|
|
if (pid != -1)
|
|
|
|
counter = drm_pmu__read_for_pid(drm, evsel, pid);
|
|
|
|
else
|
|
|
|
counter = drm_pmu__read_system_wide(drm, evsel);
|
|
|
|
|
|
|
|
if (evsel->prev_raw_counts)
|
|
|
|
old_count = perf_counts(evsel->prev_raw_counts, cpu_map_idx, thread);
|
|
|
|
|
|
|
|
count = perf_counts(evsel->counts, cpu_map_idx, thread);
|
|
|
|
if (old_count) {
|
|
|
|
count->val = old_count->val + counter;
|
|
|
|
count->run = old_count->run + 1;
|
|
|
|
count->ena = old_count->ena + 1;
|
|
|
|
} else {
|
|
|
|
count->val = counter;
|
|
|
|
count->run++;
|
|
|
|
count->ena++;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|