This patch add the function that call perf function and bt_pages that can record the data that get from perf.
The interface is in "/sys/kernel/debug/bloodtest/perf". "on" is the switch. When it set to 1, access "test" will call perf. There are "perf_config", "perf_freq", "perf_period", "perf_type" can set the options of perf. After record, access "str" will get the record data in string. Access "cpu0/page" will get the record data in binary that is format is in "bin_format". Signed-off-by: Hui Zhu <zhu...@xiaomi.com> --- kernel/bloodtest/Makefile | 4 +- kernel/bloodtest/core.c | 76 +++--- kernel/bloodtest/internal.h | 43 +++- kernel/bloodtest/pages.c | 266 ++++++++++++++++++++ kernel/bloodtest/perf.c | 591 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 931 insertions(+), 49 deletions(-) create mode 100644 kernel/bloodtest/pages.c create mode 100644 kernel/bloodtest/perf.c diff --git a/kernel/bloodtest/Makefile b/kernel/bloodtest/Makefile index 7f289af..79b7ea0 100644 --- a/kernel/bloodtest/Makefile +++ b/kernel/bloodtest/Makefile @@ -1 +1,3 @@ -obj-y = core.o kernel_stat.o +obj-y = core.o pages.o kernel_stat.o + +obj-$(CONFIG_PERF_EVENTS) += perf.o diff --git a/kernel/bloodtest/core.c b/kernel/bloodtest/core.c index 7b39cbb..5ba800c 100644 --- a/kernel/bloodtest/core.c +++ b/kernel/bloodtest/core.c @@ -6,31 +6,17 @@ #include "internal.h" -enum bt_stat_enum bt_stat; -DEFINE_SPINLOCK(bt_lock); +DECLARE_RWSEM(bt_lock); static DECLARE_WAIT_QUEUE_HEAD(bt_wq); static struct hrtimer bt_timer; static ktime_t bt_ktime; - -static bool is_bt_stat(enum bt_stat_enum stat) -{ - unsigned long flags; - bool ret = false; - - spin_lock_irqsave(&bt_lock, flags); - if (bt_stat == stat) - ret = true; - spin_unlock_irqrestore(&bt_lock, flags); - - return ret; -} +static bool bt_timer_stop; /* This function must be called under the protection of bt_lock. */ static void bt_insert(void) { - bt_stat = bt_running; - + bt_insert_perf(); bt_insert_kernel_stat(); } @@ -38,8 +24,13 @@ static void bt_insert(void) static void bt_pullout(void) { bt_pullout_kernel_stat(); + bt_pullout_perf(); +} - bt_stat = bt_done; +/* This function must be called under the protection of bt_lock. */ +static void bt_task_pullout(void) +{ + bt_task_pullout_perf(); } /* This function must be called under the protection of bt_lock. */ @@ -50,38 +41,33 @@ static void bt_report(struct seq_file *p) static enum hrtimer_restart bt_timer_fn(struct hrtimer *data) { - spin_lock(&bt_lock); bt_pullout(); - spin_unlock(&bt_lock); - wake_up_interruptible_all(&bt_wq); + bt_timer_stop = true; + wake_up_all(&bt_wq); return HRTIMER_NORESTART; } -static int test_show(struct seq_file *p, void *v) +static int test_show(struct seq_file *p, void *unused) { - int ret = 0; + down_write(&bt_lock); - spin_lock(&bt_lock); - if (bt_stat == bt_running) - goto wait; + bt_timer_stop = false; - hrtimer_start(&bt_timer, bt_ktime, HRTIMER_MODE_REL); bt_insert(); + hrtimer_start(&bt_timer, bt_ktime, HRTIMER_MODE_REL); -wait: - spin_unlock(&bt_lock); - ret = wait_event_interruptible(bt_wq, is_bt_stat(bt_done)); - if (ret) - goto out; + wait_event(bt_wq, bt_timer_stop); - spin_lock(&bt_lock); - bt_report(p); - spin_unlock(&bt_lock); + bt_task_pullout(); + up_write(&bt_lock); -out: - return ret; + down_read(&bt_lock); + bt_report(p); + up_read(&bt_lock); + + return 0; } static int test_open(struct inode *inode, struct file *file) @@ -98,20 +84,28 @@ static int test_open(struct inode *inode, struct file *file) static int __init bt_init(void) { - struct dentry *d, *t; + int ret = -ENOMEM; + struct dentry *d = NULL, *t = NULL; d = debugfs_create_dir("bloodtest", NULL); if (!d) - return -ENOMEM; + goto out; t = debugfs_create_file("test", S_IRUSR, d, NULL, &test_fops); if (!t) - return -ENOMEM; + goto out; hrtimer_init(&bt_timer, CLOCK_REALTIME, HRTIMER_MODE_REL); bt_timer.function = bt_timer_fn; bt_ktime = ktime_set(1, 0); - return 0; + ret = bt_perf_init(d); + +out: + if (ret != 0) { + debugfs_remove(t); + debugfs_remove(d); + } + return ret; } core_initcall(bt_init); diff --git a/kernel/bloodtest/internal.h b/kernel/bloodtest/internal.h index 48faf4d..f6befc4 100644 --- a/kernel/bloodtest/internal.h +++ b/kernel/bloodtest/internal.h @@ -3,17 +3,46 @@ #include <linux/seq_file.h> -enum bt_stat_enum { - bt_empty = 0, - bt_done, - bt_running, -}; +extern struct rw_semaphore bt_lock; + +struct bt_pages { + struct page **pages; + unsigned int pages_num; + + int node; -extern enum bt_stat_enum bt_stat; -extern spinlock_t bt_lock; + unsigned int entry_size; + unsigned int entry_max_per_page; + + void *entry_next; + unsigned int entry_count_in_page; + unsigned int index; +}; +extern const struct file_operations bt_pages_bin_fops; +extern int bt_pages_entry_num_get(void *data, u64 *val); +extern int bt_pages_page_num_get(void *data, u64 *val); +extern void bt_pages_clear(struct bt_pages *pages); +extern int bt_pages_setup(struct bt_pages *pages, unsigned int entry_size, + unsigned int entry_max, int cpu); +extern void bt_pages_release(struct bt_pages *pages); +extern void *bt_pages_alloc_entry(struct bt_pages *pages); +extern void *bt_pages_get_entry(struct bt_pages *pages, unsigned int *index, + void *prev_entry); extern void bt_insert_kernel_stat(void); extern void bt_pullout_kernel_stat(void); extern void bt_report_kernel_stat(struct seq_file *p); +#ifdef CONFIG_PERF_EVENTS +extern void bt_insert_perf(void); +extern void bt_pullout_perf(void); +extern void bt_task_pullout_perf(void); +extern int bt_perf_init(struct dentry *d); +#else +static inline void bt_insert_perf(void) { } +static inline void bt_pullout_perf(void) { } +static inline void bt_task_pullout_perf(void) { } +static inline int bt_perf_init(struct dentry *d) { return 0; } +#endif + #endif /* _KERNEL_BLOODTEST_INTERNAL_H */ diff --git a/kernel/bloodtest/pages.c b/kernel/bloodtest/pages.c new file mode 100644 index 0000000..077b7b8 --- /dev/null +++ b/kernel/bloodtest/pages.c @@ -0,0 +1,266 @@ +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/fs.h> + +#include "internal.h" + +static unsigned int +bt_pages_entry_num(struct bt_pages *pages) +{ + return pages->index * pages->entry_max_per_page + + pages->entry_count_in_page; +} + +static inline unsigned int +bt_pages_page_num(struct bt_pages *pages) +{ + return pages->index + (pages->entry_count_in_page ? 1 : 0); +} + +int bt_pages_entry_num_get(void *data, u64 *val) +{ + struct bt_pages *pages = data; + + down_read(&bt_lock); + + *val = (u64)bt_pages_entry_num(pages); + + up_read(&bt_lock); + + return 0; +} + +int bt_pages_page_num_get(void *data, u64 *val) +{ + struct bt_pages *pages = data; + + down_read(&bt_lock); + + *val = (u64)bt_pages_page_num(pages); + + up_read(&bt_lock); + + return 0; +} + +void bt_pages_clear(struct bt_pages *pages) +{ + pages->entry_count_in_page = 0; + pages->index = 0; + pages->entry_next = page_address(pages->pages[0]); +} + +/* pages must be memset to zero before call this function. */ + +int bt_pages_setup(struct bt_pages *pages, + unsigned int entry_size, unsigned int entry_max, + int cpu) +{ + int i; + + pages->entry_size = entry_size; + pages->entry_max_per_page = PAGE_SIZE / entry_size; + + if (cpu >= 0) + pages->node = cpu_to_node(cpu); + else + pages->node = NUMA_NO_NODE; + + pages->pages_num = entry_max / pages->entry_max_per_page; + if (entry_max % pages->entry_max_per_page) + pages->pages_num++; + pages->pages = kmalloc_node(sizeof(struct page *) * pages->pages_num, + GFP_KERNEL | __GFP_ZERO, + pages->node); + if (!pages->pages) + return -ENOMEM; + + for (i = 0; i < pages->pages_num; i++) { + pages->pages[i] = alloc_pages_node(pages->node, + GFP_KERNEL | __GFP_ZERO, + 0); + if (!pages->pages[i]) + return -ENOMEM; + clear_page(page_address(pages->pages[i])); + } + + bt_pages_clear(pages); + + return 0; +} + +void bt_pages_release(struct bt_pages *pages) +{ + int i; + + if (!pages->pages) + return; + + for (i = 0; i < pages->pages_num; i++) { + if (pages->pages[i]) + __free_page(pages->pages[i]); + } + + kfree(pages->pages); + + memset(pages, 0, sizeof(struct bt_pages)); +} + +void * +bt_pages_alloc_entry(struct bt_pages *pages) +{ + void *ret = pages->entry_next; + + if (!ret) + goto out; + + pages->entry_count_in_page ++; + + if (pages->entry_count_in_page >= pages->entry_max_per_page) { + /* Goto next page. */ + pages->index ++; + pages->entry_count_in_page = 0; + if (pages->index >= pages->pages_num) { + /* Pages is full. */ + pages->entry_next = NULL; + } else + pages->entry_next + = page_address(pages->pages[pages->index]); + } else + pages->entry_next += pages->entry_size; + +out: + return ret; +} + +void * +bt_pages_get_entry(struct bt_pages *pages, unsigned int *index, void *prev_entry) +{ + unsigned int max_size; + void *last_entry; + +get_entry: + if (*index > pages->index) + return NULL; + + if (*index == pages->index) + max_size = pages->entry_count_in_page * pages->entry_size; + else + max_size = pages->entry_max_per_page * pages->entry_size; + + if (max_size == 0) + return NULL; + + if (!prev_entry) + return page_address(pages->pages[*index]); + + last_entry = (void *)(((unsigned long)prev_entry & PAGE_MASK) + + max_size - pages->entry_size); + + if (prev_entry >= last_entry) { + /* Goto use next page. */ + (*index)++; + prev_entry = NULL; + goto get_entry; + } + + return prev_entry + pages->entry_size; +} + +static int bt_pages_bin_open(struct inode *inode, struct file *file) +{ + down_read(&bt_lock); + + file->private_data = inode->i_private; + + return 0; +} + +static int bt_pages_bin_release(struct inode *inode, struct file *file) +{ + up_read(&bt_lock); + + return 0; +} + +ssize_t bt_pages_bin_read(struct file *file, char __user *buf, + size_t len, loff_t *ppos) +{ + pgoff_t index; + loff_t offset; + size_t copied = 0; + struct bt_pages *pages; + + if (!access_ok(VERIFY_WRITE, buf, len)) + return -EFAULT; + + pages = file->private_data; + offset = *ppos; + index = offset >> PAGE_SHIFT; + offset &= ~PAGE_MASK; + + for (; index <= pages->index; index++) { + size_t copy_size; + + if (len == 0) + break; + + /* Get copy_size. */ + if (index == pages->index) { + copy_size = pages->entry_count_in_page * + pages->entry_size; + if (copy_size == 0 || copy_size <= offset) + break; + copy_size -= offset; + } else + copy_size = PAGE_SIZE - offset; + if (copy_size > len) + copy_size = len; + + if (__copy_to_user(buf, + page_address(pages->pages[index]) + offset, + copy_size)) + return -EFAULT; + + buf += copy_size; + len -= copy_size; + copied += copy_size; + offset = 0; + } + + *ppos += copied; + return copied; +} + +static int bt_pages_bin_fault(struct vm_fault *vmf) +{ + struct file *file = vmf->vma->vm_file; + struct bt_pages *pages = file->private_data; + + if (vmf->pgoff >= bt_pages_page_num(pages)) + return VM_FAULT_SIGBUS; + + vmf->page = pages->pages[vmf->pgoff]; + return 0; +} + +static const struct vm_operations_struct bt_pages_bin_vm_ops = { + .fault = bt_pages_bin_fault, +}; + +int bt_pages_bin_mmap(struct file *file, struct vm_area_struct *vma) +{ + if ((vma->vm_flags & VM_SHARED) && (vma->vm_flags & VM_MAYWRITE)) + return -EINVAL; + + vma->vm_ops = &bt_pages_bin_vm_ops; + return 0; +} + +const struct file_operations bt_pages_bin_fops = { + .open = bt_pages_bin_open, + .release = bt_pages_bin_release, + .read = bt_pages_bin_read, + .mmap = bt_pages_bin_mmap, +}; diff --git a/kernel/bloodtest/perf.c b/kernel/bloodtest/perf.c new file mode 100644 index 0000000..cf23844 --- /dev/null +++ b/kernel/bloodtest/perf.c @@ -0,0 +1,591 @@ +#include <linux/perf_event.h> +#include <linux/debugfs.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/stddef.h> + +#include "internal.h" + +struct perf_entry { + u64 pc; + u8 is_user; + s16 oom_score_adj; + char comm[TASK_COMM_LEN]; +}; + +static bool perf_on; +static bool perf_use_freq = true; +static unsigned int perf_type = PERF_TYPE_SOFTWARE; +static unsigned int perf_config = PERF_COUNT_SW_CPU_CLOCK; +static unsigned int perf_period = 1000; +static unsigned int perf_freq = 200; +static unsigned int rec_max = 210; +struct perf_rec { + struct perf_event *event; + + atomic_t is_running; + + /* Record entry. */ + struct bt_pages pages; + unsigned int drop; + + struct dentry *dir; + struct dentry *number_fs; + struct dentry *page_fs; + struct dentry *drop_fs; + struct dentry *bin_fs; +} __percpu *percpu_rec; + +struct dentry *perf_dir; +struct dentry *perf_str_dir; + +static int perf_number_get(void *data, u64 *val) +{ + unsigned int *number_point = data; + + down_read(&bt_lock); + + *val = (u64)*number_point; + + up_read(&bt_lock); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(perf_number_fops, perf_number_get, NULL, "%llu\n"); + +static void perf_overflow_handler(struct perf_event *event, + struct perf_sample_data *data, + struct pt_regs *regs) +{ + struct perf_rec *rec = this_cpu_ptr(percpu_rec); + struct perf_entry *entry; + + if (!atomic_read(&rec->is_running)) + return; + + entry = bt_pages_alloc_entry(&rec->pages); + if (entry) { + struct task_struct *p; + + entry->pc = instruction_pointer(regs); + entry->is_user = user_mode(regs); + + rcu_read_lock(); + if (thread_group_leader(current)) + p = current; + else + p = current->group_leader; + strncpy(entry->comm, p->comm, sizeof(p->comm)); + entry->oom_score_adj = p->signal->oom_score_adj; + rcu_read_unlock(); + } else + rec->drop++; +} + +static int perf_event_creat(void) +{ + int ret = 0; + struct perf_event_attr attr; + int cpu; + + /* Setup attr. */ + memset(&attr, 0, sizeof(attr)); + attr.type = perf_type; + attr.config = perf_config; + attr.disabled = true; + attr.pinned = true; + attr.freq = perf_use_freq; + if (attr.freq) { + attr.sample_freq = perf_freq; + pr_info("bloodtest: perf freq %llu\n", + (unsigned long long) attr.sample_freq); + } else { + attr.sample_period = perf_period; + pr_info("bloodtest: perf period %llu\n", + (unsigned long long) attr.sample_period); + } + attr.size = sizeof(attr); + + for_each_possible_cpu(cpu) { + struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu); + + /* Set event. */ + rec->event = perf_event_create_kernel_counter(&attr, cpu, NULL, + perf_overflow_handler, + NULL); + if (IS_ERR(rec->event)) { + ret = PTR_ERR(rec->event); + rec->event = NULL; + break; + } + } + + return ret; +} + +static void perf_event_release(void) +{ + int cpu; + + for_each_possible_cpu(cpu) { + struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu); + + if (rec->event) { + perf_event_release_kernel(rec->event); + rec->event = NULL; + } + } +} + +DEFINE_SIMPLE_ATTRIBUTE(perf_entry_num_fops, bt_pages_entry_num_get, NULL, + "%llu\n"); + +DEFINE_SIMPLE_ATTRIBUTE(perf_page_num_fops, bt_pages_page_num_get, NULL, + "%llu\n"); + +static int perf_str_show(struct seq_file *p, void *unused) +{ + int cpu; + + for_each_possible_cpu(cpu) { + struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu); + struct perf_entry *entry = NULL; + unsigned int index = 0; + + seq_printf(p, "cpu%d\n", cpu); + while (1) { + char buffer[KSYM_SYMBOL_LEN]; + + entry = bt_pages_get_entry(&rec->pages, &index, + entry); + if (!entry) + break; + + if (entry->is_user) + buffer[0] = '\0'; + else + sprint_symbol(buffer, (unsigned long)entry->pc); + + seq_printf(p, "%c %s\t\t%d\t%s[0x%llx]\n", + entry->is_user ? 'u' : 'k', + entry->comm, + (int)entry->oom_score_adj, + buffer, + (unsigned long long)entry->pc); + } + seq_puts(p, "\n"); + } + + return 0; +} + +static int perf_str_open(struct inode *inode, struct file *file) +{ + down_read(&bt_lock); + + return single_open(file, perf_str_show, NULL); +} + +static int perf_str_release(struct inode *inode, struct file *file) +{ + int ret = single_release(inode, file); + + up_read(&bt_lock); + return ret; +} + +static const struct file_operations perf_str_fops = { + .open = perf_str_open, + .read = seq_read, + .llseek = seq_lseek, + .release = perf_str_release, +}; + +static int +perf_pages_alloc(void) +{ + int cpu; + + for_each_possible_cpu(cpu) { + int ret; + struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu); + + ret = bt_pages_setup(&rec->pages, + sizeof(struct perf_entry), + rec_max, cpu); + if (ret) + return ret; + } + + return 0; +} + +static void +perf_pages_release(void) +{ + int cpu; + + for_each_possible_cpu(cpu) { + struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu); + + bt_pages_release(&rec->pages); + } +} + +static int perf_alloc(void) +{ + int cpu, ret; + + percpu_rec = alloc_percpu(struct perf_rec); + if (!percpu_rec) + return -ENOMEM; + + /* Init rec. */ + for_each_possible_cpu(cpu) { + struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu); + + memset(rec, 0, sizeof(struct perf_rec)); + } + + ret = perf_pages_alloc(); + if (ret) + return ret; + + for_each_possible_cpu(cpu) { + struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu); + char name[10]; + + snprintf(name, 10, "cpu%d", cpu); + rec->dir = debugfs_create_dir(name, perf_dir); + if (!rec->dir) + return -ENOMEM; + + rec->number_fs = debugfs_create_file("number", + S_IRUSR, + rec->dir, + &rec->pages, + &perf_entry_num_fops); + if (!rec->number_fs) + return -ENOMEM; + + rec->page_fs = debugfs_create_file("page", + S_IRUSR, + rec->dir, + &rec->pages, + &perf_page_num_fops); + if (!rec->page_fs) + return -ENOMEM; + + rec->drop_fs = debugfs_create_file("drop", + S_IRUSR, + rec->dir, + &rec->drop, + &perf_number_fops); + if (!rec->drop_fs) + return -ENOMEM; + + rec->bin_fs = debugfs_create_file("bin", + S_IRUSR, + rec->dir, + &rec->pages, + &bt_pages_bin_fops); + if (!rec->bin_fs) + return -ENOMEM; + } + + perf_str_dir = debugfs_create_file("str", + S_IRUSR, + perf_dir, + NULL, + &perf_str_fops); + + return perf_event_creat(); +} + +static void perf_release(void) +{ + int cpu; + + if (!percpu_rec) + return; + + debugfs_remove(perf_str_dir); + perf_str_dir = NULL; + + for_each_possible_cpu(cpu) { + struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu); + + debugfs_remove(rec->number_fs); + debugfs_remove(rec->page_fs); + debugfs_remove(rec->drop_fs); + debugfs_remove(rec->bin_fs); + debugfs_remove(rec->dir); + } + + perf_pages_release(); + + perf_event_release(); + + free_percpu(percpu_rec); + percpu_rec = NULL; +} + +static int perf_on_set(void *data, u64 val) +{ + int ret = 0; + + down_write(&bt_lock); + + if (!perf_on && val) { + ret = perf_alloc(); + if (ret) { + perf_release(); + goto out; + } + + perf_on = true; + } else if (perf_on && !val) { + perf_release(); + + perf_on = false; + } + +out: + up_write(&bt_lock); + return ret; +} + +static int perf_on_get(void *data, u64 *val) +{ + down_read(&bt_lock); + + if (perf_on) + *val = 1; + else + *val = 0; + + up_read(&bt_lock); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(perf_on_fops, perf_on_get, perf_on_set, "%llu\n"); + +static int perf_event_set(void *data, u64 val) +{ + unsigned int *number_point = data; + int ret = 0; + + down_write(&bt_lock); + + *number_point = (unsigned int)val; + + if (number_point == &perf_freq) + perf_use_freq = true; + else if (number_point == &perf_period) + perf_use_freq = false; + + if (perf_on) { + perf_event_release(); + ret = perf_event_creat(); + if (ret) { + pr_err("bloodtest: alloc perf get error %d\n", ret); + perf_release(); + perf_on = false; + } + } + + up_write(&bt_lock); + return ret; +} + +DEFINE_SIMPLE_ATTRIBUTE(perf_event_fops, + perf_number_get, + perf_event_set, "%llu\n"); + +static int perf_bin_format_show(struct seq_file *p, void *unused) +{ +#ifdef CONFIG_CPU_BIG_ENDIAN + seq_puts(p, "big-endian\n"); +#else + seq_puts(p, "little-endian\n"); +#endif + seq_printf(p, "size:%lu\n", sizeof(struct perf_entry)); + + seq_printf(p, "pc format:u64 unsigned offset:%lu size:%lu\n", + offsetof(struct perf_entry, pc), sizeof(u64)); + seq_printf(p, "is_user format:u8 unsigned offset:%lu size:%lu\n", + offsetof(struct perf_entry, is_user), sizeof(u8)); + seq_printf(p, "oom_score_adj format:s16 signed offset:%lu size:%lu\n", + offsetof(struct perf_entry, oom_score_adj), sizeof(s16)); + seq_printf(p, "comm format:char[] signed offset:%lu size:%d\n", + offsetof(struct perf_entry, comm), TASK_COMM_LEN); + + return 0; +} + +static int perf_bin_format_open(struct inode *inode, struct file *file) +{ + return single_open(file, perf_bin_format_show, NULL); +} + +static const struct file_operations perf_bin_format_fops = { + .open = perf_bin_format_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int rec_max_set(void *data, u64 val) +{ + int ret = 0; + + down_write(&bt_lock); + if (rec_max == val) + goto out; + + rec_max = val; + + if (!perf_on) + goto out; + + perf_pages_release(); + ret = perf_pages_alloc(); + if (ret) { + perf_release(); + perf_on = false; + goto out; + } + +out: + up_write(&bt_lock); + return ret; +} + +DEFINE_SIMPLE_ATTRIBUTE(rec_max_fops, + perf_number_get, + rec_max_set, "%llu\n"); + +void +bt_insert_perf(void) +{ + int cpu; + + if (!perf_on) + return; + + for_each_possible_cpu(cpu) { + struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu); + + /* The perf is not running. So doesn't lock. */ + bt_pages_clear(&rec->pages); + rec->drop = 0; + atomic_set(&rec->is_running, 1); + perf_event_enable(rec->event); + } +} + +void +bt_pullout_perf(void) +{ + int cpu; + + if (!perf_on) + return; + + for_each_possible_cpu(cpu) { + struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu); + + /* This function is called by hrtimer that irq is disabled. + Function perf_event_disable will call + smp_call_function_single that should not run when irq is + disabled. + So call perf_event_disable later. */ + atomic_set(&rec->is_running, 0); + } +} + +void +bt_task_pullout_perf(void) +{ + int cpu; + + if (!perf_on) + return; + + for_each_possible_cpu(cpu) { + struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu); + + perf_event_disable(rec->event); + } +} + + + +int __init bt_perf_init(struct dentry *f) +{ + int ret = -ENOMEM; + struct dentry *on = NULL, *format = NULL, *period = NULL, + *freq = NULL, *max = NULL, *type = NULL, *config = NULL; + + perf_dir = debugfs_create_dir("perf", f); + if (!perf_dir) + goto out; + + on = debugfs_create_file("on", S_IRUSR | S_IWUSR, perf_dir, NULL, + &perf_on_fops); + if (!on) + goto out; + + format = debugfs_create_file("bin_format", S_IRUSR, + perf_dir, NULL, + &perf_bin_format_fops); + if (!format) + goto out; + + period = debugfs_create_file("perf_period", S_IRUSR | S_IWUSR, perf_dir, + &perf_period, + &perf_event_fops); + if (!period) + goto out; + + freq = debugfs_create_file("perf_freq", S_IRUSR | S_IWUSR, perf_dir, + &perf_freq, + &perf_event_fops); + if (!freq) + goto out; + + type = debugfs_create_file("perf_type", S_IRUSR | S_IWUSR, perf_dir, + &perf_type, + &perf_event_fops); + if (!type) + goto out; + + config = debugfs_create_file("perf_config", S_IRUSR | S_IWUSR, perf_dir, + &perf_config, + &perf_event_fops); + if (!config) + goto out; + + max = debugfs_create_file("rec_max", S_IRUSR | S_IWUSR, perf_dir, + &rec_max, + &rec_max_fops); + if (!max) + goto out; + + ret = 0; +out: + if (ret) { + debugfs_remove(on); + debugfs_remove(format); + debugfs_remove(period); + debugfs_remove(freq); + debugfs_remove(max); + debugfs_remove(type); + debugfs_remove(config); + debugfs_remove(perf_dir); + } + return ret; +} -- 1.9.1