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