提交 3b6f9e5c 编写于 作者: P Paul Mackerras

perf_counter: Add support for pinned and exclusive counter groups

Impact: New perf_counter features

A pinned counter group is one that the user wants to have on the CPU
whenever possible, i.e. whenever the associated task is running, for
a per-task group, or always for a per-cpu group.  If the system
cannot satisfy that, it puts the group into an error state where
it is not scheduled any more and reads from it return EOF (i.e. 0
bytes read).  The group can be released from error state and made
readable again using prctl(PR_TASK_PERF_COUNTERS_ENABLE).  When we
have finer-grained enable/disable controls on counters we'll be able
to reset the error state on individual groups.

An exclusive group is one that the user wants to be the only group
using the CPU performance monitor hardware whenever it is on.  The
counter group scheduler will not schedule an exclusive group if there
are already other groups on the CPU and will not schedule other groups
onto the CPU if there is an exclusive group scheduled (that statement
does not apply to groups containing only software counters, which can
always go on and which do not prevent an exclusive group from going on).
With an exclusive group, we will be able to let users program PMU
registers at a low level without the concern that those settings will
perturb other measurements.

Along the way this reorganizes things a little:
- is_software_counter() is moved to perf_counter.h.
- cpuctx->active_oncpu now records the number of hardware counters on
  the CPU, i.e. it now excludes software counters.  Nothing was reading
  cpuctx->active_oncpu before, so this change is harmless.
- A new cpuctx->exclusive field records whether we currently have an
  exclusive group on the CPU.
- counter_sched_out moves higher up in perf_counter.c and gets called
  from __perf_counter_remove_from_context and __perf_counter_exit_task,
  where we used to have essentially the same code.
- __perf_counter_sched_in now goes through the counter list twice, doing
  the pinned counters in the first loop and the non-pinned counters in
  the second loop, in order to give the pinned counters the best chance
  to be scheduled in.

Note that only a group leader can be exclusive or pinned, and that
attribute applies to the whole group.  This avoids some awkwardness in
some corner cases (e.g. where a group leader is closed and the other
group members get added to the context list).  If we want to relax that
restriction later, we can, and it is easier to relax a restriction than
to apply a new one.

