tracing: Add TRACE_DEFINE_ENUM() macro to map enums to their values
authorSteven Rostedt (Red Hat) <rostedt@goodmis.org>
Tue, 24 Mar 2015 21:58:09 +0000 (17:58 -0400)
committerSteven Rostedt <rostedt@goodmis.org>
Wed, 8 Apr 2015 13:39:56 +0000 (09:39 -0400)
Several tracepoints use the helper functions __print_symbolic() or
__print_flags() and pass in enums that do the mapping between the
binary data stored and the value to print. This works well for reading
the ASCII trace files, but when the data is read via userspace tools
such as perf and trace-cmd, the conversion of the binary value to a
human string format is lost if an enum is used, as userspace does not
have access to what the ENUM is.

For example, the tracepoint trace_tlb_flush() has:

 __print_symbolic(REC->reason,
    { TLB_FLUSH_ON_TASK_SWITCH, "flush on task switch" },
    { TLB_REMOTE_SHOOTDOWN, "remote shootdown" },
    { TLB_LOCAL_SHOOTDOWN, "local shootdown" },
    { TLB_LOCAL_MM_SHOOTDOWN, "local mm shootdown" })

Which maps the enum values to the strings they represent. But perf and
trace-cmd do no know what value TLB_LOCAL_MM_SHOOTDOWN is, and would
not be able to map it.

With TRACE_DEFINE_ENUM(), developers can place these in the event header
files and ftrace will convert the enums to their values:

By adding:

 TRACE_DEFINE_ENUM(TLB_FLUSH_ON_TASK_SWITCH);
 TRACE_DEFINE_ENUM(TLB_REMOTE_SHOOTDOWN);
 TRACE_DEFINE_ENUM(TLB_LOCAL_SHOOTDOWN);
 TRACE_DEFINE_ENUM(TLB_LOCAL_MM_SHOOTDOWN);

 $ cat /sys/kernel/debug/tracing/events/tlb/tlb_flush/format
[...]
 __print_symbolic(REC->reason,
    { 0, "flush on task switch" },
    { 1, "remote shootdown" },
    { 2, "local shootdown" },
    { 3, "local mm shootdown" })

The above is what userspace expects to see, and tools do not need to
be modified to parse them.

Link: http://lkml.kernel.org/r/20150403013802.220157513@goodmis.org
Cc: Guilherme Cox <cox@computer.org>
Cc: Tony Luck <tony.luck@gmail.com>
Cc: Xie XiuQi <xiexiuqi@huawei.com>
Acked-by: Namhyung Kim <namhyung@kernel.org>
Reviewed-by: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Tested-by: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
include/asm-generic/vmlinux.lds.h
include/linux/ftrace_event.h
include/linux/tracepoint.h
include/trace/ftrace.h
kernel/trace/trace.c
kernel/trace/trace.h
kernel/trace/trace_events.c

index ac78910d7416c5f7f2a68cfbde0d950d58ab0d9c..f8e8b34dc4274ee8fd105565fd8d731d3615e109 100644 (file)
 #define FTRACE_EVENTS()        . = ALIGN(8);                                   \
                        VMLINUX_SYMBOL(__start_ftrace_events) = .;      \
                        *(_ftrace_events)                               \
-                       VMLINUX_SYMBOL(__stop_ftrace_events) = .;
+                       VMLINUX_SYMBOL(__stop_ftrace_events) = .;       \
+                       VMLINUX_SYMBOL(__start_ftrace_enum_maps) = .;   \
+                       *(_ftrace_enum_map)                             \
+                       VMLINUX_SYMBOL(__stop_ftrace_enum_maps) = .;
 #else
 #define FTRACE_EVENTS()
 #endif
index 62b8fac7ded5bb359d7cc369af15cdddfef9935e..112cf49d95767d22ac247865f0873f636fce3ab1 100644 (file)
@@ -285,7 +285,7 @@ struct ftrace_event_call {
                struct tracepoint       *tp;
        };
        struct trace_event      event;
-       const char              *print_fmt;
+       char                    *print_fmt;
        struct event_filter     *filter;
        void                    *mod;
        void                    *data;
index c72851328ca9cc071620cfdef3892ca91db6d96a..a5f7f3ecafa3a77123531ca2a07a01b086906db0 100644 (file)
@@ -36,6 +36,12 @@ struct tracepoint {
        struct tracepoint_func __rcu *funcs;
 };
 
+struct trace_enum_map {
+       const char              *system;
+       const char              *enum_string;
+       unsigned long           enum_value;
+};
+
 extern int
 tracepoint_probe_register(struct tracepoint *tp, void *probe, void *data);
 extern int
@@ -87,6 +93,8 @@ extern void syscall_unregfunc(void);
 
 #define PARAMS(args...) args
 
