tracing: Add a way to soft disable trace events
authorSteven Rostedt (Red Hat) <rostedt@goodmis.org>
Tue, 12 Mar 2013 17:26:18 +0000 (13:26 -0400)
committerSteven Rostedt <rostedt@goodmis.org>
Fri, 15 Mar 2013 04:36:03 +0000 (00:36 -0400)
In order to let triggers enable or disable events, we need a 'soft'
method for doing so. For example, if a function probe is added that
lets a user enable or disable events when a function is called, that
change must be done without taking locks or a mutex, and definitely
it can't sleep. But the full enabling of a tracepoint is expensive.

By adding a 'SOFT_DISABLE' flag, and converting the flags to be updated
without the protection of a mutex (using set/clear_bit()), this soft
disable flag can be used to allow critical sections to enable or disable
events from being traced (after the event has been placed into "SOFT_MODE").

Some caveats though: The comm recorder (to map pids with a comm) can not
be soft disabled (yet). If you disable an event with with a "soft"
disable and wait a while before reading the trace, the comm cache may be
replaced and you'll get a bunch of <...> for comms in the trace.

Reading the "enable" file for an event that is disabled will now give
you "0*" where the '*' denotes that the tracepoint is still active but
the event itself is "disabled".

[ fixed _BIT used in & operation : thanks to Dan Carpenter and smatch ]

Cc: Dan Carpenter <dan.carpenter@oracle.com>
Cc: Tom Zanussi <tom.zanussi@linux.intel.com>
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
include/linux/ftrace_event.h
include/trace/ftrace.h
kernel/trace/trace_events.c

index 4cb6cd8338a46c5e8530b1b6ff99a49105fdb717..4e28b011e63b18ddd8ff5081120656efaa78179a 100644 (file)
@@ -251,16 +251,23 @@ struct ftrace_subsystem_dir;
 enum {
        FTRACE_EVENT_FL_ENABLED_BIT,
        FTRACE_EVENT_FL_RECORDED_CMD_BIT,
+       FTRACE_EVENT_FL_SOFT_MODE_BIT,
+       FTRACE_EVENT_FL_SOFT_DISABLED_BIT,
 };
 
 /*
  * Ftrace event file flags:
  *  ENABLED      - The event is enabled
  *  RECORDED_CMD  - The comms should be recorded at sched_switch
+ *  SOFT_MODE     - The event is enabled/disabled by SOFT_DISABLED
+ *  SOFT_DISABLED - When set, do not trace the event (even though its
+ *                   tracepoint may be enabled)
  */
 enum {
        FTRACE_EVENT_FL_ENABLED         = (1 << FTRACE_EVENT_FL_ENABLED_BIT),
        FTRACE_EVENT_FL_RECORDED_CMD    = (1 << FTRACE_EVENT_FL_RECORDED_CMD_BIT),
+       FTRACE_EVENT_FL_SOFT_MODE       = (1 << FTRACE_EVENT_FL_SOFT_MODE_BIT),
+       FTRACE_EVENT_FL_SOFT_DISABLED   = (1 << FTRACE_EVENT_FL_SOFT_DISABLED_BIT),
 };
 
 struct ftrace_event_file {
@@ -274,17 +281,18 @@ struct ftrace_event_file {
         * 32 bit flags:
         *   bit 0:             enabled
         *   bit 1:             enabled cmd record
+        *   bit 2:             enable/disable with the soft disable bit
+        *   bit 3:             soft disabled
         *
-        * Changes to flags must hold the event_mutex.
-        *
-        * Note: Reads of flags do not hold the event_mutex since
-        * they occur in critical sections. But the way flags
+        * Note: The bits must be set atomically to prevent races
+        * from other writers. Reads of flags do not need to be in
+        * sync as they occur in critical sections. But the way flags
         * is currently used, these changes do not affect the code
         * except that when a change is made, it may have a slight
         * delay in propagating the changes to other CPUs due to
-        * caching and such.
+        * caching and such. Which is mostly OK ;-)
         */
-       unsigned int            flags;
+       unsigned long           flags;
 };
 
 #define __TRACE_EVENT_FLAGS(name, value)                               \
index bbf09c2021b9aae3475e6d5dbb8178c2c97f1675..4bda044e6c77fd05c2f879fb187f4b4e78d90c11 100644 (file)
@@ -413,6 +413,10 @@ static inline notrace int ftrace_get_offsets_##call(                       \
  *     int __data_size;
  *     int pc;
  *
+ *     if (test_bit(FTRACE_EVENT_FL_SOFT_DISABLED_BIT,
+ *                  &ftrace_file->flags))
+ *             return;
+ *
  *     local_save_flags(irq_flags);
  *     pc = preempt_count();
  *
@@ -518,6 +522,10 @@ ftrace_raw_event_##call(void *__data, proto)                               \
        int __data_size;                                                \
        int pc;                                                         \
                                                                        \
+       if (test_bit(FTRACE_EVENT_FL_SOFT_DISABLED_BIT,                 \
+                    &ftrace_file->flags))                              \
+               return;                                                 \
+                                                                       \
        local_save_flags(irq_flags);                                    \
        pc = preempt_count();                                           \
                                                                        \
index 38b54c5edeb9c0d2643af73432c1cff3c4e92797..106640b0df4aa9001b1d63d7a5b67c1e557ae44a 100644 (file)
@@ -205,37 +205,77 @@ void trace_event_enable_cmd_record(bool enable)
 
                if (enable) {
                        tracing_start_cmdline_record();
-                       file->flags |= FTRACE_EVENT_FL_RECORDED_CMD;
+                       set_bit(FTRACE_EVENT_FL_RECORDED_CMD_BIT, &file->flags);
                } else {
                        tracing_stop_cmdline_record();
-                       file->flags &= ~FTRACE_EVENT_FL_RECORDED_CMD;
+                       clear_bit(FTRACE_EVENT_FL_RECORDED_CMD_BIT, &file->flags);
                }
        } while_for_each_event_file();
        mutex_unlock(&event_mutex);
 }
 
-static int ftrace_event_enable_disable(struct ftrace_event_file *file,
-                                      int enable)
+static int __ftrace_event_enable_disable(struct ftrace_event_file *file,
+                                        int enable, int soft_disable)
 {
        struct ftrace_event_call *call = file->event_call;
        int ret = 0;
+       int disable;
 
        switch (enable) {
        case 0:
-               if (file->flags & FTRACE_EVENT_FL_ENABLED) {
-                       file->flags &= ~FTRACE_EVENT_FL_ENABLED;
+               /*
+                * When soft_disable is set and enable is cleared, we want
+                * to clear the SOFT_DISABLED flag but leave the event in the
+                * state that it was. That is, if the event was enabled and
+                * SOFT_DISABLED isn't set, then do nothing. But if SOFT_DISABLED
+                * is set we do not want the event to be enabled before we
+                * clear the bit.
+                *
+                * When soft_disable is not set but the SOFT_MODE flag is,
+                * we do nothing. Do not disable the tracepoint, otherwise
+                * "soft enable"s (clearing the SOFT_DISABLED bit) wont work.
+                */
+               if (soft_disable) {
+                       disable = file->flags & FTRACE_EVENT_FL_SOFT_DISABLED;
+                       clear_bit(FTRACE_EVENT_FL_SOFT_MODE_BIT, &file->flags);
+               } else
+                       disable = !(file->flags & FTRACE_EVENT_FL_SOFT_MODE);
+
+               if (disable && (file->flags & FTRACE_EVENT_FL_ENABLED)) {
+                       clear_bit(FTRACE_EVENT_FL_ENABLED_BIT, &file->flags);
                        if (file->flags & FTRACE_EVENT_FL_RECORDED_CMD) {
                                tracing_stop_cmdline_record();
-                               file->flags &= ~FTRACE_EVENT_FL_RECORDED_CMD;
+                               clear_bit(FTRACE_EVENT_FL_RECORDED_CMD_BIT, &file->flags);
                        }
                        call->class->reg(call, TRACE_REG_UNREGISTER, file);
                }
+               /* If in SOFT_MODE, just set the SOFT_DISABLE_BIT */
+               if (file->flags & FTRACE_EVENT_FL_SOFT_MODE)
+                       set_bit(FTRACE_EVENT_FL_SOFT_DISABLED_BIT, &file->flags);
                break;
        case 1:
+               /*
+                * When soft_disable is set and enable is set, we want to
+                * register the tracepoint for the event, but leave the event
+                * as is. That means, if the event was already enabled, we do
+                * nothing (but set SOFT_MODE). If the event is disabled, we
+                * set SOFT_DISABLED before enabling the event tracepoint, so
+                * it still seems to be disabled.
+                */
+               if (!soft_disable)
+                       clear_bit(FTRACE_EVENT_FL_SOFT_DISABLED_BIT, &file->flags);
+               else
+                       set_bit(FTRACE_EVENT_FL_SOFT_MODE_BIT, &file->flags);
+
                if (!(file->flags & FTRACE_EVENT_FL_ENABLED)) {
+
+                       /* Keep the event disabled, when going to SOFT_MODE. */
+                       if (soft_disable)
+                               set_bit(FTRACE_EVENT_FL_SOFT_DISABLED_BIT, &file->flags);
+
                        if (trace_flags & TRACE_ITER_RECORD_CMD) {
                                tracing_start_cmdline_record();
-                               file->flags |= FTRACE_EVENT_FL_RECORDED_CMD;
+                               set_bit(FTRACE_EVENT_FL_RECORDED_CMD_BIT, &file->flags);
                        }
                        ret = call->class->reg(call, TRACE_REG_REGISTER, file);
                        if (ret) {
@@ -244,7 +284,7 @@ static int ftrace_event_enable_disable(struct ftrace_event_file *file,
                                        "%s\n", call->name);
                                break;
                        }
-                       file->flags |= FTRACE_EVENT_FL_ENABLED;
+                       set_bit(FTRACE_EVENT_FL_ENABLED_BIT, &file->flags);
 
                        /* WAS_ENABLED gets set but never cleared. */
                        call->flags |= TRACE_EVENT_FL_WAS_ENABLED;
@@ -255,6 +295,12 @@ static int ftrace_event_enable_disable(struct ftrace_event_file *file,
        return ret;
 }
 
+static int ftrace_event_enable_disable(struct ftrace_event_file *file,
+                                      int enable)
+{
+       return __ftrace_event_enable_disable(file, enable, 0);
+}
+
 static void ftrace_clear_events(struct trace_array *tr)
 {
        struct ftrace_event_file *file;
@@ -547,12 +593,15 @@ event_enable_read(struct file *filp, char __user *ubuf, size_t cnt,
        struct ftrace_event_file *file = filp->private_data;
        char *buf;
 
-       if (file->flags & FTRACE_EVENT_FL_ENABLED)
-               buf = "1\n";
-       else
+       if (file->flags & FTRACE_EVENT_FL_ENABLED) {
+               if (file->flags & FTRACE_EVENT_FL_SOFT_DISABLED)
+                       buf = "0*\n";
+               else
+                       buf = "1\n";
+       } else
                buf = "0\n";
 
-       return simple_read_from_buffer(ubuf, cnt, ppos, buf, 2);
+       return simple_read_from_buffer(ubuf, cnt, ppos, buf, strlen(buf));
 }
 
 static ssize_t