Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0 2 : : /* 3 : : * Infrastructure for statistic tracing (histogram output). 4 : : * 5 : : * Copyright (C) 2008-2009 Frederic Weisbecker <fweisbec@gmail.com> 6 : : * 7 : : * Based on the code from trace_branch.c which is 8 : : * Copyright (C) 2008 Steven Rostedt <srostedt@redhat.com> 9 : : * 10 : : */ 11 : : 12 : : #include <linux/security.h> 13 : : #include <linux/list.h> 14 : : #include <linux/slab.h> 15 : : #include <linux/rbtree.h> 16 : : #include <linux/tracefs.h> 17 : : #include "trace_stat.h" 18 : : #include "trace.h" 19 : : 20 : : 21 : : /* 22 : : * List of stat red-black nodes from a tracer 23 : : * We use a such tree to sort quickly the stat 24 : : * entries from the tracer. 25 : : */ 26 : : struct stat_node { 27 : : struct rb_node node; 28 : : void *stat; 29 : : }; 30 : : 31 : : /* A stat session is the stats output in one file */ 32 : : struct stat_session { 33 : : struct list_head session_list; 34 : : struct tracer_stat *ts; 35 : : struct rb_root stat_root; 36 : : struct mutex stat_mutex; 37 : : struct dentry *file; 38 : : }; 39 : : 40 : : /* All of the sessions currently in use. Each stat file embed one session */ 41 : : static LIST_HEAD(all_stat_sessions); 42 : : static DEFINE_MUTEX(all_stat_sessions_mutex); 43 : : 44 : : /* The root directory for all stat files */ 45 : : static struct dentry *stat_dir; 46 : : 47 : 0 : static void __reset_stat_session(struct stat_session *session) 48 : : { 49 : : struct stat_node *snode, *n; 50 : : 51 : 0 : rbtree_postorder_for_each_entry_safe(snode, n, &session->stat_root, node) { 52 : 0 : if (session->ts->stat_release) 53 : 0 : session->ts->stat_release(snode->stat); 54 : 0 : kfree(snode); 55 : : } 56 : : 57 : 0 : session->stat_root = RB_ROOT; 58 : 0 : } 59 : : 60 : 0 : static void reset_stat_session(struct stat_session *session) 61 : : { 62 : 0 : mutex_lock(&session->stat_mutex); 63 : 0 : __reset_stat_session(session); 64 : 0 : mutex_unlock(&session->stat_mutex); 65 : 0 : } 66 : : 67 : 0 : static void destroy_session(struct stat_session *session) 68 : : { 69 : 0 : tracefs_remove(session->file); 70 : 0 : __reset_stat_session(session); 71 : : mutex_destroy(&session->stat_mutex); 72 : 0 : kfree(session); 73 : 0 : } 74 : : 75 : : typedef int (*cmp_stat_t)(void *, void *); 76 : : 77 : 0 : static int insert_stat(struct rb_root *root, void *stat, cmp_stat_t cmp) 78 : : { 79 : 0 : struct rb_node **new = &(root->rb_node), *parent = NULL; 80 : : struct stat_node *data; 81 : : 82 : 0 : data = kzalloc(sizeof(*data), GFP_KERNEL); 83 : 0 : if (!data) 84 : : return -ENOMEM; 85 : 0 : data->stat = stat; 86 : : 87 : : /* 88 : : * Figure out where to put new node 89 : : * This is a descendent sorting 90 : : */ 91 : 0 : while (*new) { 92 : : struct stat_node *this; 93 : : int result; 94 : : 95 : : this = container_of(*new, struct stat_node, node); 96 : 0 : result = cmp(data->stat, this->stat); 97 : : 98 : 0 : parent = *new; 99 : 0 : if (result >= 0) 100 : 0 : new = &((*new)->rb_left); 101 : : else 102 : 0 : new = &((*new)->rb_right); 103 : : } 104 : : 105 : 0 : rb_link_node(&data->node, parent, new); 106 : 0 : rb_insert_color(&data->node, root); 107 : 0 : return 0; 108 : : } 109 : : 110 : : /* 111 : : * For tracers that don't provide a stat_cmp callback. 112 : : * This one will force an insertion as right-most node 113 : : * in the rbtree. 114 : : */ 115 : 0 : static int dummy_cmp(void *p1, void *p2) 116 : : { 117 : 0 : return -1; 118 : : } 119 : : 120 : : /* 121 : : * Initialize the stat rbtree at each trace_stat file opening. 122 : : * All of these copies and sorting are required on all opening 123 : : * since the stats could have changed between two file sessions. 124 : : */ 125 : 0 : static int stat_seq_init(struct stat_session *session) 126 : : { 127 : 0 : struct tracer_stat *ts = session->ts; 128 : 0 : struct rb_root *root = &session->stat_root; 129 : : void *stat; 130 : : int ret = 0; 131 : : int i; 132 : : 133 : 0 : mutex_lock(&session->stat_mutex); 134 : 0 : __reset_stat_session(session); 135 : : 136 : 0 : if (!ts->stat_cmp) 137 : 0 : ts->stat_cmp = dummy_cmp; 138 : : 139 : 0 : stat = ts->stat_start(ts); 140 : 0 : if (!stat) 141 : : goto exit; 142 : : 143 : 0 : ret = insert_stat(root, stat, ts->stat_cmp); 144 : 0 : if (ret) 145 : : goto exit; 146 : : 147 : : /* 148 : : * Iterate over the tracer stat entries and store them in an rbtree. 149 : : */ 150 : 0 : for (i = 1; ; i++) { 151 : 0 : stat = ts->stat_next(stat, i); 152 : : 153 : : /* End of insertion */ 154 : 0 : if (!stat) 155 : : break; 156 : : 157 : 0 : ret = insert_stat(root, stat, ts->stat_cmp); 158 : 0 : if (ret) 159 : : goto exit_free_rbtree; 160 : 0 : } 161 : : 162 : : exit: 163 : 0 : mutex_unlock(&session->stat_mutex); 164 : 0 : return ret; 165 : : 166 : : exit_free_rbtree: 167 : 0 : __reset_stat_session(session); 168 : 0 : mutex_unlock(&session->stat_mutex); 169 : 0 : return ret; 170 : : } 171 : : 172 : : 173 : 0 : static void *stat_seq_start(struct seq_file *s, loff_t *pos) 174 : : { 175 : 0 : struct stat_session *session = s->private; 176 : : struct rb_node *node; 177 : 0 : int n = *pos; 178 : : int i; 179 : : 180 : : /* Prevent from tracer switch or rbtree modification */ 181 : 0 : mutex_lock(&session->stat_mutex); 182 : : 183 : : /* If we are in the beginning of the file, print the headers */ 184 : 0 : if (session->ts->stat_headers) { 185 : 0 : if (n == 0) 186 : : return SEQ_START_TOKEN; 187 : 0 : n--; 188 : : } 189 : : 190 : 0 : node = rb_first(&session->stat_root); 191 : 0 : for (i = 0; node && i < n; i++) 192 : 0 : node = rb_next(node); 193 : : 194 : 0 : return node; 195 : : } 196 : : 197 : 0 : static void *stat_seq_next(struct seq_file *s, void *p, loff_t *pos) 198 : : { 199 : 0 : struct stat_session *session = s->private; 200 : : struct rb_node *node = p; 201 : : 202 : 0 : (*pos)++; 203 : : 204 : 0 : if (p == SEQ_START_TOKEN) 205 : 0 : return rb_first(&session->stat_root); 206 : : 207 : 0 : return rb_next(node); 208 : : } 209 : : 210 : 0 : static void stat_seq_stop(struct seq_file *s, void *p) 211 : : { 212 : 0 : struct stat_session *session = s->private; 213 : 0 : mutex_unlock(&session->stat_mutex); 214 : 0 : } 215 : : 216 : 0 : static int stat_seq_show(struct seq_file *s, void *v) 217 : : { 218 : 0 : struct stat_session *session = s->private; 219 : : struct stat_node *l = container_of(v, struct stat_node, node); 220 : : 221 : 0 : if (v == SEQ_START_TOKEN) 222 : 0 : return session->ts->stat_headers(s); 223 : : 224 : 0 : return session->ts->stat_show(s, l->stat); 225 : : } 226 : : 227 : : static const struct seq_operations trace_stat_seq_ops = { 228 : : .start = stat_seq_start, 229 : : .next = stat_seq_next, 230 : : .stop = stat_seq_stop, 231 : : .show = stat_seq_show 232 : : }; 233 : : 234 : : /* The session stat is refilled and resorted at each stat file opening */ 235 : 0 : static int tracing_stat_open(struct inode *inode, struct file *file) 236 : : { 237 : : int ret; 238 : : struct seq_file *m; 239 : 0 : struct stat_session *session = inode->i_private; 240 : : 241 : 0 : ret = security_locked_down(LOCKDOWN_TRACEFS); 242 : 0 : if (ret) 243 : : return ret; 244 : : 245 : 0 : ret = stat_seq_init(session); 246 : 0 : if (ret) 247 : : return ret; 248 : : 249 : 0 : ret = seq_open(file, &trace_stat_seq_ops); 250 : 0 : if (ret) { 251 : 0 : reset_stat_session(session); 252 : 0 : return ret; 253 : : } 254 : : 255 : 0 : m = file->private_data; 256 : 0 : m->private = session; 257 : 0 : return ret; 258 : : } 259 : : 260 : : /* 261 : : * Avoid consuming memory with our now useless rbtree. 262 : : */ 263 : 0 : static int tracing_stat_release(struct inode *i, struct file *f) 264 : : { 265 : 0 : struct stat_session *session = i->i_private; 266 : : 267 : 0 : reset_stat_session(session); 268 : : 269 : 0 : return seq_release(i, f); 270 : : } 271 : : 272 : : static const struct file_operations tracing_stat_fops = { 273 : : .open = tracing_stat_open, 274 : : .read = seq_read, 275 : : .llseek = seq_lseek, 276 : : .release = tracing_stat_release 277 : : }; 278 : : 279 : 3 : static int tracing_stat_init(void) 280 : : { 281 : : struct dentry *d_tracing; 282 : : 283 : 3 : d_tracing = tracing_init_dentry(); 284 : 3 : if (IS_ERR(d_tracing)) 285 : : return -ENODEV; 286 : : 287 : 3 : stat_dir = tracefs_create_dir("trace_stat", d_tracing); 288 : 3 : if (!stat_dir) { 289 : 0 : pr_warn("Could not create tracefs 'trace_stat' entry\n"); 290 : 0 : return -ENOMEM; 291 : : } 292 : : return 0; 293 : : } 294 : : 295 : 3 : static int init_stat_file(struct stat_session *session) 296 : : { 297 : : int ret; 298 : : 299 : 3 : if (!stat_dir && (ret = tracing_stat_init())) 300 : : return ret; 301 : : 302 : 3 : session->file = tracefs_create_file(session->ts->name, 0644, 303 : : stat_dir, 304 : : session, &tracing_stat_fops); 305 : 3 : if (!session->file) 306 : : return -ENOMEM; 307 : 3 : return 0; 308 : : } 309 : : 310 : 3 : int register_stat_tracer(struct tracer_stat *trace) 311 : : { 312 : : struct stat_session *session, *node; 313 : : int ret = -EINVAL; 314 : : 315 : 3 : if (!trace) 316 : : return -EINVAL; 317 : : 318 : 3 : if (!trace->stat_start || !trace->stat_next || !trace->stat_show) 319 : : return -EINVAL; 320 : : 321 : : /* Already registered? */ 322 : 3 : mutex_lock(&all_stat_sessions_mutex); 323 : 3 : list_for_each_entry(node, &all_stat_sessions, session_list) { 324 : 3 : if (node->ts == trace) 325 : : goto out; 326 : : } 327 : : 328 : : ret = -ENOMEM; 329 : : /* Init the session */ 330 : 3 : session = kzalloc(sizeof(*session), GFP_KERNEL); 331 : 3 : if (!session) 332 : : goto out; 333 : : 334 : 3 : session->ts = trace; 335 : 3 : INIT_LIST_HEAD(&session->session_list); 336 : 3 : mutex_init(&session->stat_mutex); 337 : : 338 : 3 : ret = init_stat_file(session); 339 : 3 : if (ret) { 340 : 0 : destroy_session(session); 341 : 0 : goto out; 342 : : } 343 : : 344 : : ret = 0; 345 : : /* Register */ 346 : : list_add_tail(&session->session_list, &all_stat_sessions); 347 : : out: 348 : 3 : mutex_unlock(&all_stat_sessions_mutex); 349 : : 350 : 3 : return ret; 351 : : } 352 : : 353 : 0 : void unregister_stat_tracer(struct tracer_stat *trace) 354 : : { 355 : : struct stat_session *node, *tmp; 356 : : 357 : 0 : mutex_lock(&all_stat_sessions_mutex); 358 : 0 : list_for_each_entry_safe(node, tmp, &all_stat_sessions, session_list) { 359 : 0 : if (node->ts == trace) { 360 : : list_del(&node->session_list); 361 : 0 : destroy_session(node); 362 : 0 : break; 363 : : } 364 : : } 365 : 0 : mutex_unlock(&all_stat_sessions_mutex); 366 : 0 : }