tracing: Buffer the output of seq_file in case of filled buffer
authorSteven Rostedt <srostedt@redhat.com>
Mon, 7 Dec 2009 14:11:39 +0000 (09:11 -0500)
committerSteven Rostedt <rostedt@goodmis.org>
Wed, 9 Dec 2009 18:55:26 +0000 (13:55 -0500)
If the seq_read fills the buffer it will call s_start again on the next
itertation with the same position. This causes a problem with the
function_graph tracer because it consumes the iteration in order to
determine leaf functions.

What happens is that the iterator stores the entry, and the function
graph plugin will look at the next entry. If that next entry is a return
of the same function and task, then the function is a leaf and the
function_graph plugin calls ring_buffer_read which moves the ring buffer
iterator forward (the trace iterator still points to the function start
entry).

The copying of the trace_seq to the seq_file buffer will fail if the
seq_file buffer is full. The seq_read will not show this entry.
The next read by userspace will cause seq_read to again call s_start
which will reuse the trace iterator entry (the function start entry).
But the function return entry was already consumed. The function graph
plugin will think that this entry is a nested function and not a leaf.

To solve this, the trace code now checks the return status of the
seq_printf (trace_print_seq). If the writing to the seq_file buffer
fails, we set a flag in the iterator (leftover) and we do not reset
the trace_seq buffer. On the next call to s_start, we check the leftover
flag, and if it is set, we just reuse the trace_seq buffer and do not
call into the plugin print functions.

Before this patch:

 2)               |      fput() {
 2)               |        __fput() {
 2)   0.550 us    |          inotify_inode_queue_event();
 2)               |          __fsnotify_parent() {
 2)   0.540 us    |          inotify_dentry_parent_queue_event();

After the patch:

 2)               |      fput() {
 2)               |        __fput() {
 2)   0.550 us    |          inotify_inode_queue_event();
 2)   0.548 us    |          __fsnotify_parent();
 2)   0.540 us    |          inotify_dentry_parent_queue_event();

[
  Updated the patch to fix a missing return 0 from the trace_print_seq()
  stub when CONFIG_TRACING is disabled.

Reported-by: Ingo Molnar <mingo@elte.hu>
]

Reported-by: Jiri Olsa <jolsa@redhat.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
include/linux/ftrace_event.h
include/linux/trace_seq.h
kernel/trace/trace.c
kernel/trace/trace_output.c

index 47bbdf9c38d0428328e48d9c772cdc3086375e5c..38f8d6553831a6a9ee2839ac56071b795700d917 100644 (file)
@@ -57,6 +57,7 @@ struct trace_iterator {
        /* The below is zeroed out in pipe_read */
        struct trace_seq        seq;
        struct trace_entry      *ent;
+       int                     leftover;
        int                     cpu;
        u64                     ts;
 
index 09077f6ed12850cd43a44e26926a3f7b13fcacc7..fad6857bc0f3b0e0a8ee78a984f774d76dd71752 100644 (file)
@@ -33,7 +33,7 @@ extern int trace_seq_vprintf(struct trace_seq *s, const char *fmt, va_list args)
        __attribute__ ((format (printf, 2, 0)));
 extern int
 trace_seq_bprintf(struct trace_seq *s, const char *fmt, const u32 *binary);
-extern void trace_print_seq(struct seq_file *m, struct trace_seq *s);
+extern int trace_print_seq(struct seq_file *m, struct trace_seq *s);
 extern ssize_t trace_seq_to_user(struct trace_seq *s, char __user *ubuf,
                                 size_t cnt);
 extern int trace_seq_puts(struct trace_seq *s, const char *str);
@@ -55,8 +55,9 @@ trace_seq_bprintf(struct trace_seq *s, const char *fmt, const u32 *binary)
        return 0;
 }
 
-static inline void trace_print_seq(struct seq_file *m, struct trace_seq *s)
+static inline int trace_print_seq(struct seq_file *m, struct trace_seq *s)
 {
+       return 0;
 }
 static inline ssize_t trace_seq_to_user(struct trace_seq *s, char __user *ubuf,
                                 size_t cnt)
index dc937e1baa91a27bb7aa2d89cf70b6e9f2213dfb..484114d70743f4b5936b99fc51bda434caa16605 100644 (file)
@@ -1516,6 +1516,8 @@ static void *s_next(struct seq_file *m, void *v, loff_t *pos)
        int i = (int)*pos;
        void *ent;
 
+       WARN_ON_ONCE(iter->leftover);
+
        (*pos)++;
 
        /* can't go backwards */
@@ -1614,8 +1616,16 @@ static void *s_start(struct seq_file *m, loff_t *pos)
                        ;
 
        } else {
-               l = *pos - 1;
-               p = s_next(m, p, &l);
+               /*
+                * If we overflowed the seq_file before, then we want
+                * to just reuse the trace_seq buffer again.
+                */
+               if (iter->leftover)
+                       p = iter;
+               else {
+                       l = *pos - 1;
+                       p = s_next(m, p, &l);
+               }
        }
 
        trace_event_read_lock();
@@ -1923,6 +1933,7 @@ static enum print_line_t print_trace_line(struct trace_iterator *iter)
 static int s_show(struct seq_file *m, void *v)
 {
        struct trace_iterator *iter = v;
+       int ret;
 
        if (iter->ent == NULL) {
                if (iter->tr) {
@@ -1942,9 +1953,27 @@ static int s_show(struct seq_file *m, void *v)
                        if (!(trace_flags & TRACE_ITER_VERBOSE))
                                print_func_help_header(m);
                }
+       } else if (iter->leftover) {
+               /*
+                * If we filled the seq_file buffer earlier, we
+                * want to just show it now.
+                */
+               ret = trace_print_seq(m, &iter->seq);
+
+               /* ret should this time be zero, but you never know */
+               iter->leftover = ret;
+
        } else {
                print_trace_line(iter);
-               trace_print_seq(m, &iter->seq);
+               ret = trace_print_seq(m, &iter->seq);
+               /*
+                * If we overflow the seq_file buffer, then it will
+                * ask us for this data again at start up.
+                * Use that instead.
+                *  ret is 0 if seq_file write succeeded.
+                *        -1 otherwise.
+                */
+               iter->leftover = ret;
        }
 
        return 0;
index b6c12c6a1bcde267a7e25d6b1b8df41c559f1500..e5cf90fef34e928546b962a2afa5927e0f5964d4 100644 (file)
@@ -23,13 +23,21 @@ static struct hlist_head event_hash[EVENT_HASHSIZE] __read_mostly;
 
 static int next_event_type = __TRACE_LAST_TYPE + 1;
 
-void trace_print_seq(struct seq_file *m, struct trace_seq *s)
+int trace_print_seq(struct seq_file *m, struct trace_seq *s)
 {
        int len = s->len >= PAGE_SIZE ? PAGE_SIZE - 1 : s->len;
+       int ret;
+
+       ret = seq_write(m, s->buffer, len);
 
-       seq_write(m, s->buffer, len);
+       /*
+        * Only reset this buffer if we successfully wrote to the
+        * seq_file buffer.
+        */
+       if (!ret)
+               trace_seq_init(s);
 
-       trace_seq_init(s);
+       return ret;
 }
 
 enum print_line_t trace_print_bprintk_msg_only(struct trace_iterator *iter)