Commit 28917cb1 authored by Ian Rogers's avatar Ian Rogers Committed by Namhyung Kim
Browse files

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 57e13264 ("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: default avatarIan Rogers <irogers@google.com>
Link: https://lore.kernel.org/r/20250624231837.179536-3-irogers@google.com


Signed-off-by: default avatarNamhyung Kim <namhyung@kernel.org>
parent e1ec69ed
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -84,6 +84,7 @@ perf-util-y += pmu.o
perf-util-y += pmus.o
perf-util-y += pmu-flex.o
perf-util-y += pmu-bison.o
perf-util-y += drm_pmu.o
perf-util-y += hwmon_pmu.o
perf-util-y += tool_pmu.o
perf-util-y += svghelper.o
+686 −0
Original line number Diff line number Diff line
// 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,
				  "Size of resident and purgeable memory bufers");
			continue;
		}
		if (starts_with(line, "drm-resident-")) {
			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
				  "Size of resident memory bufers");
			continue;
		}
		if (starts_with(line, "drm-shared-")) {
			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
				  "Size of shared memory bufers");
			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;
}
+39 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __DRM_PMU_H
#define __DRM_PMU_H
/*
 * Linux 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). This is a tool like PMU
 * that exposes DRM information.
 */

#include "pmu.h"
#include <stdbool.h>

struct list_head;
struct perf_thread_map;

void drm_pmu__exit(struct perf_pmu *pmu);
bool drm_pmu__have_event(const struct perf_pmu *pmu, const char *name);
int drm_pmu__for_each_event(const struct perf_pmu *pmu, void *state, pmu_event_callback cb);
size_t drm_pmu__num_events(const struct perf_pmu *pmu);
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);
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);


bool perf_pmu__is_drm(const struct perf_pmu *pmu);
bool evsel__is_drm(const struct evsel *evsel);

int perf_pmus__read_drm_pmus(struct list_head *pmus);

int evsel__drm_pmu_open(struct evsel *evsel,
			struct perf_thread_map *threads,
			int start_cpu_map_idx, int end_cpu_map_idx);
int evsel__drm_pmu_read(struct evsel *evsel, int cpu_map_idx, int thread);

#endif /* __DRM_PMU_H */
+9 −0
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@
#include "off_cpu.h"
#include "pmu.h"
#include "pmus.h"
#include "drm_pmu.h"
#include "hwmon_pmu.h"
#include "tool_pmu.h"
#include "rlimit.h"
@@ -1889,6 +1890,9 @@ int evsel__read_counter(struct evsel *evsel, int cpu_map_idx, int thread)
	if (evsel__is_hwmon(evsel))
		return evsel__hwmon_pmu_read(evsel, cpu_map_idx, thread);

	if (evsel__is_drm(evsel))
		return evsel__drm_pmu_read(evsel, cpu_map_idx, thread);

	if (evsel__is_retire_lat(evsel))
		return evsel__tpebs_read(evsel, cpu_map_idx, thread);

@@ -2610,6 +2614,11 @@ static int evsel__open_cpu(struct evsel *evsel, struct perf_cpu_map *cpus,
					     start_cpu_map_idx,
					     end_cpu_map_idx);
	}
	if (evsel__is_drm(evsel)) {
		return evsel__drm_pmu_open(evsel, threads,
					   start_cpu_map_idx,
					   end_cpu_map_idx);
	}

	for (idx = start_cpu_map_idx; idx < end_cpu_map_idx; idx++) {
		cpu = perf_cpu_map__cpu(cpus, idx);
+15 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@
#include "debug.h"
#include "evsel.h"
#include "pmu.h"
#include "drm_pmu.h"
#include "hwmon_pmu.h"
#include "pmus.h"
#include "tool_pmu.h"
@@ -1627,6 +1628,8 @@ int perf_pmu__config_terms(const struct perf_pmu *pmu,

	if (perf_pmu__is_hwmon(pmu))
		return hwmon_pmu__config_terms(pmu, attr, terms, err);
	if (perf_pmu__is_drm(pmu))
		return drm_pmu__config_terms(pmu, attr, terms, err);

	list_for_each_entry(term, &terms->terms, list) {
		if (pmu_config_term(pmu, attr, term, terms, zero, apply_hardcoded, err))
@@ -1767,6 +1770,10 @@ int perf_pmu__check_alias(struct perf_pmu *pmu, struct parse_events_terms *head_
		ret = hwmon_pmu__check_alias(head_terms, info, err);
		goto out;
	}
	if (perf_pmu__is_drm(pmu)) {
		ret = drm_pmu__check_alias(pmu, head_terms, info, err);
		goto out;
	}

	/* Fake PMU doesn't rewrite terms. */
	if (perf_pmu__is_fake(pmu))
@@ -1949,6 +1956,8 @@ bool perf_pmu__have_event(struct perf_pmu *pmu, const char *name)
		return false;
	if (perf_pmu__is_hwmon(pmu))
		return hwmon_pmu__have_event(pmu, name);
	if (perf_pmu__is_drm(pmu))
		return drm_pmu__have_event(pmu, name);
	if (perf_pmu__find_alias(pmu, name, /*load=*/ true) != NULL)
		return true;
	if (pmu->cpu_aliases_added || !pmu->events_table)
@@ -1962,6 +1971,8 @@ size_t perf_pmu__num_events(struct perf_pmu *pmu)

	if (perf_pmu__is_hwmon(pmu))
		return hwmon_pmu__num_events(pmu);
	if (perf_pmu__is_drm(pmu))
		return drm_pmu__num_events(pmu);

	pmu_aliases_parse(pmu);
	nr = pmu->sysfs_aliases + pmu->sys_json_aliases;
@@ -2030,6 +2041,8 @@ int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus,

	if (perf_pmu__is_hwmon(pmu))
		return hwmon_pmu__for_each_event(pmu, state, cb);
	if (perf_pmu__is_drm(pmu))
		return drm_pmu__for_each_event(pmu, state, cb);

	strbuf_init(&sb, /*hint=*/ 0);
	pmu_aliases_parse(pmu);
@@ -2511,6 +2524,8 @@ void perf_pmu__delete(struct perf_pmu *pmu)

	if (perf_pmu__is_hwmon(pmu))
		hwmon_pmu__exit(pmu);
	else if (perf_pmu__is_drm(pmu))
		drm_pmu__exit(pmu);

	perf_pmu__del_formats(&pmu->format);
	perf_pmu__del_aliases(pmu);
Loading