lkml.org 
[lkml]   [2014]   [Feb]   [6]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[PATCH v1 05/11] itrace: Add functionality to include traces in perf event samples
Date
Trace data from itrace PMUs can be used to annotate other perf events
by including it in sample records when PERF_SAMPLE_ITRACE flag is set. In
this case, a PT kernel counter is created for each such event and trace data
is retrieved from it and stored in the perf data stream.

Signed-off-by: Alexander Shishkin <alexander.shishkin@linux.intel.com>
---
include/linux/itrace.h | 37 +++++++++
include/linux/perf_event.h | 15 ++++
include/uapi/linux/perf_event.h | 5 +-
kernel/events/core.c | 35 +++++++++
kernel/events/itrace.c | 169 ++++++++++++++++++++++++++++++++++++++--
5 files changed, 252 insertions(+), 9 deletions(-)

diff --git a/include/linux/itrace.h b/include/linux/itrace.h
index 735baaf4..6adbb32 100644
--- a/include/linux/itrace.h
+++ b/include/linux/itrace.h
@@ -54,12 +54,27 @@ struct itrace_pmu {

int (*event_init)(struct perf_event *event);

+ /*
+ * Calculate the size of a sample to be written out
+ */
+ unsigned long (*sample_trace)(struct perf_event *event,
+ struct perf_sample_data *data);
+
+ /*
+ * Write out a trace sample to the given output handle
+ */
+ void (*sample_output)(struct perf_event *event,
+ struct perf_output_handle *handle,
+ struct perf_sample_data *data);
char *name;
};

#define to_itrace_pmu(x) container_of((x), struct itrace_pmu, pmu)

#ifdef CONFIG_PERF_EVENTS
+
+extern int itrace_kernel_event(struct perf_event *event,
+ struct task_struct *task);
extern int itrace_inherit_event(struct perf_event *event,
struct task_struct *task);
extern void itrace_lost_data(struct perf_event *event, u64 offset);
@@ -72,7 +87,17 @@ extern void itrace_wake_up(struct perf_event *event);

extern bool is_itrace_event(struct perf_event *event);

+extern int itrace_sampler_init(struct perf_event *event,
+ struct task_struct *task);
+extern void itrace_sampler_fini(struct perf_event *event);
+extern unsigned long itrace_sampler_trace(struct perf_event *event,
+ struct perf_sample_data *data);
+extern void itrace_sampler_output(struct perf_event *event,
+ struct perf_output_handle *handle,
+ struct perf_sample_data *data);
#else
+static int itrace_kernel_event(struct perf_event *event,
+ struct task_struct *task) { return 0; }
static int itrace_inherit_event(struct perf_event *event,
struct task_struct *task) { return 0; }
static inline void
@@ -84,6 +109,18 @@ itrace_event_installable(struct perf_event *event,
struct perf_event_context *ctx) { return -EINVAL; }
static inline void itrace_wake_up(struct perf_event *event) {}
static inline bool is_itrace_event(struct perf_event *event) { return false; }
+
+static inline int itrace_sampler_init(struct perf_event *event,
+ struct task_struct *task) {}
+static inline void
+itrace_sampler_fini(struct perf_event *event) {}
+static inline unsigned long
+itrace_sampler_trace(struct perf_event *event,
+ struct perf_sample_data *data) { return 0; }
+static inline void
+itrace_sampler_output(struct perf_event *event,
+ struct perf_output_handle *handle,
+ struct perf_sample_data *data) {}
#endif

#endif /* _LINUX_PERF_EVENT_H */
diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h
index b0147e0..11eb133 100644
--- a/include/linux/perf_event.h
+++ b/include/linux/perf_event.h
@@ -83,6 +83,12 @@ struct perf_regs_user {
struct pt_regs *regs;
};

+struct perf_trace_record {
+ u64 size;
+ unsigned long from;
+ unsigned long to;
+};
+
struct task_struct;

