xref: /kernel/linux/linux-5.10/tools/perf/util/cgroup.c (revision 8c2ecf20)
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
17int nr_cgroups;
18
19static int open_cgroup(const char *name)
20{
21	char path[PATH_MAX + 1];
22	char mnt[PATH_MAX + 1];
23	int fd;
24
25
26	if (cgroupfs_find_mountpoint(mnt, PATH_MAX + 1, "perf_event"))
27		return -1;
28
29	scnprintf(path, PATH_MAX, "%s/%s", mnt, name);
30
31	fd = open(path, O_RDONLY);
32	if (fd == -1)
33		fprintf(stderr, "no access to cgroup %s\n", path);
34
35	return fd;
36}
37
38static struct cgroup *evlist__find_cgroup(struct evlist *evlist, const char *str)
39{
40	struct evsel *counter;
41	/*
42	 * check if cgrp is already defined, if so we reuse it
43	 */
44	evlist__for_each_entry(evlist, counter) {
45		if (!counter->cgrp)
46			continue;
47		if (!strcmp(counter->cgrp->name, str))
48			return cgroup__get(counter->cgrp);
49	}
50
51	return NULL;
52}
53
54static struct cgroup *cgroup__new(const char *name, bool do_open)
55{
56	struct cgroup *cgroup = zalloc(sizeof(*cgroup));
57
58	if (cgroup != NULL) {
59		refcount_set(&cgroup->refcnt, 1);
60
61		cgroup->name = strdup(name);
62		if (!cgroup->name)
63			goto out_err;
64
65		if (do_open) {
66			cgroup->fd = open_cgroup(name);
67			if (cgroup->fd == -1)
68				goto out_free_name;
69		} else {
70			cgroup->fd = -1;
71		}
72	}
73
74	return cgroup;
75
76out_free_name:
77	zfree(&cgroup->name);
78out_err:
79	free(cgroup);
80	return NULL;
81}
82
83struct cgroup *evlist__findnew_cgroup(struct evlist *evlist, const char *name)
84{
85	struct cgroup *cgroup = evlist__find_cgroup(evlist, name);
86
87	return cgroup ?: cgroup__new(name, true);
88}
89
90static int add_cgroup(struct evlist *evlist, const char *str)
91{
92	struct evsel *counter;
93	struct cgroup *cgrp = evlist__findnew_cgroup(evlist, str);
94	int n;
95
96	if (!cgrp)
97		return -1;
98	/*
99	 * find corresponding event
100	 * if add cgroup N, then need to find event N
101	 */
102	n = 0;
103	evlist__for_each_entry(evlist, counter) {
104		if (n == nr_cgroups)
105			goto found;
106		n++;
107	}
108
109	cgroup__put(cgrp);
110	return -1;
111found:
112	counter->cgrp = cgrp;
113	return 0;
114}
115
116static void cgroup__delete(struct cgroup *cgroup)
117{
118	if (cgroup->fd >= 0)
119		close(cgroup->fd);
120	zfree(&cgroup->name);
121	free(cgroup);
122}
123
124void cgroup__put(struct cgroup *cgrp)
125{
126	if (cgrp && refcount_dec_and_test(&cgrp->refcnt)) {
127		cgroup__delete(cgrp);
128	}
129}
130
131struct cgroup *cgroup__get(struct cgroup *cgroup)
132{
133       if (cgroup)
134		refcount_inc(&cgroup->refcnt);
135       return cgroup;
136}
137
138static void evsel__set_default_cgroup(struct evsel *evsel, struct cgroup *cgroup)
139{
140	if (evsel->cgrp == NULL)
141		evsel->cgrp = cgroup__get(cgroup);
142}
143
144void evlist__set_default_cgroup(struct evlist *evlist, struct cgroup *cgroup)
145{
146	struct evsel *evsel;
147
148	evlist__for_each_entry(evlist, evsel)
149		evsel__set_default_cgroup(evsel, cgroup);
150}
151
152int parse_cgroups(const struct option *opt, const char *str,
153		  int unset __maybe_unused)
154{
155	struct evlist *evlist = *(struct evlist **)opt->value;
156	struct evsel *counter;
157	struct cgroup *cgrp = NULL;
158	const char *p, *e, *eos = str + strlen(str);
159	char *s;
160	int ret, i;
161
162	if (list_empty(&evlist->core.entries)) {
163		fprintf(stderr, "must define events before cgroups\n");
164		return -1;
165	}
166
167	for (;;) {
168		p = strchr(str, ',');
169		e = p ? p : eos;
170
171		/* allow empty cgroups, i.e., skip */
172		if (e - str) {
173			/* termination added */
174			s = strndup(str, e - str);
175			if (!s)
176				return -1;
177			ret = add_cgroup(evlist, s);
178			free(s);
179			if (ret)
180				return -1;
181		}
182		/* nr_cgroups is increased een for empty cgroups */
183		nr_cgroups++;
184		if (!p)
185			break;
186		str = p+1;
187	}
188	/* for the case one cgroup combine to multiple events */
189	i = 0;
190	if (nr_cgroups == 1) {
191		evlist__for_each_entry(evlist, counter) {
192			if (i == 0)
193				cgrp = counter->cgrp;
194			else {
195				counter->cgrp = cgrp;
196				refcount_inc(&cgrp->refcnt);
197			}
198			i++;
199		}
200	}
201	return 0;
202}
203
204int evlist__expand_cgroup(struct evlist *evlist, const char *str,
205			  struct rblist *metric_events, bool open_cgroup)
206{
207	struct evlist *orig_list, *tmp_list;
208	struct evsel *pos, *evsel, *leader;
209	struct rblist orig_metric_events;
210	struct cgroup *cgrp = NULL;
211	const char *p, *e, *eos = str + strlen(str);
212	int ret = -1;
213
214	if (evlist->core.nr_entries == 0) {
215		fprintf(stderr, "must define events before cgroups\n");
216		return -EINVAL;
217	}
218
219	orig_list = evlist__new();
220	tmp_list = evlist__new();
221	if (orig_list == NULL || tmp_list == NULL) {
222		fprintf(stderr, "memory allocation failed\n");
223		return -ENOMEM;
224	}
225
226	/* save original events and init evlist */
227	perf_evlist__splice_list_tail(orig_list, &evlist->core.entries);
228	evlist->core.nr_entries = 0;
229
230	if (metric_events) {
231		orig_metric_events = *metric_events;
232		rblist__init(metric_events);
233	} else {
234		rblist__init(&orig_metric_events);
235	}
236
237	for (;;) {
238		p = strchr(str, ',');
239		e = p ? p : eos;
240
241		/* allow empty cgroups, i.e., skip */
242		if (e - str) {
243			/* termination added */
244			char *name = strndup(str, e - str);
245			if (!name)
246				goto out_err;
247
248			cgrp = cgroup__new(name, open_cgroup);
249			free(name);
250			if (cgrp == NULL)
251				goto out_err;
252		} else {
253			cgrp = NULL;
254		}
255
256		leader = NULL;
257		evlist__for_each_entry(orig_list, pos) {
258			evsel = evsel__clone(pos);
259			if (evsel == NULL)
260				goto out_err;
261
262			cgroup__put(evsel->cgrp);
263			evsel->cgrp = cgroup__get(cgrp);
264
265			if (evsel__is_group_leader(pos))
266				leader = evsel;
267			evsel->leader = leader;
268
269			evlist__add(tmp_list, evsel);
270		}
271		/* cgroup__new() has a refcount, release it here */
272		cgroup__put(cgrp);
273		nr_cgroups++;
274
275		if (metric_events) {
276			perf_stat__collect_metric_expr(tmp_list);
277			if (metricgroup__copy_metric_events(tmp_list, cgrp,
278							    metric_events,
279							    &orig_metric_events) < 0)
280				break;
281		}
282
283		perf_evlist__splice_list_tail(evlist, &tmp_list->core.entries);
284		tmp_list->core.nr_entries = 0;
285
286		if (!p) {
287			ret = 0;
288			break;
289		}
290		str = p+1;
291	}
292
293out_err:
294	evlist__delete(orig_list);
295	evlist__delete(tmp_list);
296	rblist__exit(&orig_metric_events);
297
298	return ret;
299}
300
301static struct cgroup *__cgroup__findnew(struct rb_root *root, uint64_t id,
302					bool create, const char *path)
303{
304	struct rb_node **p = &root->rb_node;
305	struct rb_node *parent = NULL;
306	struct cgroup *cgrp;
307
308	while (*p != NULL) {
309		parent = *p;
310		cgrp = rb_entry(parent, struct cgroup, node);
311
312		if (cgrp->id == id)
313			return cgrp;
314
315		if (cgrp->id < id)
316			p = &(*p)->rb_left;
317		else
318			p = &(*p)->rb_right;
319	}
320
321	if (!create)
322		return NULL;
323
324	cgrp = malloc(sizeof(*cgrp));
325	if (cgrp == NULL)
326		return NULL;
327
328	cgrp->name = strdup(path);
329	if (cgrp->name == NULL) {
330		free(cgrp);
331		return NULL;
332	}
333
334	cgrp->fd = -1;
335	cgrp->id = id;
336	refcount_set(&cgrp->refcnt, 1);
337
338	rb_link_node(&cgrp->node, parent, p);
339	rb_insert_color(&cgrp->node, root);
340
341	return cgrp;
342}
343
344struct cgroup *cgroup__findnew(struct perf_env *env, uint64_t id,
345			       const char *path)
346{
347	struct cgroup *cgrp;
348
349	down_write(&env->cgroups.lock);
350	cgrp = __cgroup__findnew(&env->cgroups.tree, id, true, path);
351	up_write(&env->cgroups.lock);
352	return cgrp;
353}
354
355struct cgroup *cgroup__find(struct perf_env *env, uint64_t id)
356{
357	struct cgroup *cgrp;
358
359	down_read(&env->cgroups.lock);
360	cgrp = __cgroup__findnew(&env->cgroups.tree, id, false, NULL);
361	up_read(&env->cgroups.lock);
362	return cgrp;
363}
364
365void perf_env__purge_cgroups(struct perf_env *env)
366{
367	struct rb_node *node;
368	struct cgroup *cgrp;
369
370	down_write(&env->cgroups.lock);
371	while (!RB_EMPTY_ROOT(&env->cgroups.tree)) {
372		node = rb_first(&env->cgroups.tree);
373		cgrp = rb_entry(node, struct cgroup, node);
374
375		rb_erase(node, &env->cgroups.tree);
376		cgroup__put(cgrp);
377	}
378	up_write(&env->cgroups.lock);
379}
380