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