/*
@@ -97,6 +103,11 @@ struct hw_perf_event_extra {

struct event_constraint;

+enum perf_itrace_counter_type {
+ PERF_ITRACE_USER = BIT(1),
+ PERF_ITRACE_SAMPLING = BIT(2),
+};
+
/**
* struct hw_perf_event - performance event hardware details:
*/
@@ -129,6 +140,7 @@ struct hw_perf_event {
struct { /* itrace */
struct file *itrace_file;
struct task_struct *itrace_target;
+ unsigned int counter_type;
};
#ifdef CONFIG_HAVE_HW_BREAKPOINT
struct { /* breakpoint */
@@ -434,6 +446,7 @@ struct perf_event {
perf_overflow_handler_t overflow_handler;
void *overflow_handler_context;

+ struct perf_event *trace_event;
#ifdef CONFIG_EVENT_TRACING
struct ftrace_event_call *tp_event;
struct event_filter *filter;
@@ -591,6 +604,7 @@ struct perf_sample_data {
union perf_mem_data_src data_src;
struct perf_callchain_entry *callchain;
struct perf_raw_record *raw;
+ struct perf_trace_record trace;
struct perf_branch_stack *br_stack;
struct perf_regs_user regs_user;
u64 stack_user_size;
@@ -611,6 +625,7 @@ static inline void perf_sample_data_init(struct perf_sample_data *data,
data->period = period;
data->regs_user.abi = PERF_SAMPLE_REGS_ABI_NONE;
data->regs_user.regs = NULL;
+ data->trace.from = data->trace.to = data->trace.size = 0;
data->stack_user_size = 0;
data->weight = 0;
data->data_src.val = 0;
diff --git a/include/uapi/linux/perf_event.h b/include/uapi/linux/perf_event.h
index 2dd57db..a06cf4b 100644
--- a/include/uapi/linux/perf_event.h
+++ b/include/uapi/linux/perf_event.h
@@ -137,8 +137,9 @@ enum perf_event_sample_format {
PERF_SAMPLE_DATA_SRC = 1U << 15,
PERF_SAMPLE_IDENTIFIER = 1U << 16,
PERF_SAMPLE_TRANSACTION = 1U << 17,
+ PERF_SAMPLE_ITRACE = 1U << 18,

- PERF_SAMPLE_MAX = 1U << 18, /* non-ABI */
+ PERF_SAMPLE_MAX = 1U << 19, /* non-ABI */
};

/*
@@ -689,6 +690,8 @@ enum perf_event_type {
* { u64 weight; } && PERF_SAMPLE_WEIGHT
* { u64 data_src; } && PERF_SAMPLE_DATA_SRC
* { u64 transaction; } && PERF_SAMPLE_TRANSACTION
+ * { u64 size;
+ * char data[size]; } && PERF_SAMPLE_ITRACE
* };
*/
PERF_RECORD_SAMPLE = 9,
diff --git a/kernel/events/core.c b/kernel/events/core.c
index ff6e286..e1388a5 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -1576,6 +1576,9 @@ void perf_event_disable(struct perf_event *event)
struct perf_event_context *ctx = event->ctx;
struct task_struct *task = ctx->task;

+ if (event->trace_event)
+ perf_event_disable(event->trace_event);
+
if (!task) {
/*
* Disable the event on the cpu that it's on
@@ -2070,6 +2073,8 @@ void perf_event_enable(struct perf_event *event)
struct perf_event_context *ctx = event->ctx;
struct task_struct *task = ctx->task;

+ if (event->trace_event)
+ perf_event_enable(event->trace_event);
if (!task) {
/*
* Enable the event on the cpu that it's on
@@ -3209,6 +3214,8 @@ static void unaccount_event(struct perf_event *event)
static_key_slow_dec_deferred(&perf_sched_events);
if (has_branch_stack(event))
static_key_slow_dec_deferred(&perf_sched_events);
+ if ((event->attr.sample_type & PERF_SAMPLE_ITRACE) && event->trace_event)
+ itrace_sampler_fini(event);

unaccount_event_cpu(event, event->cpu);
}
@@ -4664,6 +4671,13 @@ void perf_output_sample(struct perf_output_handle *handle,
if (sample_type & PERF_SAMPLE_TRANSACTION)
perf_output_put(handle, data->txn);

+ if (sample_type & PERF_SAMPLE_ITRACE) {
+ perf_output_put(handle, data->trace.size);
+
+ if (data->trace.size)
+ itrace_sampler_output(event, handle, data);
+ }
+
if (!event->attr.watermark) {
int wakeup_events = event->attr.wakeup_events;

@@ -4771,6 +4785,14 @@ void perf_prepare_sample(struct perf_event_header *header,
data->stack_user_size = stack_size;
header->size += size;
}
+
+ if (sample_type & PERF_SAMPLE_ITRACE) {
+ u64 size = sizeof(u64);
+
+ size += itrace_sampler_trace(event, data);
+
+ header->size += size;
+ }
}

static void perf_event_output(struct perf_event *event,
@@ -6795,6 +6817,15 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu,
if (err)
goto err_pmu;
}
+
+ if (event->attr.sample_type & PERF_SAMPLE_ITRACE) {
+ err = itrace_sampler_init(event, task);
+ if (err) {
+ /* XXX: either clean up callchain buffers too
+ or forbid them to go together */
+ goto err_pmu;
+ }
+ }
}

return event;
@@ -7369,6 +7400,10 @@ perf_event_create_kernel_counter(struct perf_event_attr *attr, int cpu,

account_event(event);

+ err = itrace_kernel_event(event, task);
+ if (err)
+ goto err_free;
+
ctx = find_get_context(event->pmu, task, cpu);
if (IS_ERR(ctx)) {
err = PTR_ERR(ctx);
diff --git a/kernel/events/itrace.c b/kernel/events/itrace.c
index ec26373..f003530 100644
--- a/kernel/events/itrace.c
+++ b/kernel/events/itrace.c
@@ -89,6 +89,22 @@ bool is_itrace_event(struct perf_event *event)
return !!itrace_pmu_find(event->attr.type);
}

+static void itrace_event_destroy(struct perf_event *event)
+{
+ struct ring_buffer *rb = event->rb[PERF_RB_ITRACE];
+
+ if (!rb)
+ return;
+
+ if (event->hw.counter_type != PERF_ITRACE_USER) {
+ atomic_dec(&rb->mmap_count);
+ atomic_dec(&event->mmap_count[PERF_RB_ITRACE]);
+ ring_buffer_detach(event, rb);
+ rcu_assign_pointer(event->rb[PERF_RB_ITRACE], NULL);
+ ring_buffer_put(rb); /* should be last */
+ }
+}
+
int itrace_event_installable(struct perf_event *event,
struct perf_event_context *ctx)
{
@@ -115,8 +131,16 @@ int itrace_event_installable(struct perf_event *event,
static int itrace_event_init(struct perf_event *event)
{
struct itrace_pmu *ipmu = to_itrace_pmu(event->pmu);
+ int ret;

- return ipmu->event_init(event);
+ ret = ipmu->event_init(event);
+ if (ret)
+ return ret;
+
+ event->destroy = itrace_event_destroy;
+ event->hw.counter_type = PERF_ITRACE_USER;
+
+ return 0;
}

static unsigned long itrace_rb_get_size(int nr_pages)
@@ -214,9 +238,16 @@ out:
mutex_unlock(&event->mmap_mutex);
}

+static size_t roundup_buffer_size(u64 size)
+{
+ return 1ul << (__get_order(size) + PAGE_SHIFT);
+}
+
int itrace_inherit_event(struct perf_event *event, struct task_struct *task)
{
+ size_t size = event->attr.itrace_sample_size;
struct perf_event *parent = event->parent;
+ struct ring_buffer *rb;
struct itrace_pmu *ipmu;

if (!is_itrace_event(event))
@@ -224,14 +255,59 @@ int itrace_inherit_event(struct perf_event *event, struct task_struct *task)

ipmu = to_itrace_pmu(event->pmu);

- /*
- * inherited user's counters should inherit buffers IF
- * they aren't cpu==-1
- */
- if (parent->cpu == -1)
- return -EINVAL;
+ if (parent->hw.counter_type == PERF_ITRACE_USER) {
+ /*
+ * inherited user's counters should inherit buffers IF
+ * they aren't cpu==-1
+ */
+ if (parent->cpu == -1)
+ return -EINVAL;
+
+ itrace_set_output(event, parent);
+ return 0;
+ }
+
+ event->hw.counter_type = parent->hw.counter_type;
+
+ size = roundup_buffer_size(size);
+ rb = rb_alloc(event, size >> PAGE_SHIFT, 0, event->cpu, 0,
+ &itrace_rb_ops);
+ if (!rb)
+ return -ENOMEM;
+
+ ring_buffer_attach(event, rb);
+ rcu_assign_pointer(event->rb[PERF_RB_ITRACE], rb);
+ atomic_set(&rb->mmap_count, 1);
+ atomic_set(&event->mmap_count[PERF_RB_ITRACE], 1);
+
+ return 0;
+}
+
+int itrace_kernel_event(struct perf_event *event, struct task_struct *task)
+{
+ struct itrace_pmu *ipmu;
+ struct ring_buffer *rb;
+ size_t size;
+
+ if (!is_itrace_event(event))
+ return 0;

- itrace_set_output(event, parent);
+ ipmu = to_itrace_pmu(event->pmu);
+
+ if (!event->attr.itrace_sample_size)
+ return 0;
+
+ size = roundup_buffer_size(event->attr.itrace_sample_size);
+
+ rb = rb_alloc(event, size >> PAGE_SHIFT, 0, event->cpu, 0,
+ &itrace_rb_ops);
+ if (!rb)
+ return -ENOMEM;
+
+ ring_buffer_attach(event, rb);
+ rcu_assign_pointer(event->rb[PERF_RB_ITRACE], rb);
+ atomic_set(&rb->mmap_count, 1);
+ atomic_set(&event->mmap_count[PERF_RB_ITRACE], 1);

return 0;
}
@@ -269,3 +345,80 @@ int itrace_pmu_register(struct itrace_pmu *ipmu)

return ret;
}
+
+/*
+ * Trace sample annotation
+ * For events that have attr.sample_type & PERF_SAMPLE_ITRACE, perf calls here
+ * to configure and obtain itrace samples.
+ */
+
+int itrace_sampler_init(struct perf_event *event, struct task_struct *task)
+{
+ struct perf_event_attr attr;
+ struct perf_event *tevt;
+ struct itrace_pmu *ipmu;
+
+ ipmu = itrace_pmu_find(event->attr.itrace_sample_type);
+ if (!ipmu || !ipmu->sample_trace || !ipmu->sample_output)
+ return -ENOTSUPP;
+
+ memset(&attr, 0, sizeof(attr));
+ attr.type = ipmu->pmu.type;
+ attr.config = 0;
+ attr.sample_type = 0;
+ attr.exclude_user = event->attr.exclude_user;
+ attr.exclude_kernel = event->attr.exclude_kernel;
+ attr.itrace_sample_size = event->attr.itrace_sample_size;
+ attr.itrace_config = event->attr.itrace_config;
+
+ tevt = perf_event_create_kernel_counter(&attr, event->cpu, task, NULL, NULL);
+ if (IS_ERR(tevt))
+ return PTR_ERR(tevt);
+
+ if (!itrace_priv(tevt)) {
+ perf_event_release_kernel(tevt);
+ return -EINVAL;
+ }
+
+ event->trace_event = tevt;
+ tevt->hw.counter_type = PERF_ITRACE_SAMPLING;
+ if (event->state != PERF_EVENT_STATE_OFF)
+ perf_event_enable(event->trace_event);
+
+ return 0;
+}
+
+void itrace_sampler_fini(struct perf_event *event)
+{
+ struct perf_event *tevt = event->trace_event;
+
+ perf_event_release_kernel(tevt);
+ event->trace_event = NULL;
+}
+
+unsigned long itrace_sampler_trace(struct perf_event *event,
+ struct perf_sample_data *data)
+{
+ struct perf_event *tevt = event->trace_event;
+ struct itrace_pmu *ipmu;
+
+ if (!tevt)
+ return 0;
+
+ ipmu = to_itrace_pmu(tevt->pmu);
+ return ipmu->sample_trace(tevt, data);
+}
+
+void itrace_sampler_output(struct perf_event *event,
+ struct perf_output_handle *handle,
+ struct perf_sample_data *data)
+{
+ struct perf_event *tevt = event->trace_event;
+ struct itrace_pmu *ipmu;
+
+ if (!tevt || !data->trace.size)
+ return;
+
+ ipmu = to_itrace_pmu(tevt->pmu);
+ ipmu->sample_output(tevt, handle, data);
+}
--
1.8.5.2


\
 
 \ /
  Last update: 2014-02-06 12:41    [W:0.199 / U:0.588 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site