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 static void __reset_stat_session(struct stat_session *session) 48 { 49 struct stat_node *snode, *n; 50 51 rbtree_postorder_for_each_entry_safe(snode, n, &session->stat_root, node) { 52 if (session->ts->stat_release) 53 session->ts->stat_release(snode->stat); 54 kfree(snode); 55 } 56 57 session->stat_root = RB_ROOT; 58 } 59 60 static void reset_stat_session(struct stat_session *session) 61 { 62 mutex_lock(&session->stat_mutex); 63 __reset_stat_session(session); 64 mutex_unlock(&session->stat_mutex); 65 } 66 67 static void destroy_session(struct stat_session *session) 68 { 69 tracefs_remove(session->file); 70 __reset_stat_session(session); 71 mutex_destroy(&session->stat_mutex); 72 kfree(session); 73 } 74 75 typedef int (*cmp_stat_t)(void *, void *); 76 77 static int insert_stat(struct rb_root *root, void *stat, cmp_stat_t cmp) 78 { 79 struct rb_node **new = &(root->rb_node), *parent = NULL; 80 struct stat_node *data; 81 82 data = kzalloc(sizeof(*data), GFP_KERNEL); 83 if (!data) 84 return -ENOMEM; 85 data->stat = stat; 86 87 /* 88 * Figure out where to put new node 89 * This is a descendent sorting 90 */ 91 while (*new) { 92 struct stat_node *this; 93 int result; 94 95 this = container_of(*new, struct stat_node, node); 96 result = cmp(data->stat, this->stat); 97 98 parent = *new; 99 if (result >= 0) 100 new = &((*new)->rb_left); 101 else 102 new = &((*new)->rb_right); 103 } 104 105 rb_link_node(&data->node, parent, new); 106 rb_insert_color(&data->node, root); 107 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 static int dummy_cmp(void *p1, void *p2) 116 { 117 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 static int stat_seq_init(struct stat_session *session) 126 { 127 struct tracer_stat *ts = session->ts; 128 struct rb_root *root = &session->stat_root; 129 void *stat; 130 int ret = 0; 131 int i; 132 133 mutex_lock(&session->stat_mutex); 134 __reset_stat_session(session); 135 136 if (!ts->stat_cmp) 137 ts->stat_cmp = dummy_cmp; 138 139 stat = ts->stat_start(ts); 140 if (!stat) 141 goto exit; 142 143 ret = insert_stat(root, stat, ts->stat_cmp); 144 if (ret) 145 goto exit; 146 147 /* 148 * Iterate over the tracer stat entries and store them in an rbtree. 149 */ 150 for (i = 1; ; i++) { 151 stat = ts->stat_next(stat, i); 152 153 /* End of insertion */ 154 if (!stat) 155 break; 156 157 ret = insert_stat(root, stat, ts->stat_cmp); 158 if (ret) 159 goto exit_free_rbtree; 160 } 161 162 exit: 163 mutex_unlock(&session->stat_mutex); 164 return ret; 165 166 exit_free_rbtree: 167 __reset_stat_session(session); 168 mutex_unlock(&session->stat_mutex); 169 return ret; 170 } 171 172 173 static void *stat_seq_start(struct seq_file *s, loff_t *pos) 174 { 175 struct stat_session *session = s->private; 176 struct rb_node *node; 177 int n = *pos; 178 int i; 179 180 /* Prevent from tracer switch or rbtree modification */ 181 mutex_lock(&session->stat_mutex); 182 183 /* If we are in the beginning of the file, print the headers */ 184 if (session->ts->stat_headers) { 185 if (n == 0) 186 return SEQ_START_TOKEN; 187 n--; 188 } 189 190 node = rb_first(&session->stat_root); 191 for (i = 0; node && i < n; i++) 192 node = rb_next(node); 193 194 return node; 195 } 196 197 static void *stat_seq_next(struct seq_file *s, void *p, loff_t *pos) 198 { 199 struct stat_session *session = s->private; 200 struct rb_node *node = p; 201 202 (*pos)++; 203 204 if (p == SEQ_START_TOKEN) 205 return rb_first(&session->stat_root); 206 207 return rb_next(node); 208 } 209 210 static void stat_seq_stop(struct seq_file *s, void *p) 211 { 212 struct stat_session *session = s->private; 213 mutex_unlock(&session->stat_mutex); 214 } 215 216 static int stat_seq_show(struct seq_file *s, void *v) 217 { 218 struct stat_session *session = s->private; 219 struct stat_node *l = container_of(v, struct stat_node, node); 220 221 if (v == SEQ_START_TOKEN) 222 return session->ts->stat_headers(s); 223 224 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 static int tracing_stat_open(struct inode *inode, struct file *file) 236 { 237 int ret; 238 struct seq_file *m; 239 struct stat_session *session = inode->i_private; 240 241 ret = security_locked_down(LOCKDOWN_TRACEFS); 242 if (ret) 243 return ret; 244 245 ret = stat_seq_init(session); 246 if (ret) 247 return ret; 248 249 ret = seq_open(file, &trace_stat_seq_ops); 250 if (ret) { 251 reset_stat_session(session); 252 return ret; 253 } 254 255 m = file->private_data; 256 m->private = session; 257 return ret; 258 } 259 260 /* 261 * Avoid consuming memory with our now useless rbtree. 262 */ 263 static int tracing_stat_release(struct inode *i, struct file *f) 264 { 265 struct stat_session *session = i->i_private; 266 267 reset_stat_session(session); 268 269 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 static int tracing_stat_init(void) 280 { 281 struct dentry *d_tracing; 282 283 d_tracing = tracing_init_dentry(); 284 if (IS_ERR(d_tracing)) 285 return 0; 286 287 stat_dir = tracefs_create_dir("trace_stat", d_tracing); 288 if (!stat_dir) 289 pr_warn("Could not create tracefs 'trace_stat' entry\n"); 290 return 0; 291 } 292 293 static int init_stat_file(struct stat_session *session) 294 { 295 if (!stat_dir && tracing_stat_init()) 296 return -ENODEV; 297 298 session->file = tracefs_create_file(session->ts->name, 0644, 299 stat_dir, 300 session, &tracing_stat_fops); 301 if (!session->file) 302 return -ENOMEM; 303 return 0; 304 } 305 306 int register_stat_tracer(struct tracer_stat *trace) 307 { 308 struct stat_session *session, *node; 309 int ret; 310 311 if (!trace) 312 return -EINVAL; 313 314 if (!trace->stat_start || !trace->stat_next || !trace->stat_show) 315 return -EINVAL; 316 317 /* Already registered? */ 318 mutex_lock(&all_stat_sessions_mutex); 319 list_for_each_entry(node, &all_stat_sessions, session_list) { 320 if (node->ts == trace) { 321 mutex_unlock(&all_stat_sessions_mutex); 322 return -EINVAL; 323 } 324 } 325 mutex_unlock(&all_stat_sessions_mutex); 326 327 /* Init the session */ 328 session = kzalloc(sizeof(*session), GFP_KERNEL); 329 if (!session) 330 return -ENOMEM; 331 332 session->ts = trace; 333 INIT_LIST_HEAD(&session->session_list); 334 mutex_init(&session->stat_mutex); 335 336 ret = init_stat_file(session); 337 if (ret) { 338 destroy_session(session); 339 return ret; 340 } 341 342 /* Register */ 343 mutex_lock(&all_stat_sessions_mutex); 344 list_add_tail(&session->session_list, &all_stat_sessions); 345 mutex_unlock(&all_stat_sessions_mutex); 346 347 return 0; 348 } 349 350 void unregister_stat_tracer(struct tracer_stat *trace) 351 { 352 struct stat_session *node, *tmp; 353 354 mutex_lock(&all_stat_sessions_mutex); 355 list_for_each_entry_safe(node, tmp, &all_stat_sessions, session_list) { 356 if (node->ts == trace) { 357 list_del(&node->session_list); 358 destroy_session(node); 359 break; 360 } 361 } 362 mutex_unlock(&all_stat_sessions_mutex); 363 } 364