diff options
Diffstat (limited to 'tools/perf/util/cgroup.c')
-rw-r--r-- | tools/perf/util/cgroup.c | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/tools/perf/util/cgroup.c b/tools/perf/util/cgroup.c new file mode 100644 index 000000000..ccd02634a --- /dev/null +++ b/tools/perf/util/cgroup.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "util.h" +#include "../perf.h" +#include <subcmd/parse-options.h> +#include "evsel.h" +#include "cgroup.h" +#include "evlist.h" +#include <linux/stringify.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +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; +} |