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 <fweisbec@gmail.com>
6dbd0b4b3SFrederic Weisbecker *
7dbd0b4b3SFrederic Weisbecker * Based on the code from trace_branch.c which is
8dbd0b4b3SFrederic Weisbecker * Copyright (C) 2008 Steven Rostedt <srostedt@redhat.com>
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
131034939b6SFrederic Weisbecker mutex_lock(&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)
13909833521SSteven Rostedt goto exit;
14009833521SSteven Rostedt
141dbd3fbdfSLi Zefan ret = insert_stat(root, stat, ts->stat_cmp);
142dbd3fbdfSLi Zefan if (ret)
143dbd0b4b3SFrederic Weisbecker goto exit;
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 exit:
161034939b6SFrederic Weisbecker mutex_unlock(&session->stat_mutex);
162dbd0b4b3SFrederic Weisbecker return ret;
163dbd0b4b3SFrederic Weisbecker
164dbd3fbdfSLi Zefan exit_free_rbtree:
165636eaceeSLi Zefan __reset_stat_session(session);
166034939b6SFrederic Weisbecker mutex_unlock(&session->stat_mutex);
167dbd0b4b3SFrederic Weisbecker return ret;
168dbd0b4b3SFrederic Weisbecker }
169dbd0b4b3SFrederic Weisbecker
170dbd0b4b3SFrederic Weisbecker
stat_seq_start(struct seq_file * s,loff_t * pos)171dbd0b4b3SFrederic Weisbecker static void *stat_seq_start(struct seq_file *s, loff_t *pos)
172dbd0b4b3SFrederic Weisbecker {
1730d64f834SFrederic Weisbecker struct stat_session *session = s->private;
1748f184f27SFrederic Weisbecker struct rb_node *node;
17597d53202SLi Zefan int n = *pos;
1768f184f27SFrederic Weisbecker int i;
177dbd0b4b3SFrederic Weisbecker
178dbd3fbdfSLi Zefan /* Prevent from tracer switch or rbtree modification */
179034939b6SFrederic Weisbecker mutex_lock(&session->stat_mutex);
180dbd0b4b3SFrederic Weisbecker
181dbd0b4b3SFrederic Weisbecker /* If we are in the beginning of the file, print the headers */
18297d53202SLi Zefan if (session->ts->stat_headers) {
18397d53202SLi Zefan if (n == 0)
184e6f48901SLai Jiangshan return SEQ_START_TOKEN;
18597d53202SLi Zefan n--;
18697d53202SLi Zefan }
187dbd0b4b3SFrederic Weisbecker
1888f184f27SFrederic Weisbecker node = rb_first(&session->stat_root);
18997d53202SLi Zefan for (i = 0; node && i < n; i++)
1908f184f27SFrederic Weisbecker node = rb_next(node);
1918f184f27SFrederic Weisbecker
1928f184f27SFrederic Weisbecker return node;
193dbd0b4b3SFrederic Weisbecker }
194dbd0b4b3SFrederic Weisbecker
stat_seq_next(struct seq_file * s,void * p,loff_t * pos)195dbd0b4b3SFrederic Weisbecker static void *stat_seq_next(struct seq_file *s, void *p, loff_t *pos)
196dbd0b4b3SFrederic Weisbecker {
1970d64f834SFrederic Weisbecker struct stat_session *session = s->private;
1988f184f27SFrederic Weisbecker struct rb_node *node = p;
1998f184f27SFrederic Weisbecker
2008f184f27SFrederic Weisbecker (*pos)++;
201dbd0b4b3SFrederic Weisbecker
202e6f48901SLai Jiangshan if (p == SEQ_START_TOKEN)
2038f184f27SFrederic Weisbecker return rb_first(&session->stat_root);
204e6f48901SLai Jiangshan
2058f184f27SFrederic Weisbecker return rb_next(node);
206dbd0b4b3SFrederic Weisbecker }
207dbd0b4b3SFrederic Weisbecker
stat_seq_stop(struct seq_file * s,void * p)208034939b6SFrederic Weisbecker static void stat_seq_stop(struct seq_file *s, void *p)
209dbd0b4b3SFrederic Weisbecker {
2100d64f834SFrederic Weisbecker struct stat_session *session = s->private;
211034939b6SFrederic Weisbecker mutex_unlock(&session->stat_mutex);
212dbd0b4b3SFrederic Weisbecker }
213dbd0b4b3SFrederic Weisbecker
stat_seq_show(struct seq_file * s,void * v)214dbd0b4b3SFrederic Weisbecker static int stat_seq_show(struct seq_file *s, void *v)
215dbd0b4b3SFrederic Weisbecker {
2160d64f834SFrederic Weisbecker struct stat_session *session = s->private;
2178f184f27SFrederic Weisbecker struct stat_node *l = container_of(v, struct stat_node, node);
218e8a9cbf6SSteven Rostedt
219e6f48901SLai Jiangshan if (v == SEQ_START_TOKEN)
220e6f48901SLai Jiangshan return session->ts->stat_headers(s);
221e6f48901SLai Jiangshan
222034939b6SFrederic Weisbecker return session->ts->stat_show(s, l->stat);
223dbd0b4b3SFrederic Weisbecker }
224dbd0b4b3SFrederic Weisbecker
225dbd0b4b3SFrederic Weisbecker static const struct seq_operations trace_stat_seq_ops = {
226dbd0b4b3SFrederic Weisbecker .start = stat_seq_start,
227dbd0b4b3SFrederic Weisbecker .next = stat_seq_next,
228dbd0b4b3SFrederic Weisbecker .stop = stat_seq_stop,
229dbd0b4b3SFrederic Weisbecker .show = stat_seq_show
230dbd0b4b3SFrederic Weisbecker };
231dbd0b4b3SFrederic Weisbecker
232034939b6SFrederic Weisbecker /* The session stat is refilled and resorted at each stat file opening */
tracing_stat_open(struct inode * inode,struct file * file)233dbd0b4b3SFrederic Weisbecker static int tracing_stat_open(struct inode *inode, struct file *file)
234dbd0b4b3SFrederic Weisbecker {
235dbd0b4b3SFrederic Weisbecker int ret;
236636eaceeSLi Zefan struct seq_file *m;
2370d64f834SFrederic Weisbecker struct stat_session *session = inode->i_private;
238034939b6SFrederic Weisbecker
23917911ff3SSteven Rostedt (VMware) ret = security_locked_down(LOCKDOWN_TRACEFS);
24017911ff3SSteven Rostedt (VMware) if (ret)
24117911ff3SSteven Rostedt (VMware) return ret;
24217911ff3SSteven Rostedt (VMware)
243034939b6SFrederic Weisbecker ret = stat_seq_init(session);
244636eaceeSLi Zefan if (ret)
245636eaceeSLi Zefan return ret;
246636eaceeSLi Zefan
247636eaceeSLi Zefan ret = seq_open(file, &trace_stat_seq_ops);
248636eaceeSLi Zefan if (ret) {
249636eaceeSLi Zefan reset_stat_session(session);
250636eaceeSLi Zefan return ret;
251dbd0b4b3SFrederic Weisbecker }
252dbd0b4b3SFrederic Weisbecker
253636eaceeSLi Zefan m = file->private_data;
254636eaceeSLi Zefan m->private = session;
255dbd0b4b3SFrederic Weisbecker return ret;
256dbd0b4b3SFrederic Weisbecker }
257dbd0b4b3SFrederic Weisbecker
258dbd0b4b3SFrederic Weisbecker /*
259dbd3fbdfSLi Zefan * Avoid consuming memory with our now useless rbtree.
260dbd0b4b3SFrederic Weisbecker */
tracing_stat_release(struct inode * i,struct file * f)261dbd0b4b3SFrederic Weisbecker static int tracing_stat_release(struct inode *i, struct file *f)
262dbd0b4b3SFrederic Weisbecker {
2630d64f834SFrederic Weisbecker struct stat_session *session = i->i_private;
264034939b6SFrederic Weisbecker
265034939b6SFrederic Weisbecker reset_stat_session(session);
266034939b6SFrederic Weisbecker
267636eaceeSLi Zefan return seq_release(i, f);
268dbd0b4b3SFrederic Weisbecker }
269dbd0b4b3SFrederic Weisbecker
270dbd0b4b3SFrederic Weisbecker static const struct file_operations tracing_stat_fops = {
271dbd0b4b3SFrederic Weisbecker .open = tracing_stat_open,
272dbd0b4b3SFrederic Weisbecker .read = seq_read,
273dbd0b4b3SFrederic Weisbecker .llseek = seq_lseek,
274dbd0b4b3SFrederic Weisbecker .release = tracing_stat_release
275dbd0b4b3SFrederic Weisbecker };
276dbd0b4b3SFrederic Weisbecker
tracing_stat_init(void)277002bb86dSFrederic Weisbecker static int tracing_stat_init(void)
278dbd0b4b3SFrederic Weisbecker {
27922c36b18SWei Yang int ret;
280dbd0b4b3SFrederic Weisbecker
28122c36b18SWei Yang ret = tracing_init_dentry();
28222c36b18SWei Yang if (ret)
283afccc00fSLuis Henriques return -ENODEV;
284dbd0b4b3SFrederic Weisbecker
28522c36b18SWei Yang stat_dir = tracefs_create_dir("trace_stat", NULL);
286afccc00fSLuis Henriques if (!stat_dir) {
287a395d6a7SJoe Perches pr_warn("Could not create tracefs 'trace_stat' entry\n");
288afccc00fSLuis Henriques return -ENOMEM;
289afccc00fSLuis Henriques }
290dbd0b4b3SFrederic Weisbecker return 0;
291dbd0b4b3SFrederic Weisbecker }
292002bb86dSFrederic Weisbecker
init_stat_file(struct stat_session * session)2930d64f834SFrederic Weisbecker static int init_stat_file(struct stat_session *session)
294002bb86dSFrederic Weisbecker {
295afccc00fSLuis Henriques int ret;
296afccc00fSLuis Henriques
297afccc00fSLuis Henriques if (!stat_dir && (ret = tracing_stat_init()))
298afccc00fSLuis Henriques return ret;
299002bb86dSFrederic Weisbecker
300*4e4f6e33SSteven Rostedt (VMware) session->file = tracefs_create_file(session->ts->name, TRACE_MODE_WRITE,
301*4e4f6e33SSteven Rostedt (VMware) stat_dir, session,
302*4e4f6e33SSteven Rostedt (VMware) &tracing_stat_fops);
303002bb86dSFrederic Weisbecker if (!session->file)
304002bb86dSFrederic Weisbecker return -ENOMEM;
305002bb86dSFrederic Weisbecker return 0;
306002bb86dSFrederic Weisbecker }
30755922173SIngo Molnar
register_stat_tracer(struct tracer_stat * trace)30855922173SIngo Molnar int register_stat_tracer(struct tracer_stat *trace)
30955922173SIngo Molnar {
31043bd1236SFrederic Weisbecker struct stat_session *session, *node;
311dfb6cd1eSSteven Rostedt (VMware) int ret = -EINVAL;
31255922173SIngo Molnar
31355922173SIngo Molnar if (!trace)
31455922173SIngo Molnar return -EINVAL;
31555922173SIngo Molnar
31655922173SIngo Molnar if (!trace->stat_start || !trace->stat_next || !trace->stat_show)
31755922173SIngo Molnar return -EINVAL;
31855922173SIngo Molnar
31955922173SIngo Molnar /* Already registered? */
32055922173SIngo Molnar mutex_lock(&all_stat_sessions_mutex);
32143bd1236SFrederic Weisbecker list_for_each_entry(node, &all_stat_sessions, session_list) {
322dfb6cd1eSSteven Rostedt (VMware) if (node->ts == trace)
323dfb6cd1eSSteven Rostedt (VMware) goto out;
32455922173SIngo Molnar }
32555922173SIngo Molnar
326dfb6cd1eSSteven Rostedt (VMware) ret = -ENOMEM;
32755922173SIngo Molnar /* Init the session */
3288f184f27SFrederic Weisbecker session = kzalloc(sizeof(*session), GFP_KERNEL);
32955922173SIngo Molnar if (!session)
330dfb6cd1eSSteven Rostedt (VMware) goto out;
33155922173SIngo Molnar
33255922173SIngo Molnar session->ts = trace;
33355922173SIngo Molnar INIT_LIST_HEAD(&session->session_list);
33455922173SIngo Molnar mutex_init(&session->stat_mutex);
33555922173SIngo Molnar
33655922173SIngo Molnar ret = init_stat_file(session);
33755922173SIngo Molnar if (ret) {
33855922173SIngo Molnar destroy_session(session);
339dfb6cd1eSSteven Rostedt (VMware) goto out;
34055922173SIngo Molnar }
34155922173SIngo Molnar
342dfb6cd1eSSteven Rostedt (VMware) ret = 0;
34355922173SIngo Molnar /* Register */
34455922173SIngo Molnar list_add_tail(&session->session_list, &all_stat_sessions);
345dfb6cd1eSSteven Rostedt (VMware) out:
34655922173SIngo Molnar mutex_unlock(&all_stat_sessions_mutex);
34755922173SIngo Molnar
348dfb6cd1eSSteven Rostedt (VMware) return ret;
34955922173SIngo Molnar }
35055922173SIngo Molnar
unregister_stat_tracer(struct tracer_stat * trace)35155922173SIngo Molnar void unregister_stat_tracer(struct tracer_stat *trace)
35255922173SIngo Molnar {
3530d64f834SFrederic Weisbecker struct stat_session *node, *tmp;
35455922173SIngo Molnar
35555922173SIngo Molnar mutex_lock(&all_stat_sessions_mutex);
35655922173SIngo Molnar list_for_each_entry_safe(node, tmp, &all_stat_sessions, session_list) {
35755922173SIngo Molnar if (node->ts == trace) {
35855922173SIngo Molnar list_del(&node->session_list);
35955922173SIngo Molnar destroy_session(node);
36055922173SIngo Molnar break;
36155922173SIngo Molnar }
36255922173SIngo Molnar }
36355922173SIngo Molnar mutex_unlock(&all_stat_sessions_mutex);
36455922173SIngo Molnar }
365