mirror of
https://github.com/adulau/aha.git
synced 2024-12-28 03:36:19 +00:00
444a2a3bcd
While tracing using events with perf, if one enables the lockdep:lock_acquire event, it will infect every other perf trace events. Basically, you can enable whatever set of trace events through perf but if this event is part of the set, the only result we can get is a long list of lock_acquire events of rcu read lock, and only that. This is because of a recursion inside perf. 1) When a trace event is triggered, it will fill a per cpu buffer and submit it to perf. 2) Perf will commit this event but will also protect some data using rcu_read_lock 3) A recursion appears: rcu_read_lock triggers a lock_acquire event that will fill the per cpu event and then submit the buffer to perf. 4) Perf detects a recursion and ignores it 5) Perf continues its work on the previous event, but its buffer has been overwritten by the lock_acquire event, it has then been turned into a lock_acquire event of rcu read lock Such scenario also happens with lock_release with rcu_read_unlock(). We could turn the rcu_read_lock() into __rcu_read_lock() to drop the lock debugging from perf fast path, but that would make us lose the rcu debugging and that doesn't prevent from other possible kind of recursion from perf in the future. This patch adds a recursion protection based on a counter on the perf trace per cpu buffers to solve the problem. -v2: Fixed lost whitespace, added reviewed-by tag Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com> Reviewed-by: Masami Hiramatsu <mhiramat@redhat.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Arnaldo Carvalho de Melo <acme@redhat.com> Cc: Mike Galbraith <efault@gmx.de> Cc: Paul Mackerras <paulus@samba.org> Cc: Steven Rostedt <rostedt@goodmis.org> Cc: Li Zefan <lizf@cn.fujitsu.com> Cc: Jason Baron <jbaron@redhat.com> LKML-Reference: <1257477185-7838-1-git-send-email-fweisbec@gmail.com> Signed-off-by: Ingo Molnar <mingo@elte.hu>
120 lines
2.4 KiB
C
120 lines
2.4 KiB
C
/*
|
|
* trace event based perf counter profiling
|
|
*
|
|
* Copyright (C) 2009 Red Hat Inc, Peter Zijlstra <pzijlstr@redhat.com>
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include "trace.h"
|
|
|
|
|
|
struct perf_trace_buf *perf_trace_buf;
|
|
EXPORT_SYMBOL_GPL(perf_trace_buf);
|
|
|
|
struct perf_trace_buf *perf_trace_buf_nmi;
|
|
EXPORT_SYMBOL_GPL(perf_trace_buf_nmi);
|
|
|
|
/* Count the events in use (per event id, not per instance) */
|
|
static int total_profile_count;
|
|
|
|
static int ftrace_profile_enable_event(struct ftrace_event_call *event)
|
|
{
|
|
struct perf_trace_buf *buf;
|
|
int ret = -ENOMEM;
|
|
|
|
if (atomic_inc_return(&event->profile_count))
|
|
return 0;
|
|
|
|
if (!total_profile_count) {
|
|
buf = alloc_percpu(struct perf_trace_buf);
|
|
if (!buf)
|
|
goto fail_buf;
|
|
|
|
rcu_assign_pointer(perf_trace_buf, buf);
|
|
|
|
buf = alloc_percpu(struct perf_trace_buf);
|
|
if (!buf)
|
|
goto fail_buf_nmi;
|
|
|
|
rcu_assign_pointer(perf_trace_buf_nmi, buf);
|
|
}
|
|
|
|
ret = event->profile_enable(event);
|
|
if (!ret) {
|
|
total_profile_count++;
|
|
return 0;
|
|
}
|
|
|
|
fail_buf_nmi:
|
|
if (!total_profile_count) {
|
|
free_percpu(perf_trace_buf_nmi);
|
|
free_percpu(perf_trace_buf);
|
|
perf_trace_buf_nmi = NULL;
|
|
perf_trace_buf = NULL;
|
|
}
|
|
fail_buf:
|
|
atomic_dec(&event->profile_count);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ftrace_profile_enable(int event_id)
|
|
{
|
|
struct ftrace_event_call *event;
|
|
int ret = -EINVAL;
|
|
|
|
mutex_lock(&event_mutex);
|
|
list_for_each_entry(event, &ftrace_events, list) {
|
|
if (event->id == event_id && event->profile_enable &&
|
|
try_module_get(event->mod)) {
|
|
ret = ftrace_profile_enable_event(event);
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&event_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void ftrace_profile_disable_event(struct ftrace_event_call *event)
|
|
{
|
|
struct perf_trace_buf *buf, *nmi_buf;
|
|
|
|
if (!atomic_add_negative(-1, &event->profile_count))
|
|
return;
|
|
|
|
event->profile_disable(event);
|
|
|
|
if (!--total_profile_count) {
|
|
buf = perf_trace_buf;
|
|
rcu_assign_pointer(perf_trace_buf, NULL);
|
|
|
|
nmi_buf = perf_trace_buf_nmi;
|
|
rcu_assign_pointer(perf_trace_buf_nmi, NULL);
|
|
|
|
/*
|
|
* Ensure every events in profiling have finished before
|
|
* releasing the buffers
|
|
*/
|
|
synchronize_sched();
|
|
|
|
free_percpu(buf);
|
|
free_percpu(nmi_buf);
|
|
}
|
|
}
|
|
|
|
void ftrace_profile_disable(int event_id)
|
|
{
|
|
struct ftrace_event_call *event;
|
|
|
|
mutex_lock(&event_mutex);
|
|
list_for_each_entry(event, &ftrace_events, list) {
|
|
if (event->id == event_id) {
|
|
ftrace_profile_disable_event(event);
|
|
module_put(event->mod);
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&event_mutex);
|
|
}
|