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