// SPDX-License-Identifier: GPL-2.0 #include "../perf.h" #include #include "evsel.h" #include "cgroup.h" #include "evlist.h" #include #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(const char *name) { char path[PATH_MAX + 1]; char mnt[PATH_MAX + 1]; int fd; if (cgroupfs_find_mountpoint(mnt, PATH_MAX + 1)) return -1; scnprintf(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 struct cgroup *evlist__find_cgroup(struct perf_evlist *evlist, const char *str) { struct perf_evsel *counter; /* * 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)) return cgroup__get(counter->cgrp); } return NULL; } static struct cgroup *cgroup__new(const char *name) { struct cgroup *cgroup = zalloc(sizeof(*cgroup)); if (cgroup != NULL) { refcount_set(&cgroup->refcnt, 1); cgroup->name = strdup(name); if (!cgroup->name) goto out_err; cgroup->fd = open_cgroup(name); if (cgroup->fd == -1) goto out_free_name; } return cgroup; out_free_name: free(cgroup->name); out_err: free(cgroup); return NULL; } struct cgroup *evlist__findnew_cgroup(struct perf_evlist *evlist, const char *name) { struct cgroup *cgroup = evlist__find_cgroup(evlist, name); return cgroup ?: cgroup__new(name); } static int add_cgroup(struct perf_evlist *evlist, const char *str) { struct perf_evsel *counter; struct cgroup *cgrp = evlist__findnew_cgroup(evlist, str); int n; if (!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; } static void evsel__set_default_cgroup(struct perf_evsel *evsel, struct cgroup *cgroup) { if (evsel->cgrp == NULL) evsel->cgrp = cgroup__get(cgroup); } void evlist__set_default_cgroup(struct perf_evlist *evlist, struct cgroup *cgroup) { struct perf_evsel *evsel; evlist__for_each_entry(evlist, evsel) evsel__set_default_cgroup(evsel, 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); free(s); if (ret) 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; }