Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Ian Rogers and committed by
Namhyung Kim
28917cb1 e1ec69ed

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