xref: /linux-6.15/kernel/trace/trace_stat.c (revision 08b76731)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
2dbd0b4b3SFrederic Weisbecker /*
3dbd0b4b3SFrederic Weisbecker  * Infrastructure for statistic tracing (histogram output).
4dbd0b4b3SFrederic Weisbecker  *
58f184f27SFrederic Weisbecker  * Copyright (C) 2008-2009 Frederic Weisbecker <[email protected]>
6dbd0b4b3SFrederic Weisbecker  *
7dbd0b4b3SFrederic Weisbecker  * Based on the code from trace_branch.c which is
8dbd0b4b3SFrederic Weisbecker  * Copyright (C) 2008 Steven Rostedt <[email protected]>
9dbd0b4b3SFrederic Weisbecker  *
10dbd0b4b3SFrederic Weisbecker  */
11dbd0b4b3SFrederic Weisbecker 
1217911ff3SSteven Rostedt (VMware) #include <linux/security.h>
13dbd0b4b3SFrederic Weisbecker #include <linux/list.h>
145a0e3ad6STejun Heo #include <linux/slab.h>
158f184f27SFrederic Weisbecker #include <linux/rbtree.h>
168434dc93SSteven Rostedt (Red Hat) #include <linux/tracefs.h>
17002bb86dSFrederic Weisbecker #include "trace_stat.h"
18dbd0b4b3SFrederic Weisbecker #include "trace.h"
19dbd0b4b3SFrederic Weisbecker 
20dbd0b4b3SFrederic Weisbecker 
218f184f27SFrederic Weisbecker /*
228f184f27SFrederic Weisbecker  * List of stat red-black nodes from a tracer
238f184f27SFrederic Weisbecker  * We use a such tree to sort quickly the stat
248f184f27SFrederic Weisbecker  * entries from the tracer.
258f184f27SFrederic Weisbecker  */
268f184f27SFrederic Weisbecker struct stat_node {
278f184f27SFrederic Weisbecker 	struct rb_node		node;
28dbd0b4b3SFrederic Weisbecker 	void			*stat;
29dbd0b4b3SFrederic Weisbecker };
30dbd0b4b3SFrederic Weisbecker 
31034939b6SFrederic Weisbecker /* A stat session is the stats output in one file */
320d64f834SFrederic Weisbecker struct stat_session {
33002bb86dSFrederic Weisbecker 	struct list_head	session_list;
34034939b6SFrederic Weisbecker 	struct tracer_stat	*ts;
358f184f27SFrederic Weisbecker 	struct rb_root		stat_root;
36034939b6SFrederic Weisbecker 	struct mutex		stat_mutex;
37002bb86dSFrederic Weisbecker 	struct dentry		*file;
38034939b6SFrederic Weisbecker };
39dbd0b4b3SFrederic Weisbecker 
4073d8b8bcSWenji Huang /* All of the sessions currently in use. Each stat file embed one session */
41002bb86dSFrederic Weisbecker static LIST_HEAD(all_stat_sessions);
42002bb86dSFrederic Weisbecker static DEFINE_MUTEX(all_stat_sessions_mutex);
43002bb86dSFrederic Weisbecker 
44002bb86dSFrederic Weisbecker /* The root directory for all stat files */
45002bb86dSFrederic Weisbecker static struct dentry		*stat_dir;
46dbd0b4b3SFrederic Weisbecker 
__reset_stat_session(struct stat_session * session)47636eaceeSLi Zefan static void __reset_stat_session(struct stat_session *session)
48dbd0b4b3SFrederic Weisbecker {
499cd804acSCody P Schafer 	struct stat_node *snode, *n;
50dbd0b4b3SFrederic Weisbecker 
519cd804acSCody P Schafer 	rbtree_postorder_for_each_entry_safe(snode, n, &session->stat_root, node) {
529cd804acSCody P Schafer 		if (session->ts->stat_release)
539cd804acSCody P Schafer 			session->ts->stat_release(snode->stat);
549cd804acSCody P Schafer 		kfree(snode);
559cd804acSCody P Schafer 	}
56dbd0b4b3SFrederic Weisbecker 
578f184f27SFrederic Weisbecker 	session->stat_root = RB_ROOT;
58dbd0b4b3SFrederic Weisbecker }
59dbd0b4b3SFrederic Weisbecker 
reset_stat_session(struct stat_session * session)60636eaceeSLi Zefan static void reset_stat_session(struct stat_session *session)
61636eaceeSLi Zefan {
62636eaceeSLi Zefan 	mutex_lock(&session->stat_mutex);
63636eaceeSLi Zefan 	__reset_stat_session(session);
64636eaceeSLi Zefan 	mutex_unlock(&session->stat_mutex);
65636eaceeSLi Zefan }
66636eaceeSLi Zefan 
destroy_session(struct stat_session * session)670d64f834SFrederic Weisbecker static void destroy_session(struct stat_session *session)
68dbd0b4b3SFrederic Weisbecker {
698434dc93SSteven Rostedt (Red Hat) 	tracefs_remove(session->file);
70636eaceeSLi Zefan 	__reset_stat_session(session);
71034939b6SFrederic Weisbecker 	mutex_destroy(&session->stat_mutex);
72034939b6SFrederic Weisbecker 	kfree(session);
73034939b6SFrederic Weisbecker }
74002bb86dSFrederic Weisbecker 
insert_stat(struct rb_root * root,void * stat,cmp_func_t cmp)7580042c8fSAndy Shevchenko static int insert_stat(struct rb_root *root, void *stat, cmp_func_t cmp)
768f184f27SFrederic Weisbecker {
778f184f27SFrederic Weisbecker 	struct rb_node **new = &(root->rb_node), *parent = NULL;
78dbd3fbdfSLi Zefan 	struct stat_node *data;
79dbd3fbdfSLi Zefan 
80dbd3fbdfSLi Zefan 	data = kzalloc(sizeof(*data), GFP_KERNEL);
81dbd3fbdfSLi Zefan 	if (!data)
82dbd3fbdfSLi Zefan 		return -ENOMEM;
83dbd3fbdfSLi Zefan 	data->stat = stat;
848f184f27SFrederic Weisbecker 
858f184f27SFrederic Weisbecker 	/*
868f184f27SFrederic Weisbecker 	 * Figure out where to put new node
878f184f27SFrederic Weisbecker 	 * This is a descendent sorting
888f184f27SFrederic Weisbecker 	 */
898f184f27SFrederic Weisbecker 	while (*new) {
908f184f27SFrederic Weisbecker 		struct stat_node *this;
918f184f27SFrederic Weisbecker 		int result;
928f184f27SFrederic Weisbecker 
938f184f27SFrederic Weisbecker 		this = container_of(*new, struct stat_node, node);
948f184f27SFrederic Weisbecker 		result = cmp(data->stat, this->stat);
958f184f27SFrederic Weisbecker 
968f184f27SFrederic Weisbecker 		parent = *new;
978f184f27SFrederic Weisbecker 		if (result >= 0)
988f184f27SFrederic Weisbecker 			new = &((*new)->rb_left);
998f184f27SFrederic Weisbecker 		else
1008f184f27SFrederic Weisbecker 			new = &((*new)->rb_right);
1018f184f27SFrederic Weisbecker 	}
1028f184f27SFrederic Weisbecker 
1038f184f27SFrederic Weisbecker 	rb_link_node(&data->node, parent, new);
1048f184f27SFrederic Weisbecker 	rb_insert_color(&data->node, root);
105dbd3fbdfSLi Zefan 	return 0;
1068f184f27SFrederic Weisbecker }
1078f184f27SFrederic Weisbecker 
108dbd0b4b3SFrederic Weisbecker /*
109dbd0b4b3SFrederic Weisbecker  * For tracers that don't provide a stat_cmp callback.
110dbd3fbdfSLi Zefan  * This one will force an insertion as right-most node
111dbd3fbdfSLi Zefan  * in the rbtree.
112dbd0b4b3SFrederic Weisbecker  */
dummy_cmp(const void * p1,const void * p2)11380042c8fSAndy Shevchenko static int dummy_cmp(const void *p1, const void *p2)
114dbd0b4b3SFrederic Weisbecker {
115b3dd7ba7SLi Zefan 	return -1;
116dbd0b4b3SFrederic Weisbecker }
117dbd0b4b3SFrederic Weisbecker 
118dbd0b4b3SFrederic Weisbecker /*
119dbd3fbdfSLi Zefan  * Initialize the stat rbtree at each trace_stat file opening.
120dbd0b4b3SFrederic Weisbecker  * All of these copies and sorting are required on all opening
121dbd0b4b3SFrederic Weisbecker  * since the stats could have changed between two file sessions.
122dbd0b4b3SFrederic Weisbecker  */
stat_seq_init(struct stat_session * session)1230d64f834SFrederic Weisbecker static int stat_seq_init(struct stat_session *session)
124dbd0b4b3SFrederic Weisbecker {
125034939b6SFrederic Weisbecker 	struct tracer_stat *ts = session->ts;
126dbd3fbdfSLi Zefan 	struct rb_root *root = &session->stat_root;
12709833521SSteven Rostedt 	void *stat;
128dbd0b4b3SFrederic Weisbecker 	int ret = 0;
129dbd0b4b3SFrederic Weisbecker 	int i;
130dbd0b4b3SFrederic Weisbecker 
131*08b76731SSteven Rostedt 	guard(mutex)(&session->stat_mutex);
132636eaceeSLi Zefan 	__reset_stat_session(session);
133dbd0b4b3SFrederic Weisbecker 
134034939b6SFrederic Weisbecker 	if (!ts->stat_cmp)
135034939b6SFrederic Weisbecker 		ts->stat_cmp = dummy_cmp;
136dbd0b4b3SFrederic Weisbecker 
13742548008SSteven Rostedt 	stat = ts->stat_start(ts);
13809833521SSteven Rostedt 	if (!stat)
139*08b76731SSteven Rostedt 		return 0;
14009833521SSteven Rostedt 
141dbd3fbdfSLi Zefan 	ret = insert_stat(root, stat, ts->stat_cmp);
142dbd3fbdfSLi Zefan 	if (ret)
143*08b76731SSteven Rostedt 		return ret;
144dbd0b4b3SFrederic Weisbecker 
145dbd0b4b3SFrederic Weisbecker 	/*
146dbd3fbdfSLi Zefan 	 * Iterate over the tracer stat entries and store them in an rbtree.
147dbd0b4b3SFrederic Weisbecker 	 */
148dbd0b4b3SFrederic Weisbecker 	for (i = 1; ; i++) {
14909833521SSteven Rostedt 		stat = ts->stat_next(stat, i);
15009833521SSteven Rostedt 
15109833521SSteven Rostedt 		/* End of insertion */
15209833521SSteven Rostedt 		if (!stat)
15309833521SSteven Rostedt 			break;
15409833521SSteven Rostedt 
155dbd3fbdfSLi Zefan 		ret = insert_stat(root, stat, ts->stat_cmp);
156dbd3fbdfSLi Zefan 		if (ret)
157dbd3fbdfSLi Zefan 			goto exit_free_rbtree;
158dbd0b4b3SFrederic Weisbecker 	}
159220ba351SLai Jiangshan 
160dbd0b4b3SFrederic Weisbecker 	return ret;
161dbd0b4b3SFrederic Weisbecker 
162dbd3fbdfSLi Zefan exit_free_rbtree:
163636eaceeSLi Zefan 	__reset_stat_session(session);
164dbd0b4b3SFrederic Weisbecker 	return ret;
165dbd0b4b3SFrederic Weisbecker }
166dbd0b4b3SFrederic Weisbecker 
167dbd0b4b3SFrederic Weisbecker 
stat_seq_start(struct seq_file * s,loff_t * pos)168dbd0b4b3SFrederic Weisbecker static void *stat_seq_start(struct seq_file *s, loff_t *pos)
169dbd0b4b3SFrederic Weisbecker {
1700d64f834SFrederic Weisbecker 	struct stat_session *session = s->private;
1718f184f27SFrederic Weisbecker 	struct rb_node *node;
17297d53202SLi Zefan 	int n = *pos;
1738f184f27SFrederic Weisbecker 	int i;
174dbd0b4b3SFrederic Weisbecker 
175dbd3fbdfSLi Zefan 	/* Prevent from tracer switch or rbtree modification */
176034939b6SFrederic Weisbecker 	mutex_lock(&session->stat_mutex);
177dbd0b4b3SFrederic Weisbecker 
178dbd0b4b3SFrederic Weisbecker 	/* If we are in the beginning of the file, print the headers */
17997d53202SLi Zefan 	if (session->ts->stat_headers) {
18097d53202SLi Zefan 		if (n == 0)
181e6f48901SLai Jiangshan 			return SEQ_START_TOKEN;
18297d53202SLi Zefan 		n--;
18397d53202SLi Zefan 	}
184dbd0b4b3SFrederic Weisbecker 
1858f184f27SFrederic Weisbecker 	node = rb_first(&session->stat_root);
18697d53202SLi Zefan 	for (i = 0; node && i < n; i++)
1878f184f27SFrederic Weisbecker 		node = rb_next(node);
1888f184f27SFrederic Weisbecker 
1898f184f27SFrederic Weisbecker 	return node;
190dbd0b4b3SFrederic Weisbecker }
191dbd0b4b3SFrederic Weisbecker 
stat_seq_next(struct seq_file * s,void * p,loff_t * pos)192dbd0b4b3SFrederic Weisbecker static void *stat_seq_next(struct seq_file *s, void *p, loff_t *pos)
193dbd0b4b3SFrederic Weisbecker {
1940d64f834SFrederic Weisbecker 	struct stat_session *session = s->private;
1958f184f27SFrederic Weisbecker 	struct rb_node *node = p;
1968f184f27SFrederic Weisbecker 
1978f184f27SFrederic Weisbecker 	(*pos)++;
198dbd0b4b3SFrederic Weisbecker 
199e6f48901SLai Jiangshan 	if (p == SEQ_START_TOKEN)
2008f184f27SFrederic Weisbecker 		return rb_first(&session->stat_root);
201e6f48901SLai Jiangshan 
2028f184f27SFrederic Weisbecker 	return rb_next(node);
203dbd0b4b3SFrederic Weisbecker }
204dbd0b4b3SFrederic Weisbecker 
stat_seq_stop(struct seq_file * s,void * p)205034939b6SFrederic Weisbecker static void stat_seq_stop(struct seq_file *s, void *p)
206dbd0b4b3SFrederic Weisbecker {
2070d64f834SFrederic Weisbecker 	struct stat_session *session = s->private;
208034939b6SFrederic Weisbecker 	mutex_unlock(&session->stat_mutex);
209dbd0b4b3SFrederic Weisbecker }
210dbd0b4b3SFrederic Weisbecker 
stat_seq_show(struct seq_file * s,void * v)211dbd0b4b3SFrederic Weisbecker static int stat_seq_show(struct seq_file *s, void *v)
212dbd0b4b3SFrederic Weisbecker {
2130d64f834SFrederic Weisbecker 	struct stat_session *session = s->private;
2148f184f27SFrederic Weisbecker 	struct stat_node *l = container_of(v, struct stat_node, node);
215e8a9cbf6SSteven Rostedt 
216e6f48901SLai Jiangshan 	if (v == SEQ_START_TOKEN)
217e6f48901SLai Jiangshan 		return session->ts->stat_headers(s);
218e6f48901SLai Jiangshan 
219034939b6SFrederic Weisbecker 	return session->ts->stat_show(s, l->stat);
220dbd0b4b3SFrederic Weisbecker }
221dbd0b4b3SFrederic Weisbecker 
222dbd0b4b3SFrederic Weisbecker static const struct seq_operations trace_stat_seq_ops = {
223dbd0b4b3SFrederic Weisbecker 	.start		= stat_seq_start,
224dbd0b4b3SFrederic Weisbecker 	.next		= stat_seq_next,
225dbd0b4b3SFrederic Weisbecker 	.stop		= stat_seq_stop,
226dbd0b4b3SFrederic Weisbecker 	.show		= stat_seq_show
227dbd0b4b3SFrederic Weisbecker };
228dbd0b4b3SFrederic Weisbecker 
229034939b6SFrederic Weisbecker /* The session stat is refilled and resorted at each stat file opening */
tracing_stat_open(struct inode * inode,struct file * file)230dbd0b4b3SFrederic Weisbecker static int tracing_stat_open(struct inode *inode, struct file *file)
231dbd0b4b3SFrederic Weisbecker {
232dbd0b4b3SFrederic Weisbecker 	int ret;
233636eaceeSLi Zefan 	struct seq_file *m;
2340d64f834SFrederic Weisbecker 	struct stat_session *session = inode->i_private;
235034939b6SFrederic Weisbecker 
23617911ff3SSteven Rostedt (VMware) 	ret = security_locked_down(LOCKDOWN_TRACEFS);
23717911ff3SSteven Rostedt (VMware) 	if (ret)
23817911ff3SSteven Rostedt (VMware) 		return ret;
23917911ff3SSteven Rostedt (VMware) 
240034939b6SFrederic Weisbecker 	ret = stat_seq_init(session);
241636eaceeSLi Zefan 	if (ret)
242636eaceeSLi Zefan 		return ret;
243636eaceeSLi Zefan 
244636eaceeSLi Zefan 	ret = seq_open(file, &trace_stat_seq_ops);
245636eaceeSLi Zefan 	if (ret) {
246636eaceeSLi Zefan 		reset_stat_session(session);
247636eaceeSLi Zefan 		return ret;
248dbd0b4b3SFrederic Weisbecker 	}
249dbd0b4b3SFrederic Weisbecker 
250636eaceeSLi Zefan 	m = file->private_data;
251636eaceeSLi Zefan 	m->private = session;
252dbd0b4b3SFrederic Weisbecker 	return ret;
253dbd0b4b3SFrederic Weisbecker }
254dbd0b4b3SFrederic Weisbecker 
255dbd0b4b3SFrederic Weisbecker /*
256dbd3fbdfSLi Zefan  * Avoid consuming memory with our now useless rbtree.
257dbd0b4b3SFrederic Weisbecker  */
tracing_stat_release(struct inode * i,struct file * f)258dbd0b4b3SFrederic Weisbecker static int tracing_stat_release(struct inode *i, struct file *f)
259dbd0b4b3SFrederic Weisbecker {
2600d64f834SFrederic Weisbecker 	struct stat_session *session = i->i_private;
261034939b6SFrederic Weisbecker 
262034939b6SFrederic Weisbecker 	reset_stat_session(session);
263034939b6SFrederic Weisbecker 
264636eaceeSLi Zefan 	return seq_release(i, f);
265dbd0b4b3SFrederic Weisbecker }
266dbd0b4b3SFrederic Weisbecker 
267dbd0b4b3SFrederic Weisbecker static const struct file_operations tracing_stat_fops = {
268dbd0b4b3SFrederic Weisbecker 	.open		= tracing_stat_open,
269dbd0b4b3SFrederic Weisbecker 	.read		= seq_read,
270dbd0b4b3SFrederic Weisbecker 	.llseek		= seq_lseek,
271dbd0b4b3SFrederic Weisbecker 	.release	= tracing_stat_release
272dbd0b4b3SFrederic Weisbecker };
273dbd0b4b3SFrederic Weisbecker 
tracing_stat_init(void)274002bb86dSFrederic Weisbecker static int tracing_stat_init(void)
275dbd0b4b3SFrederic Weisbecker {
27622c36b18SWei Yang 	int ret;
277dbd0b4b3SFrederic Weisbecker 
27822c36b18SWei Yang 	ret = tracing_init_dentry();
27922c36b18SWei Yang 	if (ret)
280afccc00fSLuis Henriques 		return -ENODEV;
281dbd0b4b3SFrederic Weisbecker 
28222c36b18SWei Yang 	stat_dir = tracefs_create_dir("trace_stat", NULL);
283afccc00fSLuis Henriques 	if (!stat_dir) {
284a395d6a7SJoe Perches 		pr_warn("Could not create tracefs 'trace_stat' entry\n");
285afccc00fSLuis Henriques 		return -ENOMEM;
286afccc00fSLuis Henriques 	}
287dbd0b4b3SFrederic Weisbecker 	return 0;
288dbd0b4b3SFrederic Weisbecker }
289002bb86dSFrederic Weisbecker 
init_stat_file(struct stat_session * session)2900d64f834SFrederic Weisbecker static int init_stat_file(struct stat_session *session)
291002bb86dSFrederic Weisbecker {
292afccc00fSLuis Henriques 	int ret;
293afccc00fSLuis Henriques 
294afccc00fSLuis Henriques 	if (!stat_dir && (ret = tracing_stat_init()))
295afccc00fSLuis Henriques 		return ret;
296002bb86dSFrederic Weisbecker 
29721ccc9cdSSteven Rostedt (VMware) 	session->file = tracefs_create_file(session->ts->name, TRACE_MODE_WRITE,
29821ccc9cdSSteven Rostedt (VMware) 					    stat_dir, session,
29921ccc9cdSSteven Rostedt (VMware) 					    &tracing_stat_fops);
300002bb86dSFrederic Weisbecker 	if (!session->file)
301002bb86dSFrederic Weisbecker 		return -ENOMEM;
302002bb86dSFrederic Weisbecker 	return 0;
303002bb86dSFrederic Weisbecker }
30455922173SIngo Molnar 
register_stat_tracer(struct tracer_stat * trace)30555922173SIngo Molnar int register_stat_tracer(struct tracer_stat *trace)
30655922173SIngo Molnar {
30743bd1236SFrederic Weisbecker 	struct stat_session *session, *node;
308*08b76731SSteven Rostedt 	int ret;
30955922173SIngo Molnar 
31055922173SIngo Molnar 	if (!trace)
31155922173SIngo Molnar 		return -EINVAL;
31255922173SIngo Molnar 
31355922173SIngo Molnar 	if (!trace->stat_start || !trace->stat_next || !trace->stat_show)
31455922173SIngo Molnar 		return -EINVAL;
31555922173SIngo Molnar 
316*08b76731SSteven Rostedt 	guard(mutex)(&all_stat_sessions_mutex);
317*08b76731SSteven Rostedt 
31855922173SIngo Molnar 	/* Already registered? */
31943bd1236SFrederic Weisbecker 	list_for_each_entry(node, &all_stat_sessions, session_list) {
320dfb6cd1eSSteven Rostedt (VMware) 		if (node->ts == trace)
321*08b76731SSteven Rostedt 			return -EINVAL;
32255922173SIngo Molnar 	}
32355922173SIngo Molnar 
32455922173SIngo Molnar 	/* Init the session */
3258f184f27SFrederic Weisbecker 	session = kzalloc(sizeof(*session), GFP_KERNEL);
32655922173SIngo Molnar 	if (!session)
327*08b76731SSteven Rostedt 		return -ENOMEM;
32855922173SIngo Molnar 
32955922173SIngo Molnar 	session->ts = trace;
33055922173SIngo Molnar 	INIT_LIST_HEAD(&session->session_list);
33155922173SIngo Molnar 	mutex_init(&session->stat_mutex);
33255922173SIngo Molnar 
33355922173SIngo Molnar 	ret = init_stat_file(session);
33455922173SIngo Molnar 	if (ret) {
33555922173SIngo Molnar 		destroy_session(session);
336*08b76731SSteven Rostedt 		return ret;
33755922173SIngo Molnar 	}
33855922173SIngo Molnar 
33955922173SIngo Molnar 	/* Register */
34055922173SIngo Molnar 	list_add_tail(&session->session_list, &all_stat_sessions);
34155922173SIngo Molnar 
342*08b76731SSteven Rostedt 	return 0;
34355922173SIngo Molnar }
34455922173SIngo Molnar 
unregister_stat_tracer(struct tracer_stat * trace)34555922173SIngo Molnar void unregister_stat_tracer(struct tracer_stat *trace)
34655922173SIngo Molnar {
3470d64f834SFrederic Weisbecker 	struct stat_session *node, *tmp;
34855922173SIngo Molnar 
34955922173SIngo Molnar 	mutex_lock(&all_stat_sessions_mutex);
35055922173SIngo Molnar 	list_for_each_entry_safe(node, tmp, &all_stat_sessions, session_list) {
35155922173SIngo Molnar 		if (node->ts == trace) {
35255922173SIngo Molnar 			list_del(&node->session_list);
35355922173SIngo Molnar 			destroy_session(node);
35455922173SIngo Molnar 			break;
35555922173SIngo Molnar 		}
35655922173SIngo Molnar 	}
35755922173SIngo Molnar 	mutex_unlock(&all_stat_sessions_mutex);
35855922173SIngo Molnar }
359