diff options
author | Peter Zijlstra <a.p.zijlstra@chello.nl> | 2009-08-19 05:18:27 -0400 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2009-08-25 03:36:13 -0400 |
commit | a4be7c2778d1fd8e0a8a5607bec91fa221ab2c91 (patch) | |
tree | e7aca50900a98d68173e6a45ead2815d75e2e197 | |
parent | fa289beca9de9119c7760bd984f3640da21bc94c (diff) |
perf_counter: Allow sharing of output channels
Provide the ability to configure a counter to send its output
to another (already existing) counter's output stream.
Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Mike Galbraith <efault@gmx.de>
Cc: stephane eranian <eranian@googlemail.com>
Cc: Paul Mackerras <paulus@samba.org>
LKML-Reference: <20090819092023.980284148@chello.nl>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
-rw-r--r-- | include/linux/perf_counter.h | 5 | ||||
-rw-r--r-- | kernel/perf_counter.c | 83 |
2 files changed, 85 insertions, 3 deletions
diff --git a/include/linux/perf_counter.h b/include/linux/perf_counter.h index b53f7006cc4e..e022b847c90d 100644 --- a/include/linux/perf_counter.h +++ b/include/linux/perf_counter.h | |||
@@ -216,6 +216,7 @@ struct perf_counter_attr { | |||
216 | #define PERF_COUNTER_IOC_REFRESH _IO ('$', 2) | 216 | #define PERF_COUNTER_IOC_REFRESH _IO ('$', 2) |
217 | #define PERF_COUNTER_IOC_RESET _IO ('$', 3) | 217 | #define PERF_COUNTER_IOC_RESET _IO ('$', 3) |
218 | #define PERF_COUNTER_IOC_PERIOD _IOW('$', 4, u64) | 218 | #define PERF_COUNTER_IOC_PERIOD _IOW('$', 4, u64) |
219 | #define PERF_COUNTER_IOC_SET_OUTPUT _IO ('$', 5) | ||
219 | 220 | ||
220 | enum perf_counter_ioc_flags { | 221 | enum perf_counter_ioc_flags { |
221 | PERF_IOC_FLAG_GROUP = 1U << 0, | 222 | PERF_IOC_FLAG_GROUP = 1U << 0, |
@@ -415,6 +416,9 @@ enum perf_callchain_context { | |||
415 | PERF_CONTEXT_MAX = (__u64)-4095, | 416 | PERF_CONTEXT_MAX = (__u64)-4095, |
416 | }; | 417 | }; |
417 | 418 | ||
419 | #define PERF_FLAG_FD_NO_GROUP (1U << 0) | ||
420 | #define PERF_FLAG_FD_OUTPUT (1U << 1) | ||
421 | |||
418 | #ifdef __KERNEL__ | 422 | #ifdef __KERNEL__ |
419 | /* | 423 | /* |
420 | * Kernel-internal data types and definitions: | 424 | * Kernel-internal data types and definitions: |
@@ -536,6 +540,7 @@ struct perf_counter { | |||
536 | struct list_head sibling_list; | 540 | struct list_head sibling_list; |
537 | int nr_siblings; | 541 | int nr_siblings; |
538 | struct perf_counter *group_leader; | 542 | struct perf_counter *group_leader; |
543 | struct perf_counter *output; | ||
539 | const struct pmu *pmu; | 544 | const struct pmu *pmu; |
540 | 545 | ||
541 | enum perf_counter_active_state state; | 546 | enum perf_counter_active_state state; |
diff --git a/kernel/perf_counter.c b/kernel/perf_counter.c index 06bf6a4f2608..53abcbefa0bf 100644 --- a/kernel/perf_counter.c +++ b/kernel/perf_counter.c | |||
@@ -1692,6 +1692,11 @@ static void free_counter(struct perf_counter *counter) | |||
1692 | atomic_dec(&nr_task_counters); | 1692 | atomic_dec(&nr_task_counters); |
1693 | } | 1693 | } |
1694 | 1694 | ||
1695 | if (counter->output) { | ||
1696 | fput(counter->output->filp); | ||
1697 | counter->output = NULL; | ||
1698 | } | ||
1699 | |||
1695 | if (counter->destroy) | 1700 | if (counter->destroy) |
1696 | counter->destroy(counter); | 1701 | counter->destroy(counter); |
1697 | 1702 | ||
@@ -1977,6 +1982,8 @@ unlock: | |||
1977 | return ret; | 1982 | return ret; |
1978 | } | 1983 | } |
1979 | 1984 | ||
1985 | int perf_counter_set_output(struct perf_counter *counter, int output_fd); | ||
1986 | |||
1980 | static long perf_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | 1987 | static long perf_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
1981 | { | 1988 | { |
1982 | struct perf_counter *counter = file->private_data; | 1989 | struct perf_counter *counter = file->private_data; |
@@ -2000,6 +2007,9 @@ static long perf_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | |||
2000 | case PERF_COUNTER_IOC_PERIOD: | 2007 | case PERF_COUNTER_IOC_PERIOD: |
2001 | return perf_counter_period(counter, (u64 __user *)arg); | 2008 | return perf_counter_period(counter, (u64 __user *)arg); |
2002 | 2009 | ||
2010 | case PERF_COUNTER_IOC_SET_OUTPUT: | ||
2011 | return perf_counter_set_output(counter, arg); | ||
2012 | |||
2003 | default: | 2013 | default: |
2004 | return -ENOTTY; | 2014 | return -ENOTTY; |
2005 | } | 2015 | } |
@@ -2270,6 +2280,11 @@ static int perf_mmap(struct file *file, struct vm_area_struct *vma) | |||
2270 | 2280 | ||
2271 | WARN_ON_ONCE(counter->ctx->parent_ctx); | 2281 | WARN_ON_ONCE(counter->ctx->parent_ctx); |
2272 | mutex_lock(&counter->mmap_mutex); | 2282 | mutex_lock(&counter->mmap_mutex); |
2283 | if (counter->output) { | ||
2284 | ret = -EINVAL; | ||
2285 | goto unlock; | ||
2286 | } | ||
2287 | |||
2273 | if (atomic_inc_not_zero(&counter->mmap_count)) { | 2288 | if (atomic_inc_not_zero(&counter->mmap_count)) { |
2274 | if (nr_pages != counter->data->nr_pages) | 2289 | if (nr_pages != counter->data->nr_pages) |
2275 | ret = -EINVAL; | 2290 | ret = -EINVAL; |
@@ -2655,6 +2670,7 @@ static int perf_output_begin(struct perf_output_handle *handle, | |||
2655 | struct perf_counter *counter, unsigned int size, | 2670 | struct perf_counter *counter, unsigned int size, |
2656 | int nmi, int sample) | 2671 | int nmi, int sample) |
2657 | { | 2672 | { |
2673 | struct perf_counter *output_counter; | ||
2658 | struct perf_mmap_data *data; | 2674 | struct perf_mmap_data *data; |
2659 | unsigned int offset, head; | 2675 | unsigned int offset, head; |
2660 | int have_lost; | 2676 | int have_lost; |
@@ -2664,13 +2680,17 @@ static int perf_output_begin(struct perf_output_handle *handle, | |||
2664 | u64 lost; | 2680 | u64 lost; |
2665 | } lost_event; | 2681 | } lost_event; |
2666 | 2682 | ||
2683 | rcu_read_lock(); | ||
2667 | /* | 2684 | /* |
2668 | * For inherited counters we send all the output towards the parent. | 2685 | * For inherited counters we send all the output towards the parent. |
2669 | */ | 2686 | */ |
2670 | if (counter->parent) | 2687 | if (counter->parent) |
2671 | counter = counter->parent; | 2688 | counter = counter->parent; |
2672 | 2689 | ||
2673 | rcu_read_lock(); | 2690 | output_counter = rcu_dereference(counter->output); |
2691 | if (output_counter) | ||
2692 | counter = output_counter; | ||
2693 | |||
2674 | data = rcu_dereference(counter->data); | 2694 | data = rcu_dereference(counter->data); |
2675 | if (!data) | 2695 | if (!data) |
2676 | goto out; | 2696 | goto out; |
@@ -4218,6 +4238,57 @@ err_size: | |||
4218 | goto out; | 4238 | goto out; |
4219 | } | 4239 | } |
4220 | 4240 | ||
4241 | int perf_counter_set_output(struct perf_counter *counter, int output_fd) | ||
4242 | { | ||
4243 | struct perf_counter *output_counter = NULL; | ||
4244 | struct file *output_file = NULL; | ||
4245 | struct perf_counter *old_output; | ||
4246 | int fput_needed = 0; | ||
4247 | int ret = -EINVAL; | ||
4248 | |||
4249 | if (!output_fd) | ||
4250 | goto set; | ||
4251 | |||
4252 | output_file = fget_light(output_fd, &fput_needed); | ||
4253 | if (!output_file) | ||
4254 | return -EBADF; | ||
4255 | |||
4256 | if (output_file->f_op != &perf_fops) | ||
4257 | goto out; | ||
4258 | |||
4259 | output_counter = output_file->private_data; | ||
4260 | |||
4261 | /* Don't chain output fds */ | ||
4262 | if (output_counter->output) | ||
4263 | goto out; | ||
4264 | |||
4265 | /* Don't set an output fd when we already have an output channel */ | ||
4266 | if (counter->data) | ||
4267 | goto out; | ||
4268 | |||
4269 | atomic_long_inc(&output_file->f_count); | ||
4270 | |||
4271 | set: | ||
4272 | mutex_lock(&counter->mmap_mutex); | ||
4273 | old_output = counter->output; | ||
4274 | rcu_assign_pointer(counter->output, output_counter); | ||
4275 | mutex_unlock(&counter->mmap_mutex); | ||
4276 | |||
4277 | if (old_output) { | ||
4278 | /* | ||
4279 | * we need to make sure no existing perf_output_*() | ||
4280 | * is still referencing this counter. | ||
4281 | */ | ||
4282 | synchronize_rcu(); | ||
4283 | fput(old_output->filp); | ||
4284 | } | ||
4285 | |||
4286 | ret = 0; | ||
4287 | out: | ||
4288 | fput_light(output_file, fput_needed); | ||
4289 | return ret; | ||
4290 | } | ||
4291 | |||
4221 | /** | 4292 | /** |
4222 | * sys_perf_counter_open - open a performance counter, associate it to a task/cpu | 4293 | * sys_perf_counter_open - open a performance counter, associate it to a task/cpu |
4223 | * | 4294 | * |
@@ -4240,7 +4311,7 @@ SYSCALL_DEFINE5(perf_counter_open, | |||
4240 | int ret; | 4311 | int ret; |
4241 | 4312 | ||
4242 | /* for future expandability... */ | 4313 | /* for future expandability... */ |
4243 | if (flags) | 4314 | if (flags & ~(PERF_FLAG_FD_NO_GROUP | PERF_FLAG_FD_OUTPUT)) |
4244 | return -EINVAL; | 4315 | return -EINVAL; |
4245 | 4316 | ||
4246 | ret = perf_copy_attr(attr_uptr, &attr); | 4317 | ret = perf_copy_attr(attr_uptr, &attr); |
@@ -4268,7 +4339,7 @@ SYSCALL_DEFINE5(perf_counter_open, | |||
4268 | * Look up the group leader (we will attach this counter to it): | 4339 | * Look up the group leader (we will attach this counter to it): |
4269 | */ | 4340 | */ |
4270 | group_leader = NULL; | 4341 | group_leader = NULL; |
4271 | if (group_fd != -1) { | 4342 | if (group_fd != -1 && !(flags & PERF_FLAG_FD_NO_GROUP)) { |
4272 | ret = -EINVAL; | 4343 | ret = -EINVAL; |
4273 | group_file = fget_light(group_fd, &fput_needed); | 4344 | group_file = fget_light(group_fd, &fput_needed); |
4274 | if (!group_file) | 4345 | if (!group_file) |
@@ -4310,6 +4381,12 @@ SYSCALL_DEFINE5(perf_counter_open, | |||
4310 | if (!counter_file) | 4381 | if (!counter_file) |
4311 | goto err_free_put_context; | 4382 | goto err_free_put_context; |
4312 | 4383 | ||
4384 | if (flags & PERF_FLAG_FD_OUTPUT) { | ||
4385 | ret = perf_counter_set_output(counter, group_fd); | ||
4386 | if (ret) | ||
4387 | goto err_free_put_context; | ||
4388 | } | ||
4389 | |||
4313 | counter->filp = counter_file; | 4390 | counter->filp = counter_file; |
4314 | WARN_ON_ONCE(ctx->parent_ctx); | 4391 | WARN_ON_ONCE(ctx->parent_ctx); |
4315 | mutex_lock(&ctx->mutex); | 4392 | mutex_lock(&ctx->mutex); |