perf: Fix event cleanup across CPU hotplugs

Perf hardware events are generally tied to a particular CPU.
During the cleanup process perf_remove_from_context()
tries to execute the PMU related cleanup on the CPU that the
event is tied to. But if the CPU is offline, the cleanup is
not completed successfully at the PMU level, but the event
is freed (at the core level).

This fix is to collect the events that are approaching the release
when it's CPU is down. These zombie events are cleaned up when the
CPU is hotplugged on again.

Change-Id: I9ddf8f32cfcec4e0cb1c0910fb68b6db984d7553
Signed-off-by: Raghavendra Rao Ananta <rananta@codeaurora.org>
This commit is contained in:
Raghavendra Rao Ananta 2017-11-09 14:33:27 -08:00
commit b5fd2edf6f
4 changed files with 71 additions and 3 deletions

View file

@ -779,8 +779,8 @@ static irqreturn_t armv8pmu_handle_irq(int irq_num, void *dev)
struct perf_event *event = cpuc->events[idx];
struct hw_perf_event *hwc;
/* Ignore if we don't have an event. */
if (!event)
/* Ignore if we don't have an event or if it's a zombie event */
if (!event || event->state == PERF_EVENT_STATE_ZOMBIE)
continue;
/*

View file

@ -743,6 +743,15 @@ static void cpu_pm_pmu_setup(struct arm_pmu *armpmu, unsigned long cmd)
continue;
event = hw_events->events[idx];
if (!event)
continue;
/*
* Check if an attempt was made to free this event during
* the CPU went offline.
*/
if (event->state == PERF_EVENT_STATE_ZOMBIE)
continue;
switch (cmd) {
case CPU_PM_ENTER:

View file

@ -497,7 +497,8 @@ struct perf_addr_filters_head {
* enum perf_event_active_state - the states of a event
*/
enum perf_event_active_state {
PERF_EVENT_STATE_DEAD = -4,
PERF_EVENT_STATE_DEAD = -5,
PERF_EVENT_STATE_ZOMBIE = -4,
PERF_EVENT_STATE_EXIT = -3,
PERF_EVENT_STATE_ERROR = -2,
PERF_EVENT_STATE_OFF = -1,
@ -720,6 +721,7 @@ struct perf_event {
/* Is this event shared with other events */
bool shared;
struct list_head zombie_entry;
#endif /* CONFIG_PERF_EVENTS */
};

View file

@ -4262,6 +4262,14 @@ static void put_event(struct perf_event *event)
_free_event(event);
}
/*
* Maintain a zombie list to collect all the zombie events
*/
#if defined CONFIG_HOTPLUG_CPU || defined CONFIG_KEXEC_CORE
static LIST_HEAD(zombie_list);
static DEFINE_SPINLOCK(zombie_list_lock);
#endif
/*
* Kill an event dead; while event:refcount will preserve the event
* object, it will not preserve its functionality. Once the last 'user'
@ -4272,6 +4280,26 @@ int perf_event_release_kernel(struct perf_event *event)
struct perf_event_context *ctx = event->ctx;
struct perf_event *child, *tmp;
/*
* If the cpu associated to this event is offline, set the event as a
* zombie event. The cleanup of the cpu would be done if the CPU is
* back online.
*/
#if defined CONFIG_HOTPLUG_CPU || defined CONFIG_KEXEC_CORE
if (!cpu_online(event->cpu)) {
if (event->state == PERF_EVENT_STATE_ZOMBIE)
return 0;
event->state = PERF_EVENT_STATE_ZOMBIE;
spin_lock(&zombie_list_lock);
list_add_tail(&event->zombie_entry, &zombie_list);
spin_unlock(&zombie_list_lock);
return 0;
}
#endif
/*
* If we got here through err_file: fput(event_file); we will not have
* attached to a context yet.
@ -9388,6 +9416,7 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu,
INIT_LIST_HEAD(&event->rb_entry);
INIT_LIST_HEAD(&event->active_entry);
INIT_LIST_HEAD(&event->addr_filters.list);
INIT_LIST_HEAD(&event->zombie_entry);
INIT_HLIST_NODE(&event->hlist_entry);
@ -11043,6 +11072,32 @@ check_hotplug_start_event(struct perf_event *event)
event->pmu->start(event, 0);
}
static void perf_event_zombie_cleanup(unsigned int cpu)
{
struct perf_event *event, *tmp;
spin_lock(&zombie_list_lock);
list_for_each_entry_safe(event, tmp, &zombie_list, zombie_entry) {
if (event->cpu != cpu)
continue;
list_del(&event->zombie_entry);
spin_unlock(&zombie_list_lock);
/*
* The detachment of the event with the
* PMU expects it to be in an active state
*/
event->state = PERF_EVENT_STATE_ACTIVE;
perf_event_release_kernel(event);
spin_lock(&zombie_list_lock);
}
spin_unlock(&zombie_list_lock);
}
static int perf_event_start_swevents(unsigned int cpu)
{
struct perf_event_context *ctx;
@ -11050,6 +11105,8 @@ static int perf_event_start_swevents(unsigned int cpu)
struct perf_event *event;
int idx;
perf_event_zombie_cleanup(cpu);
idx = srcu_read_lock(&pmus_srcu);
list_for_each_entry_rcu(pmu, &pmus, entry) {
ctx = &per_cpu_ptr(pmu->pmu_cpu_context, cpu)->ctx;