File Index
Files and directories
Sign in
FILE

drm_pmu.c

File preview with syntax highlighting.

linux-6.19.14/tools/perf/util/drm_pmu.c C
  1// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
  2#include "drm_pmu.h"
  3#include "counts.h"
  4#include "cpumap.h"
  5#include "debug.h"
  6#include "evsel.h"
  7#include "pmu.h"
  8#include <perf/threadmap.h>
  9#include <api/fs/fs.h>
 10#include <api/io.h>
 11#include <ctype.h>
 12#include <dirent.h>
 13#include <errno.h>
 14#include <fcntl.h>
 15#include <unistd.h>
 16#include <linux/unistd.h>
 17#include <linux/kcmp.h>
 18#include <linux/zalloc.h>
 19#include <sys/stat.h>
 20#include <sys/syscall.h>
 21#include <sys/sysmacros.h>
 22#include <sys/types.h>
 23
 24enum drm_pmu_unit {
 25	DRM_PMU_UNIT_BYTES,
 26	DRM_PMU_UNIT_CAPACITY,
 27	DRM_PMU_UNIT_CYCLES,
 28	DRM_PMU_UNIT_HZ,
 29	DRM_PMU_UNIT_NS,
 30
 31	DRM_PMU_UNIT_MAX,
 32};
 33
 34struct drm_pmu_event {
 35	const char *name;
 36	const char *desc;
 37	enum drm_pmu_unit unit;
 38};
 39
 40struct drm_pmu {
 41	struct perf_pmu pmu;
 42	struct drm_pmu_event *events;
 43	int num_events;
 44};
 45
 46static const char * const drm_pmu_unit_strs[DRM_PMU_UNIT_MAX] = {
 47	"bytes",
 48	"capacity",
 49	"cycles",
 50	"hz",
 51	"ns",
 52};
 53
 54static const char * const drm_pmu_scale_unit_strs[DRM_PMU_UNIT_MAX] = {
 55	"1bytes",
 56	"1capacity",
 57	"1cycles",
 58	"1hz",
 59	"1ns",
 60};
 61
 62bool perf_pmu__is_drm(const struct perf_pmu *pmu)
 63{
 64	return pmu && pmu->type >= PERF_PMU_TYPE_DRM_START &&
 65		pmu->type <= PERF_PMU_TYPE_DRM_END;
 66}
 67
 68bool evsel__is_drm(const struct evsel *evsel)
 69{
 70	return perf_pmu__is_drm(evsel->pmu);
 71}
 72
 73static struct drm_pmu *add_drm_pmu(struct list_head *pmus, char *line, size_t line_len)
 74{
 75	struct drm_pmu *drm;
 76	struct perf_pmu *pmu;
 77	const char *name;
 78	__u32 max_drm_pmu_type = 0, type;
 79	int i = 12;
 80
 81	if (line[line_len - 1] == '\n')
 82		line[line_len - 1] = '\0';
 83	while (isspace(line[i]))
 84		i++;
 85
 86	line[--i] = '_';
 87	line[--i] = 'm';
 88	line[--i] = 'r';
 89	line[--i] = 'd';
 90	name = &line[i];
 91
 92	list_for_each_entry(pmu, pmus, list) {
 93		if (!perf_pmu__is_drm(pmu))
 94			continue;
 95		if (pmu->type > max_drm_pmu_type)
 96			max_drm_pmu_type = pmu->type;
 97		if (!strcmp(pmu->name, name)) {
 98			/* PMU already exists. */
 99			return NULL;
100		}
101	}
102
103	if (max_drm_pmu_type != 0)
104		type = max_drm_pmu_type + 1;
105	else
106		type = PERF_PMU_TYPE_DRM_START;
107
108	if (type > PERF_PMU_TYPE_DRM_END) {
109		zfree(&drm);
110		pr_err("Unable to encode DRM PMU type for %s\n", name);
111		return NULL;
112	}
113
114	drm = zalloc(sizeof(*drm));
115	if (!drm)
116		return NULL;
117
118	if (perf_pmu__init(&drm->pmu, type, name) != 0) {
119		perf_pmu__delete(&drm->pmu);
120		return NULL;
121	}
122
123	drm->pmu.cpus = perf_cpu_map__new_int(0);
124	if (!drm->pmu.cpus) {
125		perf_pmu__delete(&drm->pmu);
126		return NULL;
127	}
128	return drm;
129}
130
131
132static bool starts_with(const char *str, const char *prefix)
133{
134	return !strncmp(prefix, str, strlen(prefix));
135}
136
137static int add_event(struct drm_pmu_event **events, int *num_events,
138		     const char *line, enum drm_pmu_unit unit, const char *desc)
139{
140	const char *colon = strchr(line, ':');
141	struct drm_pmu_event *tmp;
142
143	if (!colon)
144		return -EINVAL;
145
146	tmp = reallocarray(*events, *num_events + 1, sizeof(struct drm_pmu_event));
147	if (!tmp)
148		return -ENOMEM;
149	tmp[*num_events].unit = unit;
150	tmp[*num_events].desc = desc;
151	tmp[*num_events].name = strndup(line, colon - line);
152	if (!tmp[*num_events].name)
153		return -ENOMEM;
154	(*num_events)++;
155	*events = tmp;
156	return 0;
157}
158
159static int read_drm_pmus_cb(void *args, int fdinfo_dir_fd, const char *fd_name)
160{
161	struct list_head *pmus = args;
162	char buf[640];
163	struct io io;
164	char *line = NULL;
165	size_t line_len;
166	struct drm_pmu *drm = NULL;
167	struct drm_pmu_event *events = NULL;
168	int num_events = 0;
169
170	io__init(&io, openat(fdinfo_dir_fd, fd_name, O_RDONLY), buf, sizeof(buf));
171	if (io.fd == -1) {
172		/* Failed to open file, ignore. */
173		return 0;
174	}
175
176	while (io__getline(&io, &line, &line_len) > 0) {
177		if (starts_with(line, "drm-driver:")) {
178			drm = add_drm_pmu(pmus, line, line_len);
179			if (!drm)
180				break;
181			continue;
182		}
183		/*
184		 * Note the string matching below is alphabetical, with more
185		 * specific matches appearing before less specific.
186		 */
187		if (starts_with(line, "drm-active-")) {
188			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
189				  "Total memory active in one or more engines");
190			continue;
191		}
192		if (starts_with(line, "drm-cycles-")) {
193			add_event(&events, &num_events, line, DRM_PMU_UNIT_CYCLES,
194				"Busy cycles");
195			continue;
196		}
197		if (starts_with(line, "drm-engine-capacity-")) {
198			add_event(&events, &num_events, line, DRM_PMU_UNIT_CAPACITY,
199				"Engine capacity");
200			continue;
201		}
202		if (starts_with(line, "drm-engine-")) {
203			add_event(&events, &num_events, line, DRM_PMU_UNIT_NS,
204				  "Utilization in ns");
205			continue;
206		}
207		if (starts_with(line, "drm-maxfreq-")) {
208			add_event(&events, &num_events, line, DRM_PMU_UNIT_HZ,
209				  "Maximum frequency");
210			continue;
211		}
212		if (starts_with(line, "drm-purgeable-")) {
213			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
214				  "Size of resident and purgeable memory buffers");
215			continue;
216		}
217		if (starts_with(line, "drm-resident-")) {
218			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
219				  "Size of resident memory buffers");
220			continue;
221		}
222		if (starts_with(line, "drm-shared-")) {
223			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
224				  "Size of shared memory buffers");
225			continue;
226		}
227		if (starts_with(line, "drm-total-cycles-")) {
228			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
229				  "Total busy cycles");
230			continue;
231		}
232		if (starts_with(line, "drm-total-")) {
233			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
234				  "Size of shared and private memory");
235			continue;
236		}
237		if (verbose > 1 && starts_with(line, "drm-") &&
238		    !starts_with(line, "drm-client-id:") &&
239		    !starts_with(line, "drm-pdev:"))
240			pr_debug("Unhandled DRM PMU fdinfo line match '%s'\n", line);
241	}
242	if (drm) {
243		drm->events = events;
244		drm->num_events = num_events;
245		list_add_tail(&drm->pmu.list, pmus);
246	}
247	free(line);
248	if (io.fd != -1)
249		close(io.fd);
250	return 0;
251}
252
253void drm_pmu__exit(struct perf_pmu *pmu)
254{
255	struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
256
257	free(drm->events);
258}
259
260bool drm_pmu__have_event(const struct perf_pmu *pmu, const char *name)
261{
262	struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
263
264	if (!starts_with(name, "drm-"))
265		return false;
266
267	for (int i = 0; i < drm->num_events; i++) {
268		if (!strcasecmp(drm->events[i].name, name))
269			return true;
270	}
271	return false;
272}
273
274int drm_pmu__for_each_event(const struct perf_pmu *pmu, void *state, pmu_event_callback cb)
275{
276	struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
277
278	for (int i = 0; i < drm->num_events; i++) {
279		char encoding_buf[128];
280		struct pmu_event_info info = {
281			.pmu = pmu,
282			.name = drm->events[i].name,
283			.alias = NULL,
284			.scale_unit = drm_pmu_scale_unit_strs[drm->events[i].unit],
285			.desc = drm->events[i].desc,
286			.long_desc = NULL,
287			.encoding_desc = encoding_buf,
288			.topic = "drm",
289			.pmu_name = pmu->name,
290			.event_type_desc = "DRM event",
291		};
292		int ret;
293
294		snprintf(encoding_buf, sizeof(encoding_buf), "%s/config=0x%x/", pmu->name, i);
295
296		ret = cb(state, &info);
297		if (ret)
298			return ret;
299	}
300	return 0;
301}
302
303size_t drm_pmu__num_events(const struct perf_pmu *pmu)
304{
305	const struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
306
307	return drm->num_events;
308}
309
310static int drm_pmu__index_for_event(const struct drm_pmu *drm, const char *name)
311{
312	for (int i = 0; i < drm->num_events; i++) {
313		if (!strcmp(drm->events[i].name, name))
314			return i;
315	}
316	return -1;
317}
318
319static int drm_pmu__config_term(const struct drm_pmu *drm,
320				  struct perf_event_attr *attr,
321				  struct parse_events_term *term,
322				  struct parse_events_error *err)
323{
324	if (term->type_term == PARSE_EVENTS__TERM_TYPE_USER) {
325		int i = drm_pmu__index_for_event(drm, term->config);
326
327		if (i >= 0) {
328			attr->config = i;
329			return 0;
330		}
331	}
332	if (err) {
333		char *err_str;
334
335		parse_events_error__handle(err, term->err_val,
336					asprintf(&err_str,
337						"unexpected drm event term (%s) %s",
338						parse_events__term_type_str(term->type_term),
339						term->config) < 0
340					? strdup("unexpected drm event term")
341					: err_str,
342					NULL);
343	}
344	return -EINVAL;
345}
346
347int drm_pmu__config_terms(const struct perf_pmu *pmu,
348			    struct perf_event_attr *attr,
349			    struct parse_events_terms *terms,
350			    struct parse_events_error *err)
351{
352	struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
353	struct parse_events_term *term;
354
355	list_for_each_entry(term, &terms->terms, list) {
356		if (drm_pmu__config_term(drm, attr, term, err))
357			return -EINVAL;
358	}
359
360	return 0;
361}
362
363int drm_pmu__check_alias(const struct perf_pmu *pmu, struct parse_events_terms *terms,
364			 struct perf_pmu_info *info, struct parse_events_error *err)
365{
366	struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
367	struct parse_events_term *term =
368		list_first_entry(&terms->terms, struct parse_events_term, list);
369
370	if (term->type_term == PARSE_EVENTS__TERM_TYPE_USER) {
371		int i = drm_pmu__index_for_event(drm, term->config);
372
373		if (i >= 0) {
374			info->unit = drm_pmu_unit_strs[drm->events[i].unit];
375			info->scale = 1;
376			return 0;
377		}
378	}
379	if (err) {
380		char *err_str;
381
382		parse_events_error__handle(err, term->err_val,
383					asprintf(&err_str,
384						"unexpected drm event term (%s) %s",
385						parse_events__term_type_str(term->type_term),
386						term->config) < 0
387					? strdup("unexpected drm event term")
388					: err_str,
389					NULL);
390	}
391	return -EINVAL;
392}
393
394struct minor_info {
395	unsigned int *minors;
396	int minors_num, minors_len;
397};
398
399static int for_each_drm_fdinfo_in_dir(int (*cb)(void *args, int fdinfo_dir_fd, const char *fd_name),
400				      void *args, int proc_dir, const char *pid_name,
401				      struct minor_info *minors)
402{
403	char buf[256];
404	DIR *fd_dir;
405	struct dirent *fd_entry;
406	int fd_dir_fd, fdinfo_dir_fd = -1;
407
408
409	scnprintf(buf, sizeof(buf), "%s/fd", pid_name);
410	fd_dir_fd = openat(proc_dir, buf, O_DIRECTORY);
411	if (fd_dir_fd == -1)
412		return 0; /* Presumably lost race to open. */
413	fd_dir = fdopendir(fd_dir_fd);
414	if (!fd_dir) {
415		close(fd_dir_fd);
416		return -ENOMEM;
417	}
418	while ((fd_entry = readdir(fd_dir)) != NULL) {
419		struct stat stat;
420		unsigned int minor;
421		bool is_dup = false;
422		int ret;
423
424		if (fd_entry->d_type != DT_LNK)
425			continue;
426
427		if (fstatat(fd_dir_fd, fd_entry->d_name, &stat, 0) != 0)
428			continue;
429
430		if ((stat.st_mode & S_IFMT) != S_IFCHR || major(stat.st_rdev) != 226)
431			continue;
432
433		minor = minor(stat.st_rdev);
434		for (int i = 0; i < minors->minors_num; i++) {
435			if (minor(stat.st_rdev) == minors->minors[i]) {
436				is_dup = true;
437				break;
438			}
439		}
440		if (is_dup)
441			continue;
442
443		if (minors->minors_num == minors->minors_len) {
444			unsigned int *tmp = reallocarray(minors->minors, minors->minors_len + 4,
445							 sizeof(unsigned int));
446
447			if (tmp) {
448				minors->minors = tmp;
449				minors->minors_len += 4;
450			}
451		}
452		minors->minors[minors->minors_num++] = minor;
453		if (fdinfo_dir_fd == -1) {
454			/* Open fdinfo dir if we have a DRM fd. */
455			scnprintf(buf, sizeof(buf), "%s/fdinfo", pid_name);
456			fdinfo_dir_fd = openat(proc_dir, buf, O_DIRECTORY);
457			if (fdinfo_dir_fd == -1)
458				continue;
459		}
460		ret = cb(args, fdinfo_dir_fd, fd_entry->d_name);
461		if (ret)
462			goto close_fdinfo;
463	}
464
465close_fdinfo:
466	if (fdinfo_dir_fd != -1)
467		close(fdinfo_dir_fd);
468	closedir(fd_dir);
469	return 0;
470}
471
472static int for_each_drm_fdinfo(bool skip_all_duplicates,
473			       int (*cb)(void *args, int fdinfo_dir_fd, const char *fd_name),
474			       void *args)
475{
476	DIR *proc_dir;
477	struct dirent *proc_entry;
478	int ret;
479	/*
480	 * minors maintains an array of DRM minor device numbers seen for a pid,
481	 * or for all pids if skip_all_duplicates is true, so that duplicates
482	 * are ignored.
483	 */
484	struct minor_info minors = {
485		.minors = NULL,
486		.minors_num = 0,
487		.minors_len = 0,
488	};
489
490	proc_dir = opendir(procfs__mountpoint());
491	if (!proc_dir)
492		return 0;
493
494	/* Walk through the /proc directory. */
495	while ((proc_entry = readdir(proc_dir)) != NULL) {
496		if (proc_entry->d_type != DT_DIR ||
497		    !isdigit(proc_entry->d_name[0]))
498			continue;
499		if (!skip_all_duplicates) {
500			/* Reset the seen minor numbers for each pid. */
501			minors.minors_num = 0;
502		}
503		ret = for_each_drm_fdinfo_in_dir(cb, args,
504						 dirfd(proc_dir), proc_entry->d_name,
505						 &minors);
506		if (ret)
507			break;
508	}
509	free(minors.minors);
510	closedir(proc_dir);
511	return ret;
512}
513
514int perf_pmus__read_drm_pmus(struct list_head *pmus)
515{
516	return for_each_drm_fdinfo(/*skip_all_duplicates=*/true, read_drm_pmus_cb, pmus);
517}
518
519int evsel__drm_pmu_open(struct evsel *evsel,
520			struct perf_thread_map *threads,
521			int start_cpu_map_idx, int end_cpu_map_idx)
522{
523	(void)evsel;
524	(void)threads;
525	(void)start_cpu_map_idx;
526	(void)end_cpu_map_idx;
527	return 0;
528}
529
530static uint64_t read_count_and_apply_unit(const char *count_and_unit, enum drm_pmu_unit unit)
531{
532	char *unit_ptr = NULL;
533	uint64_t count = strtoul(count_and_unit, &unit_ptr, 10);
534
535	if (!unit_ptr)
536		return 0;
537
538	while (isblank(*unit_ptr))
539		unit_ptr++;
540
541	switch (unit) {
542	case DRM_PMU_UNIT_BYTES:
543		if (*unit_ptr == '\0')
544			assert(count == 0); /* Generally undocumented, happens for 0. */
545		else if (!strcmp(unit_ptr, "KiB"))
546			count *= 1024;
547		else if (!strcmp(unit_ptr, "MiB"))
548			count *= 1024 * 1024;
549		else
550			pr_err("Unexpected bytes unit '%s'\n", unit_ptr);
551		break;
552	case DRM_PMU_UNIT_CAPACITY:
553		/* No units expected. */
554		break;
555	case DRM_PMU_UNIT_CYCLES:
556		/* No units expected. */
557		break;
558	case DRM_PMU_UNIT_HZ:
559		if (!strcmp(unit_ptr, "Hz"))
560			count *= 1;
561		else if (!strcmp(unit_ptr, "KHz"))
562			count *= 1000;
563		else if (!strcmp(unit_ptr, "MHz"))
564			count *= 1000000;
565		else
566			pr_err("Unexpected hz unit '%s'\n", unit_ptr);
567		break;
568	case DRM_PMU_UNIT_NS:
569		/* Only unit ns expected. */
570		break;
571	case DRM_PMU_UNIT_MAX:
572	default:
573		break;
574	}
575	return count;
576}
577
578static uint64_t read_drm_event(int fdinfo_dir_fd, const char *fd_name,
579			       const char *match, enum drm_pmu_unit unit)
580{
581	char buf[640];
582	struct io io;
583	char *line = NULL;
584	size_t line_len;
585	uint64_t count = 0;
586
587	io__init(&io, openat(fdinfo_dir_fd, fd_name, O_RDONLY), buf, sizeof(buf));
588	if (io.fd == -1) {
589		/* Failed to open file, ignore. */
590		return 0;
591	}
592	while (io__getline(&io, &line, &line_len) > 0) {
593		size_t i = strlen(match);
594
595		if (strncmp(line, match, i))
596			continue;
597		if (line[i] != ':')
598			continue;
599		while (isblank(line[++i]))
600			;
601		if (line[line_len - 1] == '\n')
602			line[line_len - 1] = '\0';
603		count = read_count_and_apply_unit(&line[i], unit);
604		break;
605	}
606	free(line);
607	close(io.fd);
608	return count;
609}
610
611struct read_drm_event_cb_args {
612	const char *match;
613	uint64_t count;
614	enum drm_pmu_unit unit;
615};
616
617static int read_drm_event_cb(void *vargs, int fdinfo_dir_fd, const char *fd_name)
618{
619	struct read_drm_event_cb_args *args = vargs;
620
621	args->count += read_drm_event(fdinfo_dir_fd, fd_name, args->match, args->unit);
622	return 0;
623}
624
625static uint64_t drm_pmu__read_system_wide(struct drm_pmu *drm, struct evsel *evsel)
626{
627	struct read_drm_event_cb_args args = {
628		.count = 0,
629		.match = drm->events[evsel->core.attr.config].name,
630		.unit = drm->events[evsel->core.attr.config].unit,
631	};
632
633	for_each_drm_fdinfo(/*skip_all_duplicates=*/false, read_drm_event_cb, &args);
634	return args.count;
635}
636
637static uint64_t drm_pmu__read_for_pid(struct drm_pmu *drm, struct evsel *evsel, int pid)
638{
639	struct read_drm_event_cb_args args = {
640		.count = 0,
641		.match = drm->events[evsel->core.attr.config].name,
642		.unit = drm->events[evsel->core.attr.config].unit,
643	};
644	struct minor_info minors = {
645		.minors = NULL,
646		.minors_num = 0,
647		.minors_len = 0,
648	};
649	int proc_dir = open(procfs__mountpoint(), O_DIRECTORY);
650	char pid_name[12];
651	int ret;
652
653	if (proc_dir < 0)
654		return 0;
655
656	snprintf(pid_name, sizeof(pid_name), "%d", pid);
657	ret = for_each_drm_fdinfo_in_dir(read_drm_event_cb, &args, proc_dir, pid_name, &minors);
658	free(minors.minors);
659	close(proc_dir);
660	return ret == 0 ? args.count : 0;
661}
662
663int evsel__drm_pmu_read(struct evsel *evsel, int cpu_map_idx, int thread)
664{
665	struct drm_pmu *drm = container_of(evsel->pmu, struct drm_pmu, pmu);
666	struct perf_counts_values *count, *old_count = NULL;
667	int pid = perf_thread_map__pid(evsel->core.threads, thread);
668	uint64_t counter;
669
670	if (pid != -1)
671		counter = drm_pmu__read_for_pid(drm, evsel, pid);
672	else
673		counter = drm_pmu__read_system_wide(drm, evsel);
674
675	if (evsel->prev_raw_counts)
676		old_count = perf_counts(evsel->prev_raw_counts, cpu_map_idx, thread);
677
678	count = perf_counts(evsel->counts, cpu_map_idx, thread);
679	if (old_count) {
680		count->val = old_count->val + counter;
681		count->run = old_count->run + 1;
682		count->ena = old_count->ena + 1;
683	} else {
684		count->val = counter;
685		count->run++;
686		count->ena++;
687	}
688	return 0;
689}
~/ Read-only file preview.