]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
perf, bpf: Introduce PERF_RECORD_KSYMBOL
authorSong Liu <songliubraving@fb.com>
Thu, 17 Jan 2019 16:15:13 +0000 (08:15 -0800)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Mon, 21 Jan 2019 20:00:57 +0000 (17:00 -0300)
For better performance analysis of dynamically JITed and loaded kernel
functions, such as BPF programs, this patch introduces
PERF_RECORD_KSYMBOL, a new perf_event_type that exposes kernel symbol
register/unregister information to user space.

The following data structure is used for PERF_RECORD_KSYMBOL.

    /*
     * struct {
     *      struct perf_event_header        header;
     *      u64                             addr;
     *      u32                             len;
     *      u16                             ksym_type;
     *      u16                             flags;
     *      char                            name[];
     *      struct sample_id                sample_id;
     * };
     */

Signed-off-by: Song Liu <songliubraving@fb.com>
Reviewed-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Acked-by: Peter Zijlstra <peterz@infradead.org>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: Daniel Borkmann <daniel@iogearbox.net>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: kernel-team@fb.com
Cc: netdev@vger.kernel.org
Link: http://lkml.kernel.org/r/20190117161521.1341602-2-songliubraving@fb.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
include/linux/perf_event.h
include/uapi/linux/perf_event.h
kernel/events/core.c

index 4eb88065a9b54a2fd6c1c435fe1f26d59c35f271..136fe04953748aa9760d3f8f873db3117e87a9cf 100644 (file)
@@ -1122,6 +1122,10 @@ static inline void perf_event_task_sched_out(struct task_struct *prev,
 }
 
 extern void perf_event_mmap(struct vm_area_struct *vma);
+
+extern void perf_event_ksymbol(u16 ksym_type, u64 addr, u32 len,
+                              bool unregister, const char *sym);
+
 extern struct perf_guest_info_callbacks *perf_guest_cbs;
 extern int perf_register_guest_info_callbacks(struct perf_guest_info_callbacks *callbacks);
 extern int perf_unregister_guest_info_callbacks(struct perf_guest_info_callbacks *callbacks);
@@ -1342,6 +1346,10 @@ static inline int perf_unregister_guest_info_callbacks
 (struct perf_guest_info_callbacks *callbacks)                          { return 0; }
 
 static inline void perf_event_mmap(struct vm_area_struct *vma)         { }
+
+typedef int (perf_ksymbol_get_name_f)(char *name, int name_len, void *data);
+static inline void perf_event_ksymbol(u16 ksym_type, u64 addr, u32 len,
+                                     bool unregister, const char *sym) { }
 static inline void perf_event_exec(void)                               { }
 static inline void perf_event_comm(struct task_struct *tsk, bool exec) { }
 static inline void perf_event_namespaces(struct task_struct *tsk)      { }