This doesn't yet handle the case where a pinned counter is inherited
and goes into error state in the child - the error state is not
propagated up to the parent when the child exits, and arguably it
should.
Signed-off-by: NPaul Mackerras <paulus@samba.org>
上级 01d0287f
...@@ -35,14 +35,6 @@ void perf_counter_print_debug(void) ...@@ -35,14 +35,6 @@ void perf_counter_print_debug(void)
{ {
} }
/*
* Return 1 for a software counter, 0 for a hardware counter
*/
static inline int is_software_counter(struct perf_counter *counter)
{
return !counter->hw_event.raw && counter->hw_event.type < 0;
}
/* /*
* Read one performance monitor counter (PMC). * Read one performance monitor counter (PMC).
*/ */
...@@ -443,6 +435,7 @@ int hw_perf_group_sched_in(struct perf_counter *group_leader, ...@@ -443,6 +435,7 @@ int hw_perf_group_sched_in(struct perf_counter *group_leader,
*/ */
for (i = n0; i < n0 + n; ++i) for (i = n0; i < n0 + n; ++i)
cpuhw->counter[i]->hw.config = cpuhw->events[i]; cpuhw->counter[i]->hw.config = cpuhw->events[i];
cpuctx->active_oncpu += n;
n = 1; n = 1;
counter_sched_in(group_leader, cpu); counter_sched_in(group_leader, cpu);
list_for_each_entry(sub, &group_leader->sibling_list, list_entry) { list_for_each_entry(sub, &group_leader->sibling_list, list_entry) {
...@@ -451,7 +444,6 @@ int hw_perf_group_sched_in(struct perf_counter *group_leader, ...@@ -451,7 +444,6 @@ int hw_perf_group_sched_in(struct perf_counter *group_leader,
++n; ++n;
} }
} }
cpuctx->active_oncpu += n;
ctx->nr_active += n; ctx->nr_active += n;
return 1; return 1;
......
...@@ -86,7 +86,10 @@ struct perf_counter_hw_event { ...@@ -86,7 +86,10 @@ struct perf_counter_hw_event {
nmi : 1, /* NMI sampling */ nmi : 1, /* NMI sampling */
raw : 1, /* raw event type */ raw : 1, /* raw event type */
inherit : 1, /* children inherit it */ inherit : 1, /* children inherit it */
__reserved_1 : 28; pinned : 1, /* must always be on PMU */
exclusive : 1, /* only counter on PMU */
__reserved_1 : 26;
u64 __reserved_2; u64 __reserved_2;
}; };
...@@ -141,6 +144,7 @@ struct hw_perf_counter_ops { ...@@ -141,6 +144,7 @@ struct hw_perf_counter_ops {
* enum perf_counter_active_state - the states of a counter * enum perf_counter_active_state - the states of a counter
*/ */
enum perf_counter_active_state { enum perf_counter_active_state {
PERF_COUNTER_STATE_ERROR = -2,
PERF_COUNTER_STATE_OFF = -1, PERF_COUNTER_STATE_OFF = -1,
PERF_COUNTER_STATE_INACTIVE = 0, PERF_COUNTER_STATE_INACTIVE = 0,
PERF_COUNTER_STATE_ACTIVE = 1, PERF_COUNTER_STATE_ACTIVE = 1,
...@@ -214,6 +218,7 @@ struct perf_cpu_context { ...@@ -214,6 +218,7 @@ struct perf_cpu_context {
struct perf_counter_context *task_ctx; struct perf_counter_context *task_ctx;
int active_oncpu; int active_oncpu;
int max_pertask; int max_pertask;
int exclusive;
}; };
/* /*
...@@ -240,6 +245,14 @@ extern int hw_perf_group_sched_in(struct perf_counter *group_leader, ...@@ -240,6 +245,14 @@ extern int hw_perf_group_sched_in(struct perf_counter *group_leader,
struct perf_cpu_context *cpuctx, struct perf_cpu_context *cpuctx,
struct perf_counter_context *ctx, int cpu); struct perf_counter_context *ctx, int cpu);
/*
* Return 1 for a software counter, 0 for a hardware counter
*/
static inline int is_software_counter(struct perf_counter *counter)
{
return !counter->hw_event.raw && counter->hw_event.type < 0;
}
#else #else
static inline void static inline void
perf_counter_task_sched_in(struct task_struct *task, int cpu) { } perf_counter_task_sched_in(struct task_struct *task, int cpu) { }
......
...@@ -93,6 +93,25 @@ list_del_counter(struct perf_counter *counter, struct perf_counter_context *ctx) ...@@ -93,6 +93,25 @@ list_del_counter(struct perf_counter *counter, struct perf_counter_context *ctx)
} }
} }
static void
counter_sched_out(struct perf_counter *counter,
struct perf_cpu_context *cpuctx,
struct perf_counter_context *ctx)
{
if (counter->state != PERF_COUNTER_STATE_ACTIVE)
return;
counter->state = PERF_COUNTER_STATE_INACTIVE;
counter->hw_ops->disable(counter);
counter->oncpu = -1;
if (!is_software_counter(counter))
cpuctx->active_oncpu--;
ctx->nr_active--;
if (counter->hw_event.exclusive || !cpuctx->active_oncpu)
cpuctx->exclusive = 0;
}
/* /*
* Cross CPU call to remove a performance counter * Cross CPU call to remove a performance counter
* *
...@@ -118,14 +137,9 @@ static void __perf_counter_remove_from_context(void *info) ...@@ -118,14 +137,9 @@ static void __perf_counter_remove_from_context(void *info)
curr_rq_lock_irq_save(&flags); curr_rq_lock_irq_save(&flags);
spin_lock(&ctx->lock); spin_lock(&ctx->lock);
if (counter->state == PERF_COUNTER_STATE_ACTIVE) { counter_sched_out(counter, cpuctx, ctx);
counter->state = PERF_COUNTER_STATE_INACTIVE;
counter->hw_ops->disable(counter); counter->task = NULL;
ctx->nr_active--;
cpuctx->active_oncpu--;
counter->task = NULL;
counter->oncpu = -1;
}
ctx->nr_counters--; ctx->nr_counters--;
/* /*
...@@ -207,7 +221,7 @@ counter_sched_in(struct perf_counter *counter, ...@@ -207,7 +221,7 @@ counter_sched_in(struct perf_counter *counter,
struct perf_counter_context *ctx, struct perf_counter_context *ctx,
int cpu) int cpu)
{ {
if (counter->state == PERF_COUNTER_STATE_OFF) if (counter->state <= PERF_COUNTER_STATE_OFF)
return 0; return 0;
counter->state = PERF_COUNTER_STATE_ACTIVE; counter->state = PERF_COUNTER_STATE_ACTIVE;
...@@ -223,12 +237,63 @@ counter_sched_in(struct perf_counter *counter, ...@@ -223,12 +237,63 @@ counter_sched_in(struct perf_counter *counter,
return -EAGAIN; return -EAGAIN;
} }
cpuctx->active_oncpu++; if (!is_software_counter(counter))
cpuctx->active_oncpu++;
ctx->nr_active++; ctx->nr_active++;
if (counter->hw_event.exclusive)
cpuctx->exclusive = 1;
return 0; return 0;
} }
/*
* Return 1 for a group consisting entirely of software counters,
* 0 if the group contains any hardware counters.
*/
static int is_software_only_group(struct perf_counter *leader)
{
struct perf_counter *counter;
if (!is_software_counter(leader))
return 0;
list_for_each_entry(counter, &leader->sibling_list, list_entry)
if (!is_software_counter(counter))
return 0;
return 1;
}
/*
* Work out whether we can put this counter group on the CPU now.
*/
static int group_can_go_on(struct perf_counter *counter,
struct perf_cpu_context *cpuctx,
int can_add_hw)
{
/*
* Groups consisting entirely of software counters can always go on.
*/
if (is_software_only_group(counter))
return 1;
/*
* If an exclusive group is already on, no other hardware
* counters can go on.
*/
if (cpuctx->exclusive)
return 0;
/*
* If this group is exclusive and there are already
* counters on the CPU, it can't go on.
*/
if (counter->hw_event.exclusive && cpuctx->active_oncpu)
return 0;
/*
* Otherwise, try to add it if all previous groups were able
* to go on.
*/
return can_add_hw;
}
/* /*
* Cross CPU call to install and enable a performance counter * Cross CPU call to install and enable a performance counter
*/ */
...@@ -240,6 +305,7 @@ static void __perf_install_in_context(void *info) ...@@ -240,6 +305,7 @@ static void __perf_install_in_context(void *info)
int cpu = smp_processor_id(); int cpu = smp_processor_id();
unsigned long flags; unsigned long flags;
u64 perf_flags; u64 perf_flags;
int err;
/* /*
* If this is a task context, we need to check whether it is * If this is a task context, we need to check whether it is
...@@ -261,9 +327,21 @@ static void __perf_install_in_context(void *info) ...@@ -261,9 +327,21 @@ static void __perf_install_in_context(void *info)
list_add_counter(counter, ctx); list_add_counter(counter, ctx);
ctx->nr_counters++; ctx->nr_counters++;
counter_sched_in(counter, cpuctx, ctx, cpu); /*
* An exclusive counter can't go on if there are already active
* hardware counters, and no hardware counter can go on if there
* is already an exclusive counter on.
*/
if (counter->state == PERF_COUNTER_STATE_INACTIVE &&
!group_can_go_on(counter, cpuctx, 1))
err = -EEXIST;
else
err = counter_sched_in(counter, cpuctx, ctx, cpu);
if (err && counter->hw_event.pinned)
counter->state = PERF_COUNTER_STATE_ERROR;
if (!ctx->task && cpuctx->max_pertask) if (!err && !ctx->task && cpuctx->max_pertask)
cpuctx->max_pertask--; cpuctx->max_pertask--;
hw_perf_restore(perf_flags); hw_perf_restore(perf_flags);
...@@ -326,22 +404,6 @@ perf_install_in_context(struct perf_counter_context *ctx, ...@@ -326,22 +404,6 @@ perf_install_in_context(struct perf_counter_context *ctx,
spin_unlock_irq(&ctx->lock); spin_unlock_irq(&ctx->lock);
} }
static void
counter_sched_out(struct perf_counter *counter,
struct perf_cpu_context *cpuctx,
struct perf_counter_context *ctx)
{
if (counter->state != PERF_COUNTER_STATE_ACTIVE)
return;
counter->state = PERF_COUNTER_STATE_INACTIVE;
counter->hw_ops->disable(counter);
counter->oncpu = -1;
cpuctx->active_oncpu--;
ctx->nr_active--;
}
static void static void
group_sched_out(struct perf_counter *group_counter, group_sched_out(struct perf_counter *group_counter,
struct perf_cpu_context *cpuctx, struct perf_cpu_context *cpuctx,
...@@ -359,6 +421,9 @@ group_sched_out(struct perf_counter *group_counter, ...@@ -359,6 +421,9 @@ group_sched_out(struct perf_counter *group_counter,
*/ */
list_for_each_entry(counter, &group_counter->sibling_list, list_entry) list_for_each_entry(counter, &group_counter->sibling_list, list_entry)
counter_sched_out(counter, cpuctx, ctx); counter_sched_out(counter, cpuctx, ctx);
if (group_counter->hw_event.exclusive)
cpuctx->exclusive = 0;
} }
void __perf_counter_sched_out(struct perf_counter_context *ctx, void __perf_counter_sched_out(struct perf_counter_context *ctx,
...@@ -455,30 +520,6 @@ group_sched_in(struct perf_counter *group_counter, ...@@ -455,30 +520,6 @@ group_sched_in(struct perf_counter *group_counter,
return -EAGAIN; return -EAGAIN;
} }
/*
* Return 1 for a software counter, 0 for a hardware counter
*/
static inline int is_software_counter(struct perf_counter *counter)
{
return !counter->hw_event.raw && counter->hw_event.type < 0;
}
/*
* Return 1 for a group consisting entirely of software counters,
* 0 if the group contains any hardware counters.
*/
static int is_software_only_group(struct perf_counter *leader)
{
struct perf_counter *counter;
if (!is_software_counter(leader))
return 0;
list_for_each_entry(counter, &leader->sibling_list, list_entry)
if (!is_software_counter(counter))
return 0;
return 1;
}
static void static void
__perf_counter_sched_in(struct perf_counter_context *ctx, __perf_counter_sched_in(struct perf_counter_context *ctx,
struct perf_cpu_context *cpuctx, int cpu) struct perf_cpu_context *cpuctx, int cpu)
...@@ -492,7 +533,38 @@ __perf_counter_sched_in(struct perf_counter_context *ctx, ...@@ -492,7 +533,38 @@ __perf_counter_sched_in(struct perf_counter_context *ctx,
spin_lock(&ctx->lock); spin_lock(&ctx->lock);
flags = hw_perf_save_disable(); flags = hw_perf_save_disable();
/*
* First go through the list and put on any pinned groups
* in order to give them the best chance of going on.
*/
list_for_each_entry(counter, &ctx->counter_list, list_entry) {
if (counter->state <= PERF_COUNTER_STATE_OFF ||
!counter->hw_event.pinned)
continue;
if (counter->cpu != -1 && counter->cpu != cpu)
continue;
if (group_can_go_on(counter, cpuctx, 1))
group_sched_in(counter, cpuctx, ctx, cpu);
/*
* If this pinned group hasn't been scheduled,
* put it in error state.
*/
if (counter->state == PERF_COUNTER_STATE_INACTIVE)
counter->state = PERF_COUNTER_STATE_ERROR;
}
list_for_each_entry(counter, &ctx->counter_list, list_entry) { list_for_each_entry(counter, &ctx->counter_list, list_entry) {
/*
* Ignore counters in OFF or ERROR state, and
* ignore pinned counters since we did them already.
*/
if (counter->state <= PERF_COUNTER_STATE_OFF ||
counter->hw_event.pinned)
continue;
/* /*
* Listen to the 'cpu' scheduling filter constraint * Listen to the 'cpu' scheduling filter constraint
* of counters: * of counters:
...@@ -500,14 +572,10 @@ __perf_counter_sched_in(struct perf_counter_context *ctx, ...@@ -500,14 +572,10 @@ __perf_counter_sched_in(struct perf_counter_context *ctx,
if (counter->cpu != -1 && counter->cpu != cpu) if (counter->cpu != -1 && counter->cpu != cpu)
continue; continue;
/* if (group_can_go_on(counter, cpuctx, can_add_hw)) {
* If we scheduled in a group atomically and exclusively,
* or if this group can't go on, don't add any more
* hardware counters.
*/
if (can_add_hw || is_software_only_group(counter))
if (group_sched_in(counter, cpuctx, ctx, cpu)) if (group_sched_in(counter, cpuctx, ctx, cpu))
can_add_hw = 0; can_add_hw = 0;
}
} }
hw_perf_restore(flags); hw_perf_restore(flags);
spin_unlock(&ctx->lock); spin_unlock(&ctx->lock);
...@@ -567,8 +635,10 @@ int perf_counter_task_disable(void) ...@@ -567,8 +635,10 @@ int perf_counter_task_disable(void)
*/ */
perf_flags = hw_perf_save_disable(); perf_flags = hw_perf_save_disable();
list_for_each_entry(counter, &ctx->counter_list, list_entry) list_for_each_entry(counter, &ctx->counter_list, list_entry) {
counter->state = PERF_COUNTER_STATE_OFF; if (counter->state != PERF_COUNTER_STATE_ERROR)
counter->state = PERF_COUNTER_STATE_OFF;
}
hw_perf_restore(perf_flags); hw_perf_restore(perf_flags);
...@@ -607,7 +677,7 @@ int perf_counter_task_enable(void) ...@@ -607,7 +677,7 @@ int perf_counter_task_enable(void)
perf_flags = hw_perf_save_disable(); perf_flags = hw_perf_save_disable();
list_for_each_entry(counter, &ctx->counter_list, list_entry) { list_for_each_entry(counter, &ctx->counter_list, list_entry) {
if (counter->state != PERF_COUNTER_STATE_OFF) if (counter->state > PERF_COUNTER_STATE_OFF)
continue; continue;
counter->state = PERF_COUNTER_STATE_INACTIVE; counter->state = PERF_COUNTER_STATE_INACTIVE;
counter->hw_event.disabled = 0; counter->hw_event.disabled = 0;
...@@ -849,6 +919,14 @@ perf_read_hw(struct perf_counter *counter, char __user *buf, size_t count) ...@@ -849,6 +919,14 @@ perf_read_hw(struct perf_counter *counter, char __user *buf, size_t count)
if (count != sizeof(cntval)) if (count != sizeof(cntval))
return -EINVAL; return -EINVAL;
/*
* Return end-of-file for a read on a counter that is in
* error state (i.e. because it was pinned but it couldn't be
* scheduled on to the CPU at some point).
*/
if (counter->state == PERF_COUNTER_STATE_ERROR)
return 0;
mutex_lock(&counter->mutex); mutex_lock(&counter->mutex);
cntval = perf_counter_read(counter); cntval = perf_counter_read(counter);
mutex_unlock(&counter->mutex); mutex_unlock(&counter->mutex);
...@@ -884,7 +962,7 @@ perf_read_irq_data(struct perf_counter *counter, ...@@ -884,7 +962,7 @@ perf_read_irq_data(struct perf_counter *counter,
{ {
struct perf_data *irqdata, *usrdata; struct perf_data *irqdata, *usrdata;
DECLARE_WAITQUEUE(wait, current); DECLARE_WAITQUEUE(wait, current);
ssize_t res; ssize_t res, res2;
irqdata = counter->irqdata; irqdata = counter->irqdata;
usrdata = counter->usrdata; usrdata = counter->usrdata;
...@@ -905,6 +983,9 @@ perf_read_irq_data(struct perf_counter *counter, ...@@ -905,6 +983,9 @@ perf_read_irq_data(struct perf_counter *counter,
if (signal_pending(current)) if (signal_pending(current))
break; break;
if (counter->state == PERF_COUNTER_STATE_ERROR)
break;
spin_unlock_irq(&counter->waitq.lock); spin_unlock_irq(&counter->waitq.lock);
schedule(); schedule();
spin_lock_irq(&counter->waitq.lock); spin_lock_irq(&counter->waitq.lock);
...@@ -913,7 +994,8 @@ perf_read_irq_data(struct perf_counter *counter, ...@@ -913,7 +994,8 @@ perf_read_irq_data(struct perf_counter *counter,
__set_current_state(TASK_RUNNING); __set_current_state(TASK_RUNNING);
spin_unlock_irq(&counter->waitq.lock); spin_unlock_irq(&counter->waitq.lock);
if (usrdata->len + irqdata->len < count) if (usrdata->len + irqdata->len < count &&
counter->state != PERF_COUNTER_STATE_ERROR)
return -ERESTARTSYS; return -ERESTARTSYS;
read_pending: read_pending:
mutex_lock(&counter->mutex); mutex_lock(&counter->mutex);
...@@ -925,11 +1007,12 @@ perf_read_irq_data(struct perf_counter *counter, ...@@ -925,11 +1007,12 @@ perf_read_irq_data(struct perf_counter *counter,
/* Switch irq buffer: */ /* Switch irq buffer: */
usrdata = perf_switch_irq_data(counter); usrdata = perf_switch_irq_data(counter);
if (perf_copy_usrdata(usrdata, buf + res, count - res) < 0) { res2 = perf_copy_usrdata(usrdata, buf + res, count - res);
if (res2 < 0) {
if (!res) if (!res)
res = -EFAULT; res = -EFAULT;
} else { } else {
res = count; res += res2;
} }
out: out:
mutex_unlock(&counter->mutex); mutex_unlock(&counter->mutex);
...@@ -1348,6 +1431,11 @@ sys_perf_counter_open(struct perf_counter_hw_event *hw_event_uptr __user, ...@@ -1348,6 +1431,11 @@ sys_perf_counter_open(struct perf_counter_hw_event *hw_event_uptr __user,
*/ */
if (group_leader->ctx != ctx) if (group_leader->ctx != ctx)
goto err_put_context; goto err_put_context;
/*
* Only a group leader can be exclusive or pinned
*/
if (hw_event.exclusive || hw_event.pinned)
goto err_put_context;
} }
ret = -EINVAL; ret = -EINVAL;
...@@ -1473,13 +1561,7 @@ __perf_counter_exit_task(struct task_struct *child, ...@@ -1473,13 +1561,7 @@ __perf_counter_exit_task(struct task_struct *child,
cpuctx = &__get_cpu_var(perf_cpu_context); cpuctx = &__get_cpu_var(perf_cpu_context);
if (child_counter->state == PERF_COUNTER_STATE_ACTIVE) { counter_sched_out(child_counter, cpuctx, child_ctx);
child_counter->state = PERF_COUNTER_STATE_INACTIVE;
child_counter->hw_ops->disable(child_counter);
cpuctx->active_oncpu--;
child_ctx->nr_active--;
child_counter->oncpu = -1;
}
list_del_init(&child_counter->list_entry); list_del_init(&child_counter->list_entry);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册