+#define TRACE_DEFINE_ENUM(x)
+
 #endif /* _LINUX_TRACEPOINT_H */
 
 /*
index 2f9b95b6d3fb92f7cc150dadcba34ce6f7a754a6..37d4b10b111d816fe6a88517521beffac2472b80 100644 (file)
 
 TRACE_MAKE_SYSTEM_STR();
 
+#undef TRACE_DEFINE_ENUM
+#define TRACE_DEFINE_ENUM(a)                           \
+       static struct trace_enum_map __used __initdata  \
+       __##TRACE_SYSTEM##_##a =                        \
+       {                                               \
+               .system = TRACE_SYSTEM_STRING,          \
+               .enum_string = #a,                      \
+               .enum_value = a                         \
+       };                                              \
+       static struct trace_enum_map __used             \
+       __attribute__((section("_ftrace_enum_map")))    \
+       *TRACE_SYSTEM##_##a = &__##TRACE_SYSTEM##_##a
+
 /*
  * DECLARE_EVENT_CLASS can be used to add a generic function
  * handlers for events. That is, if all events have the same
@@ -136,6 +149,9 @@ TRACE_MAKE_SYSTEM_STR();
  * The size of an array is also encoded, in the higher 16 bits of <item>.
  */
 
+#undef TRACE_DEFINE_ENUM
+#define TRACE_DEFINE_ENUM(a)
+
 #undef __field
 #define __field(type, item)
 
@@ -553,7 +569,7 @@ static inline notrace int ftrace_get_offsets_##call(                        \
  *     .trace                  = ftrace_raw_output_<call>, <-- stage 2
  * };
  *
- * static const char print_fmt_<call>[] = <TP_printk>;
+ * static char print_fmt_<call>[] = <TP_printk>;
  *
  * static struct ftrace_event_class __used event_class_<template> = {
  *     .system                 = "<system>",
@@ -704,7 +720,7 @@ static inline void ftrace_test_probe_##call(void)                   \
 #undef DECLARE_EVENT_CLASS
 #define DECLARE_EVENT_CLASS(call, proto, args, tstruct, assign, print) \
 _TRACE_PERF_PROTO(call, PARAMS(proto));                                        \
-static const char print_fmt_##call[] = print;                          \
+static char print_fmt_##call[] = print;                                        \
 static struct ftrace_event_class __used __refdata event_class_##call = { \
        .system                 = TRACE_SYSTEM_STRING,                  \
        .define_fields          = ftrace_define_fields_##call,          \
@@ -733,7 +749,7 @@ __attribute__((section("_ftrace_events"))) *__event_##call = &event_##call
 #undef DEFINE_EVENT_PRINT
 #define DEFINE_EVENT_PRINT(template, call, proto, args, print)         \
                                                                        \
-static const char print_fmt_##call[] = print;                          \
+static char print_fmt_##call[] = print;                                        \
                                                                        \
 static struct ftrace_event_call __used event_##call = {                        \
        .class                  = &event_class_##template,              \
index 62c6506d663fa11e79128759bd03103d12ed7c0f..ebf49649534c6854b27ad95ceee44e86ce34b1d0 100644 (file)
@@ -3908,6 +3908,20 @@ static const struct file_operations tracing_saved_cmdlines_size_fops = {
        .write          = tracing_saved_cmdlines_size_write,
 };
 
+static void
+trace_insert_enum_map(struct trace_enum_map **start, struct trace_enum_map **stop)
+{
+       struct trace_enum_map **map;
+       int len = stop - start;
+
+       if (len <= 0)
+               return;
+
+       map = start;
+
+       trace_event_enum_update(map, len);
+}
+
 static ssize_t
 tracing_set_trace_read(struct file *filp, char __user *ubuf,
                       size_t cnt, loff_t *ppos)
@@ -6542,6 +6556,14 @@ struct dentry *tracing_init_dentry(void)
        return tr->dir;
 }
 
+extern struct trace_enum_map *__start_ftrace_enum_maps[];
+extern struct trace_enum_map *__stop_ftrace_enum_maps[];
+
+static void __init trace_enum_init(void)
+{
+       trace_insert_enum_map(__start_ftrace_enum_maps, __stop_ftrace_enum_maps);
+}
+
 static __init int tracer_init_debugfs(void)
 {
        struct dentry *d_tracer;
@@ -6566,6 +6588,8 @@ static __init int tracer_init_debugfs(void)
        trace_create_file("saved_cmdlines_size", 0644, d_tracer,
                          NULL, &tracing_saved_cmdlines_size_fops);
 
+       trace_enum_init();
+
 #ifdef CONFIG_DYNAMIC_FTRACE
        trace_create_file("dyn_ftrace_total_info", 0444, d_tracer,
                        &ftrace_update_tot_cnt, &tracing_dyn_info_fops);
@@ -6888,7 +6912,7 @@ void __init trace_init(void)
                        tracepoint_printk = 0;
        }
        tracer_alloc_buffers();
-       trace_event_init();     
+       trace_event_init();
 }
 
 __init static int clear_boot_tracer(void)
index dd8205a35760d7a4b8bf53543f52abd0f1f9a6aa..b48d4b08f69101af10381766f4897fc6c390ec2b 100644 (file)
@@ -1309,8 +1309,10 @@ static inline void init_ftrace_syscalls(void) { }
 
 #ifdef CONFIG_EVENT_TRACING
 void trace_event_init(void);
+void trace_event_enum_update(struct trace_enum_map **map, int len);
 #else
 static inline void __init trace_event_init(void) { }
+static inlin void trace_event_enum_update(struct trace_enum_map **map, int len) { }
 #endif
 
 extern struct trace_iterator *tracepoint_print_iter;
index db54dda10ccc179f733db8fd8407cc402c11e409..fc58c50fbf0141eceadf90a56a6e731e25314fec 100644 (file)
@@ -1704,6 +1704,125 @@ __register_event(struct ftrace_event_call *call, struct module *mod)
        return 0;
 }
 
+static char *enum_replace(char *ptr, struct trace_enum_map *map, int len)
+{
+       int rlen;
+       int elen;
+
+       /* Find the length of the enum value as a string */
+       elen = snprintf(ptr, 0, "%ld", map->enum_value);
+       /* Make sure there's enough room to replace the string with the value */
+       if (len < elen)
+               return NULL;
+
+       snprintf(ptr, elen + 1, "%ld", map->enum_value);
+
+       /* Get the rest of the string of ptr */
+       rlen = strlen(ptr + len);
+       memmove(ptr + elen, ptr + len, rlen);
+       /* Make sure we end the new string */
+       ptr[elen + rlen] = 0;
+
+       return ptr + elen;
+}
+
+static void update_event_printk(struct ftrace_event_call *call,
+                               struct trace_enum_map *map)
+{
+       char *ptr;
+       int quote = 0;
+       int len = strlen(map->enum_string);
+
+       for (ptr = call->print_fmt; *ptr; ptr++) {
+               if (*ptr == '\\') {
+                       ptr++;
+                       /* paranoid */
+                       if (!*ptr)
+                               break;
+                       continue;
+               }
+               if (*ptr == '"') {
+                       quote ^= 1;
+                       continue;
+               }
+               if (quote)
+                       continue;
+               if (isdigit(*ptr)) {
+                       /* skip numbers */
+                       do {
+                               ptr++;
+                               /* Check for alpha chars like ULL */
+                       } while (isalnum(*ptr));
+                       /*
+                        * A number must have some kind of delimiter after
+                        * it, and we can ignore that too.
+                        */
+                       continue;
+               }
+               if (isalpha(*ptr) || *ptr == '_') {
+                       if (strncmp(map->enum_string, ptr, len) == 0 &&
+                           !isalnum(ptr[len]) && ptr[len] != '_') {
+                               ptr = enum_replace(ptr, map, len);
+                               /* Hmm, enum string smaller than value */
+                               if (WARN_ON_ONCE(!ptr))
+                                       return;
+                               /*
+                                * No need to decrement here, as enum_replace()
+                                * returns the pointer to the character passed
+                                * the enum, and two enums can not be placed
+                                * back to back without something in between.
+                                * We can skip that something in between.
+                                */
+                               continue;
+                       }
+               skip_more:
+                       do {
+                               ptr++;
+                       } while (isalnum(*ptr) || *ptr == '_');
+                       /*
+                        * If what comes after this variable is a '.' or
+                        * '->' then we can continue to ignore that string.
+                        */
+                       if (*ptr == '.' || (ptr[0] == '-' && ptr[1] == '>')) {
+                               ptr += *ptr == '.' ? 1 : 2;
+                               goto skip_more;
+                       }
+                       /*
+                        * Once again, we can skip the delimiter that came
+                        * after the string.
+                        */
+                       continue;
+               }
+       }
+}
+
+void trace_event_enum_update(struct trace_enum_map **map, int len)
+{
+       struct ftrace_event_call *call, *p;
+       const char *last_system = NULL;
+       int last_i;
+       int i;
+
+       down_write(&trace_event_sem);
+       list_for_each_entry_safe(call, p, &ftrace_events, list) {
+               /* events are usually grouped together with systems */
+               if (!last_system || call->class->system != last_system) {
+                       last_i = 0;
+                       last_system = call->class->system;
+               }
+
+               for (i = last_i; i < len; i++) {
+                       if (call->class->system == map[i]->system) {
+                               /* Save the first system if need be */
+                               if (!last_i)
+                                       last_i = i;
+                               update_event_printk(call, map[i]);
+                       }
+               }
+       }
+       up_write(&trace_event_sem);
+}
+
 static struct ftrace_event_file *
 trace_create_new_event(struct ftrace_event_call *call,
                       struct trace_array *tr)