637 lines · c
1// SPDX-License-Identifier: GPL-2.02#include <subcmd/parse-options.h>3#include "evsel.h"4#include "cgroup.h"5#include "evlist.h"6#include "rblist.h"7#include "metricgroup.h"8#include "stat.h"9#include <linux/zalloc.h>10#include <sys/types.h>11#include <sys/stat.h>12#include <sys/statfs.h>13#include <fcntl.h>14#include <stdlib.h>15#include <string.h>16#include <api/fs/fs.h>17#include <ftw.h>18#include <regex.h>19 20int nr_cgroups;21bool cgrp_event_expanded;22 23/* used to match cgroup name with patterns */24struct cgroup_name {25 struct list_head list;26 bool used;27 char name[];28};29static LIST_HEAD(cgroup_list);30 31static int open_cgroup(const char *name)32{33 char path[PATH_MAX + 1];34 char mnt[PATH_MAX + 1];35 int fd;36 37 38 if (cgroupfs_find_mountpoint(mnt, PATH_MAX + 1, "perf_event"))39 return -1;40 41 scnprintf(path, PATH_MAX, "%s/%s", mnt, name);42 43 fd = open(path, O_RDONLY);44 if (fd == -1)45 fprintf(stderr, "no access to cgroup %s\n", path);46 47 return fd;48}49 50#ifdef HAVE_FILE_HANDLE51static u64 __read_cgroup_id(const char *path)52{53 struct {54 struct file_handle fh;55 uint64_t cgroup_id;56 } handle;57 int mount_id;58 59 handle.fh.handle_bytes = sizeof(handle.cgroup_id);60 if (name_to_handle_at(AT_FDCWD, path, &handle.fh, &mount_id, 0) < 0)61 return -1ULL;62 63 return handle.cgroup_id;64}65 66int read_cgroup_id(struct cgroup *cgrp)67{68 char path[PATH_MAX + 1];69 char mnt[PATH_MAX + 1];70 71 if (cgroupfs_find_mountpoint(mnt, PATH_MAX + 1, "perf_event"))72 return -1;73 74 scnprintf(path, PATH_MAX, "%s/%s", mnt, cgrp->name);75 76 cgrp->id = __read_cgroup_id(path);77 return 0;78}79#else80static inline u64 __read_cgroup_id(const char *path __maybe_unused) { return -1ULL; }81#endif /* HAVE_FILE_HANDLE */82 83#ifndef CGROUP2_SUPER_MAGIC84#define CGROUP2_SUPER_MAGIC 0x6367727085#endif86 87int cgroup_is_v2(const char *subsys)88{89 char mnt[PATH_MAX + 1];90 struct statfs stbuf;91 92 if (cgroupfs_find_mountpoint(mnt, PATH_MAX + 1, subsys))93 return -1;94 95 if (statfs(mnt, &stbuf) < 0)96 return -1;97 98 return (stbuf.f_type == CGROUP2_SUPER_MAGIC);99}100 101static struct cgroup *evlist__find_cgroup(struct evlist *evlist, const char *str)102{103 struct evsel *counter;104 /*105 * check if cgrp is already defined, if so we reuse it106 */107 evlist__for_each_entry(evlist, counter) {108 if (!counter->cgrp)109 continue;110 if (!strcmp(counter->cgrp->name, str))111 return cgroup__get(counter->cgrp);112 }113 114 return NULL;115}116 117struct cgroup *cgroup__new(const char *name, bool do_open)118{119 struct cgroup *cgroup = zalloc(sizeof(*cgroup));120 121 if (cgroup != NULL) {122 refcount_set(&cgroup->refcnt, 1);123 124 cgroup->name = strdup(name);125 if (!cgroup->name)126 goto out_err;127 128 if (do_open) {129 cgroup->fd = open_cgroup(name);130 if (cgroup->fd == -1)131 goto out_free_name;132 } else {133 cgroup->fd = -1;134 }135 }136 137 return cgroup;138 139out_free_name:140 zfree(&cgroup->name);141out_err:142 free(cgroup);143 return NULL;144}145 146struct cgroup *evlist__findnew_cgroup(struct evlist *evlist, const char *name)147{148 struct cgroup *cgroup = evlist__find_cgroup(evlist, name);149 150 return cgroup ?: cgroup__new(name, true);151}152 153static int add_cgroup(struct evlist *evlist, const char *str)154{155 struct evsel *counter;156 struct cgroup *cgrp = evlist__findnew_cgroup(evlist, str);157 int n;158 159 if (!cgrp)160 return -1;161 /*162 * find corresponding event163 * if add cgroup N, then need to find event N164 */165 n = 0;166 evlist__for_each_entry(evlist, counter) {167 if (n == nr_cgroups)168 goto found;169 n++;170 }171 172 cgroup__put(cgrp);173 return -1;174found:175 counter->cgrp = cgrp;176 return 0;177}178 179static void cgroup__delete(struct cgroup *cgroup)180{181 if (cgroup->fd >= 0)182 close(cgroup->fd);183 zfree(&cgroup->name);184 free(cgroup);185}186 187void cgroup__put(struct cgroup *cgrp)188{189 if (cgrp && refcount_dec_and_test(&cgrp->refcnt)) {190 cgroup__delete(cgrp);191 }192}193 194struct cgroup *cgroup__get(struct cgroup *cgroup)195{196 if (cgroup)197 refcount_inc(&cgroup->refcnt);198 return cgroup;199}200 201static void evsel__set_default_cgroup(struct evsel *evsel, struct cgroup *cgroup)202{203 if (evsel->cgrp == NULL)204 evsel->cgrp = cgroup__get(cgroup);205}206 207void evlist__set_default_cgroup(struct evlist *evlist, struct cgroup *cgroup)208{209 struct evsel *evsel;210 211 evlist__for_each_entry(evlist, evsel)212 evsel__set_default_cgroup(evsel, cgroup);213}214 215/* helper function for ftw() in match_cgroups and list_cgroups */216static int add_cgroup_name(const char *fpath, const struct stat *sb __maybe_unused,217 int typeflag, struct FTW *ftwbuf __maybe_unused)218{219 struct cgroup_name *cn;220 221 if (typeflag != FTW_D)222 return 0;223 224 cn = malloc(sizeof(*cn) + strlen(fpath) + 1);225 if (cn == NULL)226 return -1;227 228 cn->used = false;229 strcpy(cn->name, fpath);230 231 list_add_tail(&cn->list, &cgroup_list);232 return 0;233}234 235static int check_and_add_cgroup_name(const char *fpath)236{237 struct cgroup_name *cn;238 239 list_for_each_entry(cn, &cgroup_list, list) {240 if (!strcmp(cn->name, fpath))241 return 0;242 }243 244 /* pretend if it's added by ftw() */245 return add_cgroup_name(fpath, NULL, FTW_D, NULL);246}247 248static void release_cgroup_list(void)249{250 struct cgroup_name *cn;251 252 while (!list_empty(&cgroup_list)) {253 cn = list_first_entry(&cgroup_list, struct cgroup_name, list);254 list_del(&cn->list);255 free(cn);256 }257}258 259/* collect given cgroups only */260static int list_cgroups(const char *str)261{262 const char *p, *e, *eos = str + strlen(str);263 struct cgroup_name *cn;264 char *s;265 266 /* use given name as is when no regex is given */267 for (;;) {268 p = strchr(str, ',');269 e = p ? p : eos;270 271 if (e - str) {272 int ret;273 274 s = strndup(str, e - str);275 if (!s)276 return -1;277 278 ret = check_and_add_cgroup_name(s);279 free(s);280 if (ret < 0)281 return -1;282 } else {283 if (check_and_add_cgroup_name("/") < 0)284 return -1;285 }286 287 if (!p)288 break;289 str = p+1;290 }291 292 /* these groups will be used */293 list_for_each_entry(cn, &cgroup_list, list)294 cn->used = true;295 296 return 0;297}298 299/* collect all cgroups first and then match with the pattern */300static int match_cgroups(const char *str)301{302 char mnt[PATH_MAX];303 const char *p, *e, *eos = str + strlen(str);304 struct cgroup_name *cn;305 regex_t reg;306 int prefix_len;307 char *s;308 309 if (cgroupfs_find_mountpoint(mnt, sizeof(mnt), "perf_event"))310 return -1;311 312 /* cgroup_name will have a full path, skip the root directory */313 prefix_len = strlen(mnt);314 315 /* collect all cgroups in the cgroup_list */316 if (nftw(mnt, add_cgroup_name, 20, 0) < 0)317 return -1;318 319 for (;;) {320 p = strchr(str, ',');321 e = p ? p : eos;322 323 /* allow empty cgroups, i.e., skip */324 if (e - str) {325 /* termination added */326 s = strndup(str, e - str);327 if (!s)328 return -1;329 if (regcomp(®, s, REG_NOSUB)) {330 free(s);331 return -1;332 }333 334 /* check cgroup name with the pattern */335 list_for_each_entry(cn, &cgroup_list, list) {336 char *name = cn->name + prefix_len;337 338 if (name[0] == '/' && name[1])339 name++;340 if (!regexec(®, name, 0, NULL, 0))341 cn->used = true;342 }343 regfree(®);344 free(s);345 } else {346 /* first entry to root cgroup */347 cn = list_first_entry(&cgroup_list, struct cgroup_name,348 list);349 cn->used = true;350 }351 352 if (!p)353 break;354 str = p+1;355 }356 return prefix_len;357}358 359int parse_cgroups(const struct option *opt, const char *str,360 int unset __maybe_unused)361{362 struct evlist *evlist = *(struct evlist **)opt->value;363 struct evsel *counter;364 struct cgroup *cgrp = NULL;365 const char *p, *e, *eos = str + strlen(str);366 char *s;367 int ret, i;368 369 if (list_empty(&evlist->core.entries)) {370 fprintf(stderr, "must define events before cgroups\n");371 return -1;372 }373 374 for (;;) {375 p = strchr(str, ',');376 e = p ? p : eos;377 378 /* allow empty cgroups, i.e., skip */379 if (e - str) {380 /* termination added */381 s = strndup(str, e - str);382 if (!s)383 return -1;384 ret = add_cgroup(evlist, s);385 free(s);386 if (ret)387 return -1;388 }389 /* nr_cgroups is increased een for empty cgroups */390 nr_cgroups++;391 if (!p)392 break;393 str = p+1;394 }395 /* for the case one cgroup combine to multiple events */396 i = 0;397 if (nr_cgroups == 1) {398 evlist__for_each_entry(evlist, counter) {399 if (i == 0)400 cgrp = counter->cgrp;401 else {402 counter->cgrp = cgrp;403 refcount_inc(&cgrp->refcnt);404 }405 i++;406 }407 }408 return 0;409}410 411static bool has_pattern_string(const char *str)412{413 return !!strpbrk(str, "{}[]()|*+?^$");414}415 416int evlist__expand_cgroup(struct evlist *evlist, const char *str,417 struct rblist *metric_events, bool open_cgroup)418{419 struct evlist *orig_list, *tmp_list;420 struct evsel *pos, *evsel, *leader;421 struct rblist orig_metric_events;422 struct cgroup *cgrp = NULL;423 struct cgroup_name *cn;424 int ret = -1;425 int prefix_len;426 427 if (evlist->core.nr_entries == 0) {428 fprintf(stderr, "must define events before cgroups\n");429 return -EINVAL;430 }431 432 orig_list = evlist__new();433 tmp_list = evlist__new();434 if (orig_list == NULL || tmp_list == NULL) {435 fprintf(stderr, "memory allocation failed\n");436 return -ENOMEM;437 }438 439 /* save original events and init evlist */440 evlist__splice_list_tail(orig_list, &evlist->core.entries);441 evlist->core.nr_entries = 0;442 443 if (metric_events) {444 orig_metric_events = *metric_events;445 rblist__init(metric_events);446 } else {447 rblist__init(&orig_metric_events);448 }449 450 if (has_pattern_string(str))451 prefix_len = match_cgroups(str);452 else453 prefix_len = list_cgroups(str);454 455 if (prefix_len < 0)456 goto out_err;457 458 list_for_each_entry(cn, &cgroup_list, list) {459 char *name;460 461 if (!cn->used)462 continue;463 464 /* cgroup_name might have a full path, skip the prefix */465 name = cn->name + prefix_len;466 if (name[0] == '/' && name[1])467 name++;468 469 /* the cgroup can go away in the meantime */470 cgrp = cgroup__new(name, open_cgroup);471 if (cgrp == NULL)472 continue;473 474 leader = NULL;475 evlist__for_each_entry(orig_list, pos) {476 evsel = evsel__clone(pos);477 if (evsel == NULL)478 goto out_err;479 480 cgroup__put(evsel->cgrp);481 evsel->cgrp = cgroup__get(cgrp);482 483 if (evsel__is_group_leader(pos))484 leader = evsel;485 evsel__set_leader(evsel, leader);486 487 evlist__add(tmp_list, evsel);488 }489 /* cgroup__new() has a refcount, release it here */490 cgroup__put(cgrp);491 nr_cgroups++;492 493 if (metric_events) {494 if (metricgroup__copy_metric_events(tmp_list, cgrp,495 metric_events,496 &orig_metric_events) < 0)497 goto out_err;498 }499 500 evlist__splice_list_tail(evlist, &tmp_list->core.entries);501 tmp_list->core.nr_entries = 0;502 }503 504 if (list_empty(&evlist->core.entries)) {505 fprintf(stderr, "no cgroup matched: %s\n", str);506 goto out_err;507 }508 509 ret = 0;510 cgrp_event_expanded = true;511 512out_err:513 evlist__delete(orig_list);514 evlist__delete(tmp_list);515 rblist__exit(&orig_metric_events);516 release_cgroup_list();517 518 return ret;519}520 521static struct cgroup *__cgroup__findnew(struct rb_root *root, uint64_t id,522 bool create, const char *path)523{524 struct rb_node **p = &root->rb_node;525 struct rb_node *parent = NULL;526 struct cgroup *cgrp;527 528 while (*p != NULL) {529 parent = *p;530 cgrp = rb_entry(parent, struct cgroup, node);531 532 if (cgrp->id == id)533 return cgrp;534 535 if (cgrp->id < id)536 p = &(*p)->rb_left;537 else538 p = &(*p)->rb_right;539 }540 541 if (!create)542 return NULL;543 544 cgrp = malloc(sizeof(*cgrp));545 if (cgrp == NULL)546 return NULL;547 548 cgrp->name = strdup(path);549 if (cgrp->name == NULL) {550 free(cgrp);551 return NULL;552 }553 554 cgrp->fd = -1;555 cgrp->id = id;556 refcount_set(&cgrp->refcnt, 1);557 558 rb_link_node(&cgrp->node, parent, p);559 rb_insert_color(&cgrp->node, root);560 561 return cgrp;562}563 564struct cgroup *cgroup__findnew(struct perf_env *env, uint64_t id,565 const char *path)566{567 struct cgroup *cgrp;568 569 down_write(&env->cgroups.lock);570 cgrp = __cgroup__findnew(&env->cgroups.tree, id, true, path);571 up_write(&env->cgroups.lock);572 return cgrp;573}574 575struct cgroup *__cgroup__find(struct rb_root *root, uint64_t id)576{577 return __cgroup__findnew(root, id, /*create=*/false, /*path=*/NULL);578}579 580struct cgroup *cgroup__find(struct perf_env *env, uint64_t id)581{582 struct cgroup *cgrp;583 584 down_read(&env->cgroups.lock);585 cgrp = __cgroup__findnew(&env->cgroups.tree, id, false, NULL);586 up_read(&env->cgroups.lock);587 return cgrp;588}589 590void perf_env__purge_cgroups(struct perf_env *env)591{592 struct rb_node *node;593 struct cgroup *cgrp;594 595 down_write(&env->cgroups.lock);596 while (!RB_EMPTY_ROOT(&env->cgroups.tree)) {597 node = rb_first(&env->cgroups.tree);598 cgrp = rb_entry(node, struct cgroup, node);599 600 rb_erase(node, &env->cgroups.tree);601 cgroup__put(cgrp);602 }603 up_write(&env->cgroups.lock);604}605 606void read_all_cgroups(struct rb_root *root)607{608 char mnt[PATH_MAX];609 struct cgroup_name *cn;610 int prefix_len;611 612 if (cgroupfs_find_mountpoint(mnt, sizeof(mnt), "perf_event"))613 return;614 615 /* cgroup_name will have a full path, skip the root directory */616 prefix_len = strlen(mnt);617 618 /* collect all cgroups in the cgroup_list */619 if (nftw(mnt, add_cgroup_name, 20, 0) < 0)620 return;621 622 list_for_each_entry(cn, &cgroup_list, list) {623 const char *name;624 u64 cgrp_id;625 626 /* cgroup_name might have a full path, skip the prefix */627 name = cn->name + prefix_len;628 if (name[0] == '\0')629 name = "/";630 631 cgrp_id = __read_cgroup_id(cn->name);632 __cgroup__findnew(root, cgrp_id, /*create=*/true, name);633 }634 635 release_cgroup_list();636}637