// SPDX-License-Identifier: GPL-2.0 #include "util.h" #include "../perf.h" #include #include "evsel.h" #include "cgroup.h" #include "evlist.h" #include #include #include #include int nr_cgroups; static int cgroupfs_find_mountpoint(char *buf, size_t maxlen) { FILE *fp; char mountpoint[PATH_MAX + 1], tokens[PATH_MAX + 1], type[PATH_MAX + 1]; char path_v1[PATH_MAX + 1], path_v2[PATH_MAX + 2], *path; char *token, *saved_ptr = NULL; fp = fopen("/proc/mounts", "r"); if (!fp) return -1; /* * in order to handle split hierarchy, we need to scan /proc/mounts * and inspect every cgroupfs mount point to find one that has * perf_event subsystem */ path_v1[0] = '\0'; path_v2[0] = '\0'; while (fscanf(fp, "%*s %"__stringify(PATH_MAX)"s %"__stringify(PATH_MAX)"s %" __stringify(PATH_MAX)"s %*d %*d\n", mountpoint, type, tokens) == 3) { if (!path_v1[0] && !strcmp(type, "cgroup")) { token = strtok_r(tokens, ",", &saved_ptr); while (token != NULL) { if (!strcmp(token, "perf_event")) { strcpy(path_v1, mountpoint); break; } token = strtok_r(NULL, ",", &saved_ptr); } } if (!path_v2[0] && !strcmp(type, "cgroup2")) strcpy(path_v2, mountpoint); if (path_v1[0] && path_v2[0]) break; } fclose(fp); if (path_v1[0]) path = path_v1; else if (path_v2[0]) path = path_v2; else return -1; if (strlen(path) < maxlen) { strcpy(buf, path); return 0; } return -1; } static int open_cgroup(char *name) { char path[PATH_MAX + 1]; char mnt[PATH_MAX + 1]; int fd; if (cgroupfs_find_mountpoint(mnt, PATH_MAX + 1)) return -1; snprintf(path, PATH_MAX, "%s/%s", mnt, name); fd = open(path, O_RDONLY); if (fd == -1) fprintf(stderr, "no access to cgroup %s\n", path); return fd; } static int add_cgroup(struct perf_evlist *evlist, char *str) { struct perf_evsel *counter; struct cgroup *cgrp = NULL; int n; /* * check if cgrp is already defined, if so we reuse it */ evlist__for_each_entry(evlist, counter) { if (!counter->cgrp) continue; if (!strcmp(counter->cgrp->name, str)) { cgrp = cgroup__get(counter->cgrp); break; } } if (!cgrp) { cgrp = zalloc(sizeof(*cgrp)); if (!cgrp) return -1; cgrp->name = str; refcount_set(&cgrp->refcnt, 1); cgrp->fd = open_cgroup(str); if (cgrp->fd == -1) { free(cgrp); return -1; } } /* * find corresponding event * if add cgroup N, then need to find event N */ n = 0; evlist__for_each_entry(evlist, counter) { if (n == nr_cgroups) goto found; n++; } cgroup__put(cgrp); return -1; found: counter->cgrp = cgrp; return 0; } static void cgroup__delete(struct cgroup *cgroup) { close(cgroup->fd); zfree(&cgroup->name); free(cgroup); } void cgroup__put(struct cgroup *cgrp) { if (cgrp && refcount_dec_and_test(&cgrp->refcnt)) { cgroup__delete(cgrp); } } struct cgroup *cgroup__get(struct cgroup *cgroup) { if (cgroup) refcount_inc(&cgroup->refcnt); return cgroup; } int parse_cgroups(const struct option *opt, const char *str, int unset __maybe_unused) { struct perf_evlist *evlist = *(struct perf_evlist **)opt->value; struct perf_evsel *counter; struct cgroup *cgrp = NULL; const char *p, *e, *eos = str + strlen(str); char *s; int ret, i; if (list_empty(&evlist->entries)) { fprintf(stderr, "must define events before cgroups\n"); return -1; } for (;;) { p = strchr(str, ','); e = p ? p : eos; /* allow empty cgroups, i.e., skip */ if (e - str) { /* termination added */ s = strndup(str, e - str); if (!s) return -1; ret = add_cgroup(evlist, s); if (ret) { free(s); return -1; } } /* nr_cgroups is increased een for empty cgroups */ nr_cgroups++; if (!p) break; str = p+1; } /* for the case one cgroup combine to multiple events */ i = 0; if (nr_cgroups == 1) { evlist__for_each_entry(evlist, counter) { if (i == 0) cgrp = counter->cgrp; else { counter->cgrp = cgrp; refcount_inc(&cgrp->refcnt); } i++; } } return 0; }