index ea19b5d491bfe9031249553877c69a288a45b3fd..1dee5c8f166b0f3e751152ac6e81798eb8eaf0be 100644 (file)
@@ -372,7 +372,8 @@ struct perf_event_attr {
                                context_switch :  1, /* context switch data */
                                write_backward :  1, /* Write ring buffer from end to beginning */
                                namespaces     :  1, /* include namespaces data */
-                               __reserved_1   : 35;
+                               ksymbol        :  1, /* include ksymbol events */
+                               __reserved_1   : 34;
 
        union {
                __u32           wakeup_events;    /* wakeup every n events */
@@ -963,9 +964,32 @@ enum perf_event_type {
         */
        PERF_RECORD_NAMESPACES                  = 16,
 
+       /*
+        * Record ksymbol register/unregister events:
+        *
+        * struct {
+        *      struct perf_event_header        header;
+        *      u64                             addr;
+        *      u32                             len;
+        *      u16                             ksym_type;
+        *      u16                             flags;
+        *      char                            name[];
+        *      struct sample_id                sample_id;
+        * };
+        */
+       PERF_RECORD_KSYMBOL                     = 17,
+
        PERF_RECORD_MAX,                        /* non-ABI */
 };
 
+enum perf_record_ksymbol_type {
+       PERF_RECORD_KSYMBOL_TYPE_UNKNOWN        = 0,
+       PERF_RECORD_KSYMBOL_TYPE_BPF            = 1,
+       PERF_RECORD_KSYMBOL_TYPE_MAX            /* non-ABI */
+};
+
+#define PERF_RECORD_KSYMBOL_FLAGS_UNREGISTER   (1 << 0)
+
 #define PERF_MAX_STACK_DEPTH           127
 #define PERF_MAX_CONTEXTS_PER_STACK      8
 
index bc525cd1615c48fb611899575834ebeac8033259..e04ab5f325cf3ae4b6293d0e3c4a809e4b78eb84 100644 (file)
@@ -385,6 +385,7 @@ static atomic_t nr_namespaces_events __read_mostly;
 static atomic_t nr_task_events __read_mostly;
 static atomic_t nr_freq_events __read_mostly;
 static atomic_t nr_switch_events __read_mostly;
+static atomic_t nr_ksymbol_events __read_mostly;
 
 static LIST_HEAD(pmus);
 static DEFINE_MUTEX(pmus_lock);
@@ -4235,7 +4236,7 @@ static bool is_sb_event(struct perf_event *event)
 
        if (attr->mmap || attr->mmap_data || attr->mmap2 ||
            attr->comm || attr->comm_exec ||
-           attr->task ||
+           attr->task || attr->ksymbol ||
            attr->context_switch)
                return true;
        return false;
@@ -4305,6 +4306,8 @@ static void unaccount_event(struct perf_event *event)
                dec = true;
        if (has_branch_stack(event))
                dec = true;
+       if (event->attr.ksymbol)
+               atomic_dec(&nr_ksymbol_events);
 
        if (dec) {
                if (!atomic_add_unless(&perf_sched_count, -1, 1))
@@ -7653,6 +7656,97 @@ static void perf_log_throttle(struct perf_event *event, int enable)
        perf_output_end(&handle);
 }
 
+/*
+ * ksymbol register/unregister tracking
+ */
+
+struct perf_ksymbol_event {
+       const char      *name;
+       int             name_len;
+       struct {
+               struct perf_event_header        header;
+               u64                             addr;
+               u32                             len;
+               u16                             ksym_type;
+               u16                             flags;
+       } event_id;
+};
+
+static int perf_event_ksymbol_match(struct perf_event *event)
+{
+       return event->attr.ksymbol;
+}
+
+static void perf_event_ksymbol_output(struct perf_event *event, void *data)
+{
+       struct perf_ksymbol_event *ksymbol_event = data;
+       struct perf_output_handle handle;
+       struct perf_sample_data sample;
+       int ret;
+
+       if (!perf_event_ksymbol_match(event))
+               return;
+
+       perf_event_header__init_id(&ksymbol_event->event_id.header,
+                                  &sample, event);
+       ret = perf_output_begin(&handle, event,
+                               ksymbol_event->event_id.header.size);
+       if (ret)
+               return;
+
+       perf_output_put(&handle, ksymbol_event->event_id);
+       __output_copy(&handle, ksymbol_event->name, ksymbol_event->name_len);
+       perf_event__output_id_sample(event, &handle, &sample);
+
+       perf_output_end(&handle);
+}
+
+void perf_event_ksymbol(u16 ksym_type, u64 addr, u32 len, bool unregister,
+                       const char *sym)
+{
+       struct perf_ksymbol_event ksymbol_event;
+       char name[KSYM_NAME_LEN];
+       u16 flags = 0;
+       int name_len;
+
+       if (!atomic_read(&nr_ksymbol_events))
+               return;
+
+       if (ksym_type >= PERF_RECORD_KSYMBOL_TYPE_MAX ||
+           ksym_type == PERF_RECORD_KSYMBOL_TYPE_UNKNOWN)
+               goto err;
+
+       strlcpy(name, sym, KSYM_NAME_LEN);
+       name_len = strlen(name) + 1;
+       while (!IS_ALIGNED(name_len, sizeof(u64)))
+               name[name_len++] = '\0';
+       BUILD_BUG_ON(KSYM_NAME_LEN % sizeof(u64));
+
+       if (unregister)
+               flags |= PERF_RECORD_KSYMBOL_FLAGS_UNREGISTER;
+
+       ksymbol_event = (struct perf_ksymbol_event){
+               .name = name,
+               .name_len = name_len,
+               .event_id = {
+                       .header = {
+                               .type = PERF_RECORD_KSYMBOL,
+                               .size = sizeof(ksymbol_event.event_id) +
+                                       name_len,
+                       },
+                       .addr = addr,
+                       .len = len,
+                       .ksym_type = ksym_type,
+                       .flags = flags,
+               },
+       };
+
+       perf_iterate_sb(perf_event_ksymbol_output, &ksymbol_event, NULL);
+       return;
+err:
+       WARN_ONCE(1, "%s: Invalid KSYMBOL type 0x%x\n", __func__, ksym_type);
+}
+
 void perf_event_itrace_started(struct perf_event *event)
 {
        event->attach_state |= PERF_ATTACH_ITRACE;
@@ -9912,6 +10006,8 @@ static void account_event(struct perf_event *event)
                inc = true;
        if (is_cgroup_event(event))
                inc = true;
+       if (event->attr.ksymbol)
+               atomic_inc(&nr_ksymbol_events);
 
        if (inc) {
                /*