diff options
author | Gleb Natapov <gleb@redhat.com> | 2011-11-27 10:59:09 -0500 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2011-12-06 02:34:02 -0500 |
commit | b202952075f62603bea9bfb6ebc6b0420db11949 (patch) | |
tree | 9c8e0538b455e68b5c371caba5b1585ed0ef9d8a | |
parent | b79387ef185af2323594920923cecba5753c3817 (diff) |
perf, core: Rate limit perf_sched_events jump_label patching
jump_lable patching is very expensive operation that involves pausing all
cpus. The patching of perf_sched_events jump_label is easily controllable
from userspace by unprivileged user.
When te user runs a loop like this:
"while true; do perf stat -e cycles true; done"
... the performance of my test application that just increments a counter
for one second drops by 4%.
This is on a 16 cpu box with my test application using only one of
them. An impact on a real server doing real work will be worse.
Performance of KVM PMU drops nearly 50% due to jump_lable for "perf
record" since KVM PMU implementation creates and destroys perf event
frequently.
This patch introduces a way to rate limit jump_label patching and uses
it to fix the above problem.
I believe that as jump_label use will spread the problem will become more
common and thus solving it in a generic code is appropriate. Also fixing
it in the perf code would result in moving jump_label accounting logic to
perf code with all the ifdefs in case of JUMP_LABEL=n kernel. With this
patch all details are nicely hidden inside jump_label code.
Signed-off-by: Gleb Natapov <gleb@redhat.com>
Acked-by: Jason Baron <jbaron@redhat.com>
Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Link: http://lkml.kernel.org/r/20111127155909.GO2557@redhat.com
Signed-off-by: Ingo Molnar <mingo@elte.hu>
-rw-r--r-- | include/linux/jump_label.h | 24 | ||||
-rw-r--r-- | include/linux/perf_event.h | 6 | ||||
-rw-r--r-- | kernel/events/core.c | 13 | ||||
-rw-r--r-- | kernel/jump_label.c | 35 |
4 files changed, 68 insertions, 10 deletions
diff --git a/include/linux/jump_label.h b/include/linux/jump_label.h index 388b0d425b50..a1e7f909c801 100644 --- a/include/linux/jump_label.h +++ b/include/linux/jump_label.h | |||
@@ -3,6 +3,7 @@ | |||
3 | 3 | ||
4 | #include <linux/types.h> | 4 | #include <linux/types.h> |
5 | #include <linux/compiler.h> | 5 | #include <linux/compiler.h> |
6 | #include <linux/workqueue.h> | ||
6 | 7 | ||
7 | #if defined(CC_HAVE_ASM_GOTO) && defined(CONFIG_JUMP_LABEL) | 8 | #if defined(CC_HAVE_ASM_GOTO) && defined(CONFIG_JUMP_LABEL) |
8 | 9 | ||
@@ -14,6 +15,12 @@ struct jump_label_key { | |||
14 | #endif | 15 | #endif |
15 | }; | 16 | }; |
16 | 17 | ||
18 | struct jump_label_key_deferred { | ||
19 | struct jump_label_key key; | ||
20 | unsigned long timeout; | ||
21 | struct delayed_work work; | ||
22 | }; | ||
23 | |||
17 | # include <asm/jump_label.h> | 24 | # include <asm/jump_label.h> |
18 | # define HAVE_JUMP_LABEL | 25 | # define HAVE_JUMP_LABEL |
19 | #endif /* CC_HAVE_ASM_GOTO && CONFIG_JUMP_LABEL */ | 26 | #endif /* CC_HAVE_ASM_GOTO && CONFIG_JUMP_LABEL */ |
@@ -51,8 +58,11 @@ extern void arch_jump_label_transform_static(struct jump_entry *entry, | |||
51 | extern int jump_label_text_reserved(void *start, void *end); | 58 | extern int jump_label_text_reserved(void *start, void *end); |
52 | extern void jump_label_inc(struct jump_label_key *key); | 59 | extern void jump_label_inc(struct jump_label_key *key); |
53 | extern void jump_label_dec(struct jump_label_key *key); | 60 | extern void jump_label_dec(struct jump_label_key *key); |
61 | extern void jump_label_dec_deferred(struct jump_label_key_deferred *key); | ||
54 | extern bool jump_label_enabled(struct jump_label_key *key); | 62 | extern bool jump_label_enabled(struct jump_label_key *key); |
55 | extern void jump_label_apply_nops(struct module *mod); | 63 | extern void jump_label_apply_nops(struct module *mod); |
64 | extern void jump_label_rate_limit(struct jump_label_key_deferred *key, | ||
65 | unsigned long rl); | ||
56 | 66 | ||
57 | #else /* !HAVE_JUMP_LABEL */ | 67 | #else /* !HAVE_JUMP_LABEL */ |
58 | 68 | ||
@@ -68,6 +78,10 @@ static __always_inline void jump_label_init(void) | |||
68 | { | 78 | { |
69 | } | 79 | } |
70 | 80 | ||
81 | struct jump_label_key_deferred { | ||
82 | struct jump_label_key key; | ||
83 | }; | ||
84 | |||
71 | static __always_inline bool static_branch(struct jump_label_key *key) | 85 | static __always_inline bool static_branch(struct jump_label_key *key) |
72 | { | 86 | { |
73 | if (unlikely(atomic_read(&key->enabled))) | 87 | if (unlikely(atomic_read(&key->enabled))) |
@@ -85,6 +99,11 @@ static inline void jump_label_dec(struct jump_label_key *key) | |||
85 | atomic_dec(&key->enabled); | 99 | atomic_dec(&key->enabled); |
86 | } | 100 | } |
87 | 101 | ||
102 | static inline void jump_label_dec_deferred(struct jump_label_key_deferred *key) | ||
103 | { | ||
104 | jump_label_dec(&key->key); | ||
105 | } | ||
106 | |||
88 | static inline int jump_label_text_reserved(void *start, void *end) | 107 | static inline int jump_label_text_reserved(void *start, void *end) |
89 | { | 108 | { |
90 | return 0; | 109 | return 0; |
@@ -102,6 +121,11 @@ static inline int jump_label_apply_nops(struct module *mod) | |||
102 | { | 121 | { |
103 | return 0; | 122 | return 0; |
104 | } | 123 | } |
124 | |||
125 | static inline void jump_label_rate_limit(struct jump_label_key_deferred *key, | ||
126 | unsigned long rl) | ||
127 | { | ||
128 | } | ||
105 | #endif /* HAVE_JUMP_LABEL */ | 129 | #endif /* HAVE_JUMP_LABEL */ |
106 | 130 | ||
107 | #endif /* _LINUX_JUMP_LABEL_H */ | 131 | #endif /* _LINUX_JUMP_LABEL_H */ |
diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index cb44c9e75660..564769cdb473 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h | |||
@@ -1064,12 +1064,12 @@ perf_sw_event(u32 event_id, u64 nr, struct pt_regs *regs, u64 addr) | |||
1064 | } | 1064 | } |
1065 | } | 1065 | } |
1066 | 1066 | ||
1067 | extern struct jump_label_key perf_sched_events; | 1067 | extern struct jump_label_key_deferred perf_sched_events; |
1068 | 1068 | ||
1069 | static inline void perf_event_task_sched_in(struct task_struct *prev, | 1069 | static inline void perf_event_task_sched_in(struct task_struct *prev, |
1070 | struct task_struct *task) | 1070 | struct task_struct *task) |
1071 | { | 1071 | { |
1072 | if (static_branch(&perf_sched_events)) | 1072 | if (static_branch(&perf_sched_events.key)) |
1073 | __perf_event_task_sched_in(prev, task); | 1073 | __perf_event_task_sched_in(prev, task); |
1074 | } | 1074 | } |
1075 | 1075 | ||
@@ -1078,7 +1078,7 @@ static inline void perf_event_task_sched_out(struct task_struct *prev, | |||
1078 | { | 1078 | { |
1079 | perf_sw_event(PERF_COUNT_SW_CONTEXT_SWITCHES, 1, NULL, 0); | 1079 | perf_sw_event(PERF_COUNT_SW_CONTEXT_SWITCHES, 1, NULL, 0); |
1080 | 1080 | ||
1081 | if (static_branch(&perf_sched_events)) | 1081 | if (static_branch(&perf_sched_events.key)) |
1082 | __perf_event_task_sched_out(prev, next); | 1082 | __perf_event_task_sched_out(prev, next); |
1083 | } | 1083 | } |
1084 | 1084 | ||
diff --git a/kernel/events/core.c b/kernel/events/core.c index 3c1541d7a53d..3a3b1a18f490 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c | |||
@@ -128,7 +128,7 @@ enum event_type_t { | |||
128 | * perf_sched_events : >0 events exist | 128 | * perf_sched_events : >0 events exist |
129 | * perf_cgroup_events: >0 per-cpu cgroup events exist on this cpu | 129 | * perf_cgroup_events: >0 per-cpu cgroup events exist on this cpu |
130 | */ | 130 | */ |
131 | struct jump_label_key perf_sched_events __read_mostly; | 131 | struct jump_label_key_deferred perf_sched_events __read_mostly; |
132 | static DEFINE_PER_CPU(atomic_t, perf_cgroup_events); | 132 | static DEFINE_PER_CPU(atomic_t, perf_cgroup_events); |
133 | 133 | ||
134 | static atomic_t nr_mmap_events __read_mostly; | 134 | static atomic_t nr_mmap_events __read_mostly; |
@@ -2748,7 +2748,7 @@ static void free_event(struct perf_event *event) | |||
2748 | 2748 | ||
2749 | if (!event->parent) { | 2749 | if (!event->parent) { |
2750 | if (event->attach_state & PERF_ATTACH_TASK) | 2750 | if (event->attach_state & PERF_ATTACH_TASK) |
2751 | jump_label_dec(&perf_sched_events); | 2751 | jump_label_dec_deferred(&perf_sched_events); |
2752 | if (event->attr.mmap || event->attr.mmap_data) | 2752 | if (event->attr.mmap || event->attr.mmap_data) |
2753 | atomic_dec(&nr_mmap_events); | 2753 | atomic_dec(&nr_mmap_events); |
2754 | if (event->attr.comm) | 2754 | if (event->attr.comm) |
@@ -2759,7 +2759,7 @@ static void free_event(struct perf_event *event) | |||
2759 | put_callchain_buffers(); | 2759 | put_callchain_buffers(); |
2760 | if (is_cgroup_event(event)) { | 2760 | if (is_cgroup_event(event)) { |
2761 | atomic_dec(&per_cpu(perf_cgroup_events, event->cpu)); | 2761 | atomic_dec(&per_cpu(perf_cgroup_events, event->cpu)); |
2762 | jump_label_dec(&perf_sched_events); | 2762 | jump_label_dec_deferred(&perf_sched_events); |
2763 | } | 2763 | } |
2764 | } | 2764 | } |
2765 | 2765 | ||
@@ -5784,7 +5784,7 @@ done: | |||
5784 | 5784 | ||
5785 | if (!event->parent) { | 5785 | if (!event->parent) { |
5786 | if (event->attach_state & PERF_ATTACH_TASK) | 5786 | if (event->attach_state & PERF_ATTACH_TASK) |
5787 | jump_label_inc(&perf_sched_events); | 5787 | jump_label_inc(&perf_sched_events.key); |
5788 | if (event->attr.mmap || event->attr.mmap_data) | 5788 | if (event->attr.mmap || event->attr.mmap_data) |
5789 | atomic_inc(&nr_mmap_events); | 5789 | atomic_inc(&nr_mmap_events); |
5790 | if (event->attr.comm) | 5790 | if (event->attr.comm) |
@@ -6022,7 +6022,7 @@ SYSCALL_DEFINE5(perf_event_open, | |||
6022 | * - that may need work on context switch | 6022 | * - that may need work on context switch |
6023 | */ | 6023 | */ |
6024 | atomic_inc(&per_cpu(perf_cgroup_events, event->cpu)); | 6024 | atomic_inc(&per_cpu(perf_cgroup_events, event->cpu)); |
6025 | jump_label_inc(&perf_sched_events); | 6025 | jump_label_inc(&perf_sched_events.key); |
6026 | } | 6026 | } |
6027 | 6027 | ||
6028 | /* | 6028 | /* |
@@ -6868,6 +6868,9 @@ void __init perf_event_init(void) | |||
6868 | 6868 | ||
6869 | ret = init_hw_breakpoint(); | 6869 | ret = init_hw_breakpoint(); |
6870 | WARN(ret, "hw_breakpoint initialization failed with: %d", ret); | 6870 | WARN(ret, "hw_breakpoint initialization failed with: %d", ret); |
6871 | |||
6872 | /* do not patch jump label more than once per second */ | ||
6873 | jump_label_rate_limit(&perf_sched_events, HZ); | ||
6871 | } | 6874 | } |
6872 | 6875 | ||
6873 | static int __init perf_event_sysfs_init(void) | 6876 | static int __init perf_event_sysfs_init(void) |
diff --git a/kernel/jump_label.c b/kernel/jump_label.c index 66ff7109f697..51a175ab0a03 100644 --- a/kernel/jump_label.c +++ b/kernel/jump_label.c | |||
@@ -72,15 +72,46 @@ void jump_label_inc(struct jump_label_key *key) | |||
72 | jump_label_unlock(); | 72 | jump_label_unlock(); |
73 | } | 73 | } |
74 | 74 | ||
75 | void jump_label_dec(struct jump_label_key *key) | 75 | static void __jump_label_dec(struct jump_label_key *key, |
76 | unsigned long rate_limit, struct delayed_work *work) | ||
76 | { | 77 | { |
77 | if (!atomic_dec_and_mutex_lock(&key->enabled, &jump_label_mutex)) | 78 | if (!atomic_dec_and_mutex_lock(&key->enabled, &jump_label_mutex)) |
78 | return; | 79 | return; |
79 | 80 | ||
80 | jump_label_update(key, JUMP_LABEL_DISABLE); | 81 | if (rate_limit) { |
82 | atomic_inc(&key->enabled); | ||
83 | schedule_delayed_work(work, rate_limit); | ||
84 | } else | ||
85 | jump_label_update(key, JUMP_LABEL_DISABLE); | ||
86 | |||
81 | jump_label_unlock(); | 87 | jump_label_unlock(); |
82 | } | 88 | } |
83 | 89 | ||
90 | static void jump_label_update_timeout(struct work_struct *work) | ||
91 | { | ||
92 | struct jump_label_key_deferred *key = | ||
93 | container_of(work, struct jump_label_key_deferred, work.work); | ||
94 | __jump_label_dec(&key->key, 0, NULL); | ||
95 | } | ||
96 | |||
97 | void jump_label_dec(struct jump_label_key *key) | ||
98 | { | ||
99 | __jump_label_dec(key, 0, NULL); | ||
100 | } | ||
101 | |||
102 | void jump_label_dec_deferred(struct jump_label_key_deferred *key) | ||
103 | { | ||
104 | __jump_label_dec(&key->key, key->timeout, &key->work); | ||
105 | } | ||
106 | |||
107 | |||
108 | void jump_label_rate_limit(struct jump_label_key_deferred *key, | ||
109 | unsigned long rl) | ||
110 | { | ||
111 | key->timeout = rl; | ||
112 | INIT_DELAYED_WORK(&key->work, jump_label_update_timeout); | ||
113 | } | ||
114 | |||
84 | static int addr_conflict(struct jump_entry *entry, void *start, void *end) | 115 | static int addr_conflict(struct jump_entry *entry, void *start, void *end) |
85 | { | 116 | { |
86 | if (entry->code <= (unsigned long)end && | 117 | if (entry->code <= (unsigned long)end && |