projects/linux·blob master · refs

646 lines · 13.2 KiB

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