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 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 = stat_seq_init(session); 242 if (ret) 243 return ret; 244 245 ret = seq_open(file, &trace_stat_seq_ops); 246 if (ret) { 247 reset_stat_session(session); 248 return ret; 249 } 250 251 m = file->private_data; 252 m->private = session; 253 return ret; 254 } 255 256 /* 257 * Avoid consuming memory with our now useless rbtree. 258 */ 259 static int tracing_stat_release(struct inode *i, struct file *f) 260 { 261 struct stat_session *session = i->i_private; 262 263 reset_stat_session(session); 264 265 return seq_release(i, f); 266 } 267 268 static const struct file_operations tracing_stat_fops = { 269 .open = tracing_stat_open, 270 .read = seq_read, 271 .llseek = seq_lseek, 272 .release = tracing_stat_release 273 }; 274 275 static int tracing_stat_init(void) 276 { 277 struct dentry *d_tracing; 278 279 d_tracing = tracing_init_dentry(); 280 if (IS_ERR(d_tracing)) 281 return 0; 282 283 stat_dir = tracefs_create_dir("trace_stat", d_tracing); 284 if (!stat_dir) 285 pr_warn("Could not create tracefs 'trace_stat' entry\n"); 286 return 0; 287 } 288 289 static int init_stat_file(struct stat_session *session) 290 { 291 if (!stat_dir && tracing_stat_init()) 292 return -ENODEV; 293 294 session->file = tracefs_create_file(session->ts->name, 0644, 295 stat_dir, 296 session, &tracing_stat_fops); 297 if (!session->file) 298 return -ENOMEM; 299 return 0; 300 } 301 302 int register_stat_tracer(struct tracer_stat *trace) 303 { 304 struct stat_session *session, *node; 305 int ret; 306 307 if (!trace) 308 return -EINVAL; 309 310 if (!trace->stat_start || !trace->stat_next || !trace->stat_show) 311 return -EINVAL; 312 313 /* Already registered? */ 314 mutex_lock(&all_stat_sessions_mutex); 315 list_for_each_entry(node, &all_stat_sessions, session_list) { 316 if (node->ts == trace) { 317 mutex_unlock(&all_stat_sessions_mutex); 318 return -EINVAL; 319 } 320 } 321 mutex_unlock(&all_stat_sessions_mutex); 322 323 /* Init the session */ 324 session = kzalloc(sizeof(*session), GFP_KERNEL); 325 if (!session) 326 return -ENOMEM; 327 328 session->ts = trace; 329 INIT_LIST_HEAD(&session->session_list); 330 mutex_init(&session->stat_mutex); 331 332 ret = init_stat_file(session); 333 if (ret) { 334 destroy_session(session); 335 return ret; 336 } 337 338 /* Register */ 339 mutex_lock(&all_stat_sessions_mutex); 340 list_add_tail(&session->session_list, &all_stat_sessions); 341 mutex_unlock(&all_stat_sessions_mutex); 342 343 return 0; 344 } 345 346 void unregister_stat_tracer(struct tracer_stat *trace) 347 { 348 struct stat_session *node, *tmp; 349 350 mutex_lock(&all_stat_sessions_mutex); 351 list_for_each_entry_safe(node, tmp, &all_stat_sessions, session_list) { 352 if (node->ts == trace) { 353 list_del(&node->session_list); 354 destroy_session(node); 355 break; 356 } 357 } 358 mutex_unlock(&all_stat_sessions_mutex); 359 } 360