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