]>
Commit | Line | Data |
---|---|---|
fb52607a FW |
1 | /* |
2 | * | |
3 | * Function graph tracer. | |
9005f3eb | 4 | * Copyright (c) 2008-2009 Frederic Weisbecker <fweisbec@gmail.com> |
fb52607a FW |
5 | * Mostly borrowed from function tracer which |
6 | * is Copyright (c) Steven Rostedt <srostedt@redhat.com> | |
7 | * | |
8 | */ | |
9 | #include <linux/debugfs.h> | |
10 | #include <linux/uaccess.h> | |
11 | #include <linux/ftrace.h> | |
12 | #include <linux/fs.h> | |
13 | ||
14 | #include "trace.h" | |
f0868d1e | 15 | #include "trace_output.h" |
fb52607a | 16 | |
287b6e68 | 17 | #define TRACE_GRAPH_INDENT 2 |
fb52607a | 18 | |
1a056155 | 19 | /* Flag options */ |
fb52607a | 20 | #define TRACE_GRAPH_PRINT_OVERRUN 0x1 |
1a056155 FW |
21 | #define TRACE_GRAPH_PRINT_CPU 0x2 |
22 | #define TRACE_GRAPH_PRINT_OVERHEAD 0x4 | |
11e84acc | 23 | #define TRACE_GRAPH_PRINT_PROC 0x8 |
9005f3eb FW |
24 | #define TRACE_GRAPH_PRINT_DURATION 0x10 |
25 | #define TRACE_GRAPH_PRINT_ABS_TIME 0X20 | |
1a056155 | 26 | |
fb52607a | 27 | static struct tracer_opt trace_opts[] = { |
9005f3eb | 28 | /* Display overruns? (for self-debug purpose) */ |
1a056155 FW |
29 | { TRACER_OPT(funcgraph-overrun, TRACE_GRAPH_PRINT_OVERRUN) }, |
30 | /* Display CPU ? */ | |
31 | { TRACER_OPT(funcgraph-cpu, TRACE_GRAPH_PRINT_CPU) }, | |
32 | /* Display Overhead ? */ | |
33 | { TRACER_OPT(funcgraph-overhead, TRACE_GRAPH_PRINT_OVERHEAD) }, | |
11e84acc FW |
34 | /* Display proc name/pid */ |
35 | { TRACER_OPT(funcgraph-proc, TRACE_GRAPH_PRINT_PROC) }, | |
9005f3eb FW |
36 | /* Display duration of execution */ |
37 | { TRACER_OPT(funcgraph-duration, TRACE_GRAPH_PRINT_DURATION) }, | |
38 | /* Display absolute time of an entry */ | |
39 | { TRACER_OPT(funcgraph-abstime, TRACE_GRAPH_PRINT_ABS_TIME) }, | |
fb52607a FW |
40 | { } /* Empty entry */ |
41 | }; | |
42 | ||
43 | static struct tracer_flags tracer_flags = { | |
11e84acc | 44 | /* Don't display overruns and proc by default */ |
9005f3eb FW |
45 | .val = TRACE_GRAPH_PRINT_CPU | TRACE_GRAPH_PRINT_OVERHEAD | |
46 | TRACE_GRAPH_PRINT_DURATION, | |
fb52607a FW |
47 | .opts = trace_opts |
48 | }; | |
49 | ||
287b6e68 | 50 | /* pid on the last trace processed */ |
9005f3eb | 51 | |
fb52607a FW |
52 | |
53 | static int graph_trace_init(struct trace_array *tr) | |
54 | { | |
660c7f9b SR |
55 | int cpu, ret; |
56 | ||
fb52607a FW |
57 | for_each_online_cpu(cpu) |
58 | tracing_reset(tr, cpu); | |
59 | ||
660c7f9b | 60 | ret = register_ftrace_graph(&trace_graph_return, |
287b6e68 | 61 | &trace_graph_entry); |
660c7f9b SR |
62 | if (ret) |
63 | return ret; | |
64 | tracing_start_cmdline_record(); | |
65 | ||
66 | return 0; | |
fb52607a FW |
67 | } |
68 | ||
69 | static void graph_trace_reset(struct trace_array *tr) | |
70 | { | |
660c7f9b SR |
71 | tracing_stop_cmdline_record(); |
72 | unregister_ftrace_graph(); | |
fb52607a FW |
73 | } |
74 | ||
1a056155 FW |
75 | static inline int log10_cpu(int nb) |
76 | { | |
77 | if (nb / 100) | |
78 | return 3; | |
79 | if (nb / 10) | |
80 | return 2; | |
81 | return 1; | |
82 | } | |
83 | ||
84 | static enum print_line_t | |
85 | print_graph_cpu(struct trace_seq *s, int cpu) | |
86 | { | |
87 | int i; | |
88 | int ret; | |
89 | int log10_this = log10_cpu(cpu); | |
4462344e | 90 | int log10_all = log10_cpu(cpumask_weight(cpu_online_mask)); |
1a056155 FW |
91 | |
92 | ||
d51090b3 IM |
93 | /* |
94 | * Start with a space character - to make it stand out | |
95 | * to the right a bit when trace output is pasted into | |
96 | * email: | |
97 | */ | |
98 | ret = trace_seq_printf(s, " "); | |
99 | ||
100 | /* | |
101 | * Tricky - we space the CPU field according to the max | |
102 | * number of online CPUs. On a 2-cpu system it would take | |
103 | * a maximum of 1 digit - on a 128 cpu system it would | |
104 | * take up to 3 digits: | |
105 | */ | |
1a056155 FW |
106 | for (i = 0; i < log10_all - log10_this; i++) { |
107 | ret = trace_seq_printf(s, " "); | |
108 | if (!ret) | |
109 | return TRACE_TYPE_PARTIAL_LINE; | |
110 | } | |
111 | ret = trace_seq_printf(s, "%d) ", cpu); | |
112 | if (!ret) | |
d51090b3 IM |
113 | return TRACE_TYPE_PARTIAL_LINE; |
114 | ||
1a056155 FW |
115 | return TRACE_TYPE_HANDLED; |
116 | } | |
117 | ||
11e84acc FW |
118 | #define TRACE_GRAPH_PROCINFO_LENGTH 14 |
119 | ||
120 | static enum print_line_t | |
121 | print_graph_proc(struct trace_seq *s, pid_t pid) | |
122 | { | |
123 | int i; | |
124 | int ret; | |
125 | int len; | |
126 | char comm[8]; | |
127 | int spaces = 0; | |
128 | /* sign + log10(MAX_INT) + '\0' */ | |
129 | char pid_str[11]; | |
130 | ||
131 | strncpy(comm, trace_find_cmdline(pid), 7); | |
132 | comm[7] = '\0'; | |
133 | sprintf(pid_str, "%d", pid); | |
134 | ||
135 | /* 1 stands for the "-" character */ | |
136 | len = strlen(comm) + strlen(pid_str) + 1; | |
137 | ||
138 | if (len < TRACE_GRAPH_PROCINFO_LENGTH) | |
139 | spaces = TRACE_GRAPH_PROCINFO_LENGTH - len; | |
140 | ||
141 | /* First spaces to align center */ | |
142 | for (i = 0; i < spaces / 2; i++) { | |
143 | ret = trace_seq_printf(s, " "); | |
144 | if (!ret) | |
145 | return TRACE_TYPE_PARTIAL_LINE; | |
146 | } | |
147 | ||
148 | ret = trace_seq_printf(s, "%s-%s", comm, pid_str); | |
149 | if (!ret) | |
150 | return TRACE_TYPE_PARTIAL_LINE; | |
151 | ||
152 | /* Last spaces to align center */ | |
153 | for (i = 0; i < spaces - (spaces / 2); i++) { | |
154 | ret = trace_seq_printf(s, " "); | |
155 | if (!ret) | |
156 | return TRACE_TYPE_PARTIAL_LINE; | |
157 | } | |
158 | return TRACE_TYPE_HANDLED; | |
159 | } | |
160 | ||
1a056155 | 161 | |
287b6e68 | 162 | /* If the pid changed since the last trace, output this event */ |
11e84acc | 163 | static enum print_line_t |
9005f3eb | 164 | verif_pid(struct trace_seq *s, pid_t pid, int cpu, pid_t *last_pids_cpu) |
287b6e68 | 165 | { |
d51090b3 | 166 | pid_t prev_pid; |
9005f3eb | 167 | pid_t *last_pid; |
d51090b3 | 168 | int ret; |
660c7f9b | 169 | |
9005f3eb FW |
170 | if (!last_pids_cpu) |
171 | return TRACE_TYPE_HANDLED; | |
172 | ||
173 | last_pid = per_cpu_ptr(last_pids_cpu, cpu); | |
174 | ||
175 | if (*last_pid == pid) | |
11e84acc | 176 | return TRACE_TYPE_HANDLED; |
fb52607a | 177 | |
9005f3eb FW |
178 | prev_pid = *last_pid; |
179 | *last_pid = pid; | |
d51090b3 | 180 | |
9005f3eb FW |
181 | if (prev_pid == -1) |
182 | return TRACE_TYPE_HANDLED; | |
d51090b3 IM |
183 | /* |
184 | * Context-switch trace line: | |
185 | ||
186 | ------------------------------------------ | |
187 | | 1) migration/0--1 => sshd-1755 | |
188 | ------------------------------------------ | |
189 | ||
190 | */ | |
191 | ret = trace_seq_printf(s, | |
1fd8f2a3 | 192 | " ------------------------------------------\n"); |
11e84acc FW |
193 | if (!ret) |
194 | TRACE_TYPE_PARTIAL_LINE; | |
195 | ||
196 | ret = print_graph_cpu(s, cpu); | |
197 | if (ret == TRACE_TYPE_PARTIAL_LINE) | |
198 | TRACE_TYPE_PARTIAL_LINE; | |
199 | ||
200 | ret = print_graph_proc(s, prev_pid); | |
201 | if (ret == TRACE_TYPE_PARTIAL_LINE) | |
202 | TRACE_TYPE_PARTIAL_LINE; | |
203 | ||
204 | ret = trace_seq_printf(s, " => "); | |
205 | if (!ret) | |
206 | TRACE_TYPE_PARTIAL_LINE; | |
207 | ||
208 | ret = print_graph_proc(s, pid); | |
209 | if (ret == TRACE_TYPE_PARTIAL_LINE) | |
210 | TRACE_TYPE_PARTIAL_LINE; | |
211 | ||
212 | ret = trace_seq_printf(s, | |
213 | "\n ------------------------------------------\n\n"); | |
214 | if (!ret) | |
215 | TRACE_TYPE_PARTIAL_LINE; | |
216 | ||
d51090b3 | 217 | return ret; |
287b6e68 FW |
218 | } |
219 | ||
83a8df61 FW |
220 | static bool |
221 | trace_branch_is_leaf(struct trace_iterator *iter, | |
222 | struct ftrace_graph_ent_entry *curr) | |
223 | { | |
224 | struct ring_buffer_iter *ring_iter; | |
225 | struct ring_buffer_event *event; | |
226 | struct ftrace_graph_ret_entry *next; | |
227 | ||
228 | ring_iter = iter->buffer_iter[iter->cpu]; | |
229 | ||
230 | if (!ring_iter) | |
231 | return false; | |
232 | ||
1a056155 | 233 | event = ring_buffer_iter_peek(ring_iter, NULL); |
83a8df61 FW |
234 | |
235 | if (!event) | |
236 | return false; | |
237 | ||
238 | next = ring_buffer_event_data(event); | |
239 | ||
240 | if (next->ent.type != TRACE_GRAPH_RET) | |
241 | return false; | |
242 | ||
243 | if (curr->ent.pid != next->ent.pid || | |
244 | curr->graph_ent.func != next->ret.func) | |
245 | return false; | |
246 | ||
247 | return true; | |
248 | } | |
249 | ||
9005f3eb FW |
250 | /* Signal a overhead of time execution to the output */ |
251 | static int | |
252 | print_graph_overhead(unsigned long long duration, struct trace_seq *s) | |
253 | { | |
254 | /* If duration disappear, we don't need anything */ | |
255 | if (!(tracer_flags.val & TRACE_GRAPH_PRINT_DURATION)) | |
256 | return 1; | |
257 | ||
258 | /* Non nested entry or return */ | |
259 | if (duration == -1) | |
260 | return trace_seq_printf(s, " "); | |
261 | ||
262 | if (tracer_flags.val & TRACE_GRAPH_PRINT_OVERHEAD) { | |
263 | /* Duration exceeded 100 msecs */ | |
264 | if (duration > 100000ULL) | |
265 | return trace_seq_printf(s, "! "); | |
266 | ||
267 | /* Duration exceeded 10 msecs */ | |
268 | if (duration > 10000ULL) | |
269 | return trace_seq_printf(s, "+ "); | |
270 | } | |
271 | ||
272 | return trace_seq_printf(s, " "); | |
273 | } | |
274 | ||
f8b755ac FW |
275 | static enum print_line_t |
276 | print_graph_irq(struct trace_seq *s, unsigned long addr, | |
9005f3eb | 277 | enum trace_type type, int cpu, pid_t pid) |
f8b755ac FW |
278 | { |
279 | int ret; | |
280 | ||
281 | if (addr < (unsigned long)__irqentry_text_start || | |
282 | addr >= (unsigned long)__irqentry_text_end) | |
283 | return TRACE_TYPE_UNHANDLED; | |
284 | ||
9005f3eb FW |
285 | /* Cpu */ |
286 | if (tracer_flags.val & TRACE_GRAPH_PRINT_CPU) { | |
287 | ret = print_graph_cpu(s, cpu); | |
288 | if (ret == TRACE_TYPE_PARTIAL_LINE) | |
289 | return TRACE_TYPE_PARTIAL_LINE; | |
290 | } | |
291 | /* Proc */ | |
292 | if (tracer_flags.val & TRACE_GRAPH_PRINT_PROC) { | |
293 | ret = print_graph_proc(s, pid); | |
294 | if (ret == TRACE_TYPE_PARTIAL_LINE) | |
295 | return TRACE_TYPE_PARTIAL_LINE; | |
296 | ret = trace_seq_printf(s, " | "); | |
297 | if (!ret) | |
298 | return TRACE_TYPE_PARTIAL_LINE; | |
299 | } | |
f8b755ac | 300 | |
9005f3eb FW |
301 | /* No overhead */ |
302 | ret = print_graph_overhead(-1, s); | |
303 | if (!ret) | |
304 | return TRACE_TYPE_PARTIAL_LINE; | |
f8b755ac | 305 | |
9005f3eb FW |
306 | if (type == TRACE_GRAPH_ENT) |
307 | ret = trace_seq_printf(s, "==========>"); | |
308 | else | |
309 | ret = trace_seq_printf(s, "<=========="); | |
310 | ||
311 | if (!ret) | |
312 | return TRACE_TYPE_PARTIAL_LINE; | |
313 | ||
314 | /* Don't close the duration column if haven't one */ | |
315 | if (tracer_flags.val & TRACE_GRAPH_PRINT_DURATION) | |
316 | trace_seq_printf(s, " |"); | |
317 | ret = trace_seq_printf(s, "\n"); | |
f8b755ac | 318 | |
f8b755ac FW |
319 | if (!ret) |
320 | return TRACE_TYPE_PARTIAL_LINE; | |
321 | return TRACE_TYPE_HANDLED; | |
322 | } | |
83a8df61 | 323 | |
166d3c79 | 324 | static enum print_line_t |
83a8df61 FW |
325 | print_graph_duration(unsigned long long duration, struct trace_seq *s) |
326 | { | |
327 | unsigned long nsecs_rem = do_div(duration, 1000); | |
166d3c79 FW |
328 | /* log10(ULONG_MAX) + '\0' */ |
329 | char msecs_str[21]; | |
330 | char nsecs_str[5]; | |
331 | int ret, len; | |
332 | int i; | |
333 | ||
334 | sprintf(msecs_str, "%lu", (unsigned long) duration); | |
335 | ||
336 | /* Print msecs */ | |
9005f3eb | 337 | ret = trace_seq_printf(s, "%s", msecs_str); |
166d3c79 FW |
338 | if (!ret) |
339 | return TRACE_TYPE_PARTIAL_LINE; | |
340 | ||
341 | len = strlen(msecs_str); | |
342 | ||
343 | /* Print nsecs (we don't want to exceed 7 numbers) */ | |
344 | if (len < 7) { | |
345 | snprintf(nsecs_str, 8 - len, "%03lu", nsecs_rem); | |
346 | ret = trace_seq_printf(s, ".%s", nsecs_str); | |
347 | if (!ret) | |
348 | return TRACE_TYPE_PARTIAL_LINE; | |
349 | len += strlen(nsecs_str); | |
350 | } | |
351 | ||
352 | ret = trace_seq_printf(s, " us "); | |
353 | if (!ret) | |
354 | return TRACE_TYPE_PARTIAL_LINE; | |
355 | ||
356 | /* Print remaining spaces to fit the row's width */ | |
357 | for (i = len; i < 7; i++) { | |
358 | ret = trace_seq_printf(s, " "); | |
359 | if (!ret) | |
360 | return TRACE_TYPE_PARTIAL_LINE; | |
361 | } | |
362 | ||
363 | ret = trace_seq_printf(s, "| "); | |
364 | if (!ret) | |
365 | return TRACE_TYPE_PARTIAL_LINE; | |
366 | return TRACE_TYPE_HANDLED; | |
367 | ||
83a8df61 FW |
368 | } |
369 | ||
9005f3eb | 370 | static int print_graph_abs_time(u64 t, struct trace_seq *s) |
83a8df61 | 371 | { |
9005f3eb | 372 | unsigned long usecs_rem; |
83a8df61 | 373 | |
9005f3eb FW |
374 | usecs_rem = do_div(t, 1000000000); |
375 | usecs_rem /= 1000; | |
83a8df61 | 376 | |
9005f3eb FW |
377 | return trace_seq_printf(s, "%5lu.%06lu | ", |
378 | (unsigned long)t, usecs_rem); | |
83a8df61 FW |
379 | } |
380 | ||
381 | /* Case of a leaf function on its call entry */ | |
287b6e68 | 382 | static enum print_line_t |
83a8df61 FW |
383 | print_graph_entry_leaf(struct trace_iterator *iter, |
384 | struct ftrace_graph_ent_entry *entry, struct trace_seq *s) | |
fb52607a | 385 | { |
83a8df61 FW |
386 | struct ftrace_graph_ret_entry *ret_entry; |
387 | struct ftrace_graph_ret *graph_ret; | |
388 | struct ring_buffer_event *event; | |
389 | struct ftrace_graph_ent *call; | |
390 | unsigned long long duration; | |
fb52607a | 391 | int ret; |
1a056155 | 392 | int i; |
fb52607a | 393 | |
83a8df61 FW |
394 | event = ring_buffer_read(iter->buffer_iter[iter->cpu], NULL); |
395 | ret_entry = ring_buffer_event_data(event); | |
396 | graph_ret = &ret_entry->ret; | |
397 | call = &entry->graph_ent; | |
398 | duration = graph_ret->rettime - graph_ret->calltime; | |
399 | ||
400 | /* Overhead */ | |
9005f3eb FW |
401 | ret = print_graph_overhead(duration, s); |
402 | if (!ret) | |
403 | return TRACE_TYPE_PARTIAL_LINE; | |
1a056155 FW |
404 | |
405 | /* Duration */ | |
9005f3eb FW |
406 | if (tracer_flags.val & TRACE_GRAPH_PRINT_DURATION) { |
407 | ret = print_graph_duration(duration, s); | |
408 | if (ret == TRACE_TYPE_PARTIAL_LINE) | |
409 | return TRACE_TYPE_PARTIAL_LINE; | |
410 | } | |
437f24fb | 411 | |
83a8df61 FW |
412 | /* Function */ |
413 | for (i = 0; i < call->depth * TRACE_GRAPH_INDENT; i++) { | |
414 | ret = trace_seq_printf(s, " "); | |
415 | if (!ret) | |
416 | return TRACE_TYPE_PARTIAL_LINE; | |
417 | } | |
418 | ||
419 | ret = seq_print_ip_sym(s, call->func, 0); | |
420 | if (!ret) | |
421 | return TRACE_TYPE_PARTIAL_LINE; | |
422 | ||
1a056155 | 423 | ret = trace_seq_printf(s, "();\n"); |
83a8df61 FW |
424 | if (!ret) |
425 | return TRACE_TYPE_PARTIAL_LINE; | |
426 | ||
427 | return TRACE_TYPE_HANDLED; | |
428 | } | |
429 | ||
430 | static enum print_line_t | |
431 | print_graph_entry_nested(struct ftrace_graph_ent_entry *entry, | |
f8b755ac | 432 | struct trace_seq *s, pid_t pid, int cpu) |
83a8df61 FW |
433 | { |
434 | int i; | |
435 | int ret; | |
436 | struct ftrace_graph_ent *call = &entry->graph_ent; | |
437 | ||
438 | /* No overhead */ | |
9005f3eb FW |
439 | ret = print_graph_overhead(-1, s); |
440 | if (!ret) | |
441 | return TRACE_TYPE_PARTIAL_LINE; | |
1a056155 | 442 | |
9005f3eb FW |
443 | /* No time */ |
444 | if (tracer_flags.val & TRACE_GRAPH_PRINT_DURATION) { | |
f8b755ac FW |
445 | ret = trace_seq_printf(s, " | "); |
446 | if (!ret) | |
447 | return TRACE_TYPE_PARTIAL_LINE; | |
f8b755ac FW |
448 | } |
449 | ||
83a8df61 | 450 | /* Function */ |
287b6e68 FW |
451 | for (i = 0; i < call->depth * TRACE_GRAPH_INDENT; i++) { |
452 | ret = trace_seq_printf(s, " "); | |
fb52607a FW |
453 | if (!ret) |
454 | return TRACE_TYPE_PARTIAL_LINE; | |
287b6e68 FW |
455 | } |
456 | ||
457 | ret = seq_print_ip_sym(s, call->func, 0); | |
458 | if (!ret) | |
459 | return TRACE_TYPE_PARTIAL_LINE; | |
460 | ||
1a056155 | 461 | ret = trace_seq_printf(s, "() {\n"); |
83a8df61 FW |
462 | if (!ret) |
463 | return TRACE_TYPE_PARTIAL_LINE; | |
464 | ||
287b6e68 FW |
465 | return TRACE_TYPE_HANDLED; |
466 | } | |
467 | ||
83a8df61 FW |
468 | static enum print_line_t |
469 | print_graph_entry(struct ftrace_graph_ent_entry *field, struct trace_seq *s, | |
9005f3eb | 470 | struct trace_iterator *iter) |
83a8df61 FW |
471 | { |
472 | int ret; | |
9005f3eb FW |
473 | int cpu = iter->cpu; |
474 | pid_t *last_entry = iter->private; | |
83a8df61 | 475 | struct trace_entry *ent = iter->ent; |
9005f3eb | 476 | struct ftrace_graph_ent *call = &field->graph_ent; |
83a8df61 | 477 | |
1a056155 | 478 | /* Pid */ |
9005f3eb FW |
479 | if (verif_pid(s, ent->pid, cpu, last_entry) == TRACE_TYPE_PARTIAL_LINE) |
480 | return TRACE_TYPE_PARTIAL_LINE; | |
481 | ||
482 | /* Interrupt */ | |
483 | ret = print_graph_irq(s, call->func, TRACE_GRAPH_ENT, cpu, ent->pid); | |
484 | if (ret == TRACE_TYPE_PARTIAL_LINE) | |
83a8df61 FW |
485 | return TRACE_TYPE_PARTIAL_LINE; |
486 | ||
9005f3eb FW |
487 | /* Absolute time */ |
488 | if (tracer_flags.val & TRACE_GRAPH_PRINT_ABS_TIME) { | |
489 | ret = print_graph_abs_time(iter->ts, s); | |
490 | if (!ret) | |
491 | return TRACE_TYPE_PARTIAL_LINE; | |
492 | } | |
493 | ||
1a056155 FW |
494 | /* Cpu */ |
495 | if (tracer_flags.val & TRACE_GRAPH_PRINT_CPU) { | |
496 | ret = print_graph_cpu(s, cpu); | |
11e84acc FW |
497 | if (ret == TRACE_TYPE_PARTIAL_LINE) |
498 | return TRACE_TYPE_PARTIAL_LINE; | |
499 | } | |
500 | ||
501 | /* Proc */ | |
502 | if (tracer_flags.val & TRACE_GRAPH_PRINT_PROC) { | |
503 | ret = print_graph_proc(s, ent->pid); | |
504 | if (ret == TRACE_TYPE_PARTIAL_LINE) | |
505 | return TRACE_TYPE_PARTIAL_LINE; | |
506 | ||
507 | ret = trace_seq_printf(s, " | "); | |
1a056155 FW |
508 | if (!ret) |
509 | return TRACE_TYPE_PARTIAL_LINE; | |
510 | } | |
83a8df61 FW |
511 | |
512 | if (trace_branch_is_leaf(iter, field)) | |
513 | return print_graph_entry_leaf(iter, field, s); | |
514 | else | |
f8b755ac | 515 | return print_graph_entry_nested(field, s, iter->ent->pid, cpu); |
83a8df61 FW |
516 | |
517 | } | |
518 | ||
287b6e68 FW |
519 | static enum print_line_t |
520 | print_graph_return(struct ftrace_graph_ret *trace, struct trace_seq *s, | |
9005f3eb | 521 | struct trace_entry *ent, struct trace_iterator *iter) |
287b6e68 FW |
522 | { |
523 | int i; | |
524 | int ret; | |
9005f3eb FW |
525 | int cpu = iter->cpu; |
526 | pid_t *last_pid = iter->private; | |
83a8df61 | 527 | unsigned long long duration = trace->rettime - trace->calltime; |
287b6e68 | 528 | |
83a8df61 | 529 | /* Pid */ |
9005f3eb | 530 | if (verif_pid(s, ent->pid, cpu, last_pid) == TRACE_TYPE_PARTIAL_LINE) |
437f24fb SR |
531 | return TRACE_TYPE_PARTIAL_LINE; |
532 | ||
9005f3eb FW |
533 | /* Absolute time */ |
534 | if (tracer_flags.val & TRACE_GRAPH_PRINT_ABS_TIME) { | |
535 | ret = print_graph_abs_time(iter->ts, s); | |
536 | if (!ret) | |
537 | return TRACE_TYPE_PARTIAL_LINE; | |
538 | } | |
539 | ||
83a8df61 | 540 | /* Cpu */ |
1a056155 FW |
541 | if (tracer_flags.val & TRACE_GRAPH_PRINT_CPU) { |
542 | ret = print_graph_cpu(s, cpu); | |
11e84acc FW |
543 | if (ret == TRACE_TYPE_PARTIAL_LINE) |
544 | return TRACE_TYPE_PARTIAL_LINE; | |
545 | } | |
546 | ||
547 | /* Proc */ | |
548 | if (tracer_flags.val & TRACE_GRAPH_PRINT_PROC) { | |
549 | ret = print_graph_proc(s, ent->pid); | |
550 | if (ret == TRACE_TYPE_PARTIAL_LINE) | |
551 | return TRACE_TYPE_PARTIAL_LINE; | |
552 | ||
553 | ret = trace_seq_printf(s, " | "); | |
1a056155 FW |
554 | if (!ret) |
555 | return TRACE_TYPE_PARTIAL_LINE; | |
556 | } | |
fb52607a | 557 | |
83a8df61 | 558 | /* Overhead */ |
9005f3eb FW |
559 | ret = print_graph_overhead(duration, s); |
560 | if (!ret) | |
561 | return TRACE_TYPE_PARTIAL_LINE; | |
1a056155 FW |
562 | |
563 | /* Duration */ | |
9005f3eb FW |
564 | if (tracer_flags.val & TRACE_GRAPH_PRINT_DURATION) { |
565 | ret = print_graph_duration(duration, s); | |
566 | if (ret == TRACE_TYPE_PARTIAL_LINE) | |
567 | return TRACE_TYPE_PARTIAL_LINE; | |
568 | } | |
83a8df61 FW |
569 | |
570 | /* Closing brace */ | |
287b6e68 FW |
571 | for (i = 0; i < trace->depth * TRACE_GRAPH_INDENT; i++) { |
572 | ret = trace_seq_printf(s, " "); | |
fb52607a FW |
573 | if (!ret) |
574 | return TRACE_TYPE_PARTIAL_LINE; | |
287b6e68 FW |
575 | } |
576 | ||
1a056155 | 577 | ret = trace_seq_printf(s, "}\n"); |
287b6e68 FW |
578 | if (!ret) |
579 | return TRACE_TYPE_PARTIAL_LINE; | |
fb52607a | 580 | |
83a8df61 | 581 | /* Overrun */ |
287b6e68 FW |
582 | if (tracer_flags.val & TRACE_GRAPH_PRINT_OVERRUN) { |
583 | ret = trace_seq_printf(s, " (Overruns: %lu)\n", | |
584 | trace->overrun); | |
fb52607a FW |
585 | if (!ret) |
586 | return TRACE_TYPE_PARTIAL_LINE; | |
287b6e68 | 587 | } |
f8b755ac FW |
588 | |
589 | ret = print_graph_irq(s, trace->func, TRACE_GRAPH_RET, cpu, ent->pid); | |
590 | if (ret == TRACE_TYPE_PARTIAL_LINE) | |
591 | return TRACE_TYPE_PARTIAL_LINE; | |
592 | ||
287b6e68 FW |
593 | return TRACE_TYPE_HANDLED; |
594 | } | |
595 | ||
1fd8f2a3 FW |
596 | static enum print_line_t |
597 | print_graph_comment(struct print_entry *trace, struct trace_seq *s, | |
598 | struct trace_entry *ent, struct trace_iterator *iter) | |
599 | { | |
600 | int i; | |
601 | int ret; | |
9005f3eb FW |
602 | int cpu = iter->cpu; |
603 | pid_t *last_pid = iter->private; | |
604 | ||
605 | /* Absolute time */ | |
606 | if (tracer_flags.val & TRACE_GRAPH_PRINT_ABS_TIME) { | |
607 | ret = print_graph_abs_time(iter->ts, s); | |
608 | if (!ret) | |
609 | return TRACE_TYPE_PARTIAL_LINE; | |
610 | } | |
1fd8f2a3 FW |
611 | |
612 | /* Pid */ | |
9005f3eb | 613 | if (verif_pid(s, ent->pid, cpu, last_pid) == TRACE_TYPE_PARTIAL_LINE) |
1fd8f2a3 FW |
614 | return TRACE_TYPE_PARTIAL_LINE; |
615 | ||
616 | /* Cpu */ | |
617 | if (tracer_flags.val & TRACE_GRAPH_PRINT_CPU) { | |
9005f3eb | 618 | ret = print_graph_cpu(s, cpu); |
1fd8f2a3 FW |
619 | if (ret == TRACE_TYPE_PARTIAL_LINE) |
620 | return TRACE_TYPE_PARTIAL_LINE; | |
621 | } | |
622 | ||
623 | /* Proc */ | |
624 | if (tracer_flags.val & TRACE_GRAPH_PRINT_PROC) { | |
625 | ret = print_graph_proc(s, ent->pid); | |
626 | if (ret == TRACE_TYPE_PARTIAL_LINE) | |
627 | return TRACE_TYPE_PARTIAL_LINE; | |
628 | ||
629 | ret = trace_seq_printf(s, " | "); | |
630 | if (!ret) | |
631 | return TRACE_TYPE_PARTIAL_LINE; | |
632 | } | |
633 | ||
634 | /* No overhead */ | |
9005f3eb FW |
635 | ret = print_graph_overhead(-1, s); |
636 | if (!ret) | |
637 | return TRACE_TYPE_PARTIAL_LINE; | |
638 | ||
639 | /* No time */ | |
640 | if (tracer_flags.val & TRACE_GRAPH_PRINT_DURATION) { | |
641 | ret = trace_seq_printf(s, " | "); | |
1fd8f2a3 FW |
642 | if (!ret) |
643 | return TRACE_TYPE_PARTIAL_LINE; | |
644 | } | |
645 | ||
1fd8f2a3 FW |
646 | /* Indentation */ |
647 | if (trace->depth > 0) | |
648 | for (i = 0; i < (trace->depth + 1) * TRACE_GRAPH_INDENT; i++) { | |
649 | ret = trace_seq_printf(s, " "); | |
650 | if (!ret) | |
651 | return TRACE_TYPE_PARTIAL_LINE; | |
652 | } | |
653 | ||
654 | /* The comment */ | |
655 | ret = trace_seq_printf(s, "/* %s", trace->buf); | |
656 | if (!ret) | |
657 | return TRACE_TYPE_PARTIAL_LINE; | |
658 | ||
412d0bb5 FW |
659 | /* Strip ending newline */ |
660 | if (s->buffer[s->len - 1] == '\n') { | |
661 | s->buffer[s->len - 1] = '\0'; | |
662 | s->len--; | |
663 | } | |
664 | ||
1fd8f2a3 FW |
665 | ret = trace_seq_printf(s, " */\n"); |
666 | if (!ret) | |
667 | return TRACE_TYPE_PARTIAL_LINE; | |
668 | ||
669 | return TRACE_TYPE_HANDLED; | |
670 | } | |
671 | ||
672 | ||
287b6e68 FW |
673 | enum print_line_t |
674 | print_graph_function(struct trace_iterator *iter) | |
675 | { | |
676 | struct trace_seq *s = &iter->seq; | |
677 | struct trace_entry *entry = iter->ent; | |
fb52607a | 678 | |
287b6e68 FW |
679 | switch (entry->type) { |
680 | case TRACE_GRAPH_ENT: { | |
681 | struct ftrace_graph_ent_entry *field; | |
682 | trace_assign_type(field, entry); | |
9005f3eb | 683 | return print_graph_entry(field, s, iter); |
287b6e68 FW |
684 | } |
685 | case TRACE_GRAPH_RET: { | |
686 | struct ftrace_graph_ret_entry *field; | |
687 | trace_assign_type(field, entry); | |
9005f3eb | 688 | return print_graph_return(&field->ret, s, entry, iter); |
287b6e68 | 689 | } |
1fd8f2a3 FW |
690 | case TRACE_PRINT: { |
691 | struct print_entry *field; | |
692 | trace_assign_type(field, entry); | |
693 | return print_graph_comment(field, s, entry, iter); | |
694 | } | |
287b6e68 FW |
695 | default: |
696 | return TRACE_TYPE_UNHANDLED; | |
fb52607a | 697 | } |
fb52607a FW |
698 | } |
699 | ||
decbec38 FW |
700 | static void print_graph_headers(struct seq_file *s) |
701 | { | |
702 | /* 1st line */ | |
703 | seq_printf(s, "# "); | |
9005f3eb FW |
704 | if (tracer_flags.val & TRACE_GRAPH_PRINT_ABS_TIME) |
705 | seq_printf(s, " TIME "); | |
decbec38 | 706 | if (tracer_flags.val & TRACE_GRAPH_PRINT_CPU) |
9005f3eb | 707 | seq_printf(s, "CPU"); |
decbec38 | 708 | if (tracer_flags.val & TRACE_GRAPH_PRINT_PROC) |
9005f3eb FW |
709 | seq_printf(s, " TASK/PID "); |
710 | if (tracer_flags.val & TRACE_GRAPH_PRINT_DURATION) | |
711 | seq_printf(s, " DURATION "); | |
712 | seq_printf(s, " FUNCTION CALLS\n"); | |
decbec38 FW |
713 | |
714 | /* 2nd line */ | |
715 | seq_printf(s, "# "); | |
9005f3eb FW |
716 | if (tracer_flags.val & TRACE_GRAPH_PRINT_ABS_TIME) |
717 | seq_printf(s, " | "); | |
decbec38 | 718 | if (tracer_flags.val & TRACE_GRAPH_PRINT_CPU) |
9005f3eb | 719 | seq_printf(s, "| "); |
decbec38 | 720 | if (tracer_flags.val & TRACE_GRAPH_PRINT_PROC) |
9005f3eb FW |
721 | seq_printf(s, " | | "); |
722 | if (tracer_flags.val & TRACE_GRAPH_PRINT_DURATION) | |
723 | seq_printf(s, " | | "); | |
724 | seq_printf(s, " | | | |\n"); | |
decbec38 | 725 | } |
9005f3eb FW |
726 | |
727 | static void graph_trace_open(struct trace_iterator *iter) | |
728 | { | |
729 | /* pid on the last trace processed */ | |
730 | pid_t *last_pid = alloc_percpu(pid_t); | |
731 | int cpu; | |
732 | ||
733 | if (!last_pid) | |
734 | pr_warning("function graph tracer: not enough memory\n"); | |
735 | else | |
736 | for_each_possible_cpu(cpu) { | |
737 | pid_t *pid = per_cpu_ptr(last_pid, cpu); | |
738 | *pid = -1; | |
739 | } | |
740 | ||
741 | iter->private = last_pid; | |
742 | } | |
743 | ||
744 | static void graph_trace_close(struct trace_iterator *iter) | |
745 | { | |
746 | percpu_free(iter->private); | |
747 | } | |
748 | ||
fb52607a | 749 | static struct tracer graph_trace __read_mostly = { |
decbec38 | 750 | .name = "function_graph", |
9005f3eb FW |
751 | .open = graph_trace_open, |
752 | .close = graph_trace_close, | |
decbec38 FW |
753 | .init = graph_trace_init, |
754 | .reset = graph_trace_reset, | |
755 | .print_line = print_graph_function, | |
756 | .print_header = print_graph_headers, | |
fb52607a FW |
757 | .flags = &tracer_flags, |
758 | }; | |
759 | ||
760 | static __init int init_graph_trace(void) | |
761 | { | |
762 | return register_tracer(&graph_trace); | |
763 | } | |
764 | ||
765 | device_initcall(init_graph_trace); |