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