diff --git a/mm/kmemtrace.c b/mm/kmemtrace.c index 4b33ace..b7800e0 100644 --- a/mm/kmemtrace.c +++ b/mm/kmemtrace.c @@ -15,10 +15,28 @@ #define KMEMTRACE_SUBBUF_SIZE (8192 * sizeof(struct kmemtrace_event)) #define KMEMTRACE_N_SUBBUFS 20 +/* utt forward declarations */ + +static unsigned int tracing, event_mask = ~0U; + +static int destroy_trace_controls(void); +static int create_trace_controls(void); +static int create_channel(void); +static int destroy_channel(void); +static int start_trace(void); +static int stop_trace(void); + +static struct rchan_callbacks relay_callbacks; + +static atomic_t dropped; + +/* end utt forward declarations */ + static struct rchan *kmemtrace_chan; -static u32 kmemtrace_buf_overruns; -static unsigned int kmemtrace_n_subbufs; +static int buf_size = KMEMTRACE_SUBBUF_SIZE; +static int buf_nr = KMEMTRACE_N_SUBBUFS; + #ifdef CONFIG_KMEMTRACE_DEFAULT_ENABLED static unsigned int kmemtrace_enabled = 1; #else @@ -38,6 +56,9 @@ static void kmemtrace_probe_alloc(void *probe_data, void *call_data, unsigned long flags; struct kmemtrace_event ev; + if (unlikely(!(tracing & KMEMTRACE_EVENT_ALLOC))) + return; + /* * Don't convert this to use structure initializers, * C99 does not guarantee the rvalues evaluation order. @@ -69,6 +90,9 @@ static void kmemtrace_probe_free(void *probe_data, void *call_data, unsigned long flags; struct kmemtrace_event ev; + if (unlikely(!(tracing & KMEMTRACE_EVENT_FREE))) + return; + /* * Don't convert this to use structure initializers, * C99 does not guarantee the rvalues evaluation order. @@ -90,45 +114,7 @@ static void kmemtrace_probe_free(void *probe_data, void *call_data, local_irq_restore(flags); } -static struct dentry * -kmemtrace_create_buf_file(const char *filename, struct dentry *parent, - int mode, struct rchan_buf *buf, int *is_global) -{ - return debugfs_create_file(filename, mode, parent, buf, - &relay_file_operations); -} - -static int kmemtrace_remove_buf_file(struct dentry *dentry) -{ - debugfs_remove(dentry); - - return 0; -} - -static int kmemtrace_count_overruns(struct rchan_buf *buf, - void *subbuf, void *prev_subbuf, - size_t prev_padding) -{ - if (relay_buf_full(buf)) { - /* - * We know it's not SMP-safe, but neither - * debugfs_create_u32() is. - */ - kmemtrace_buf_overruns++; - return 0; - } - - return 1; -} - -static struct rchan_callbacks relay_callbacks = { - .create_buf_file = kmemtrace_create_buf_file, - .remove_buf_file = kmemtrace_remove_buf_file, - .subbuf_start = kmemtrace_count_overruns, -}; - static struct dentry *kmemtrace_dir; -static struct dentry *kmemtrace_overruns_dentry; static struct dentry *kmemtrace_abi_version_dentry; static void kmemtrace_cleanup(void) @@ -138,14 +124,11 @@ static void kmemtrace_cleanup(void) if (kmemtrace_abi_version_dentry) debugfs_remove(kmemtrace_abi_version_dentry); - if (kmemtrace_overruns_dentry) - debugfs_remove(kmemtrace_overruns_dentry); - relay_close(kmemtrace_chan); - kmemtrace_chan = NULL; + destroy_channel(); + + destroy_trace_controls(); - if (kmemtrace_dir) - debugfs_remove(kmemtrace_dir); } static int __init kmemtrace_setup_late(void) @@ -153,23 +136,22 @@ static int __init kmemtrace_setup_late(void) if (!kmemtrace_chan) goto failed; - kmemtrace_dir = debugfs_create_dir("kmemtrace", NULL); - if (!kmemtrace_dir) + if (create_trace_controls()) { + printk("Unable to create trace controls\n"); goto cleanup; + } kmemtrace_abi_version_dentry = debugfs_create_u32("abi_version", S_IRUSR, kmemtrace_dir, &kmemtrace_abi_version); - kmemtrace_overruns_dentry = - debugfs_create_u32("total_overruns", S_IRUSR, - kmemtrace_dir, &kmemtrace_buf_overruns); - if (!kmemtrace_overruns_dentry || !kmemtrace_abi_version_dentry) - goto cleanup; - if (relay_late_setup_files(kmemtrace_chan, "cpu", kmemtrace_dir)) + if (relay_late_setup_files(kmemtrace_chan, "trace", kmemtrace_dir)) goto cleanup; - printk(KERN_INFO "kmemtrace: fully up.\n"); + /* done with early tracing. userland has to re-start it wants more */ + stop_trace(); + + printk(KERN_INFO "kmemtrace: fully ready.\n"); return 0; @@ -198,7 +180,7 @@ early_param("kmemtrace.enable", kmemtrace_set_boot_enabled); static int __init kmemtrace_set_subbufs(char *str) { - get_option(&str, &kmemtrace_n_subbufs); + get_option(&str, &buf_nr); return 0; } early_param("kmemtrace.subbufs", kmemtrace_set_subbufs); @@ -207,20 +189,21 @@ void kmemtrace_init(void) { int err; + atomic_set(&dropped, 0); + if (!kmemtrace_enabled) return; - if (!kmemtrace_n_subbufs) - kmemtrace_n_subbufs = KMEMTRACE_N_SUBBUFS; - - kmemtrace_chan = relay_open(NULL, NULL, KMEMTRACE_SUBBUF_SIZE, - kmemtrace_n_subbufs, &relay_callbacks, + kmemtrace_chan = relay_open(NULL, NULL, buf_size, + buf_nr, &relay_callbacks, NULL); if (!kmemtrace_chan) { printk(KERN_INFO "kmemtrace: could not open relay channel\n"); return; } + tracing = ~0U; + err = marker_probe_register("kmemtrace_alloc", "type_id %d " "call_site %lu ptr %lu " "bytes_req %lu bytes_alloc %lu " @@ -231,6 +214,7 @@ void kmemtrace_init(void) err = marker_probe_register("kmemtrace_free", "type_id %d " "call_site %lu ptr %lu", kmemtrace_probe_free, NULL); + if (err) goto probe_fail; @@ -242,3 +226,538 @@ probe_fail: kmemtrace_cleanup(); } + +/* all boilerplate below here */ + +/* channel setup/teardown */ + +static struct dentry *tree_root; +static DEFINE_MUTEX(tree_mutex); +static unsigned int root_users; + +void remove_root(void) +{ + if (tree_root) { + debugfs_remove(tree_root); + tree_root = NULL; + } +} + +void remove_tree(struct dentry *dir) +{ + mutex_lock(&tree_mutex); + debugfs_remove(dir); + if (--root_users == 0) + remove_root(); + mutex_unlock(&tree_mutex); +} + +struct dentry *create_tree(const char *app_name, const char *channel_name) +{ + struct dentry *dir = NULL; + int created = 0; + + mutex_lock(&tree_mutex); + + if (!tree_root) { + tree_root = debugfs_create_dir(app_name, NULL); + if (!tree_root) + goto err; + created = 1; + } + + dir = debugfs_create_dir(channel_name, tree_root); + if (dir) + root_users++; + else { + /* Delete root only if we created it */ + if (created) + remove_root(); + } + +err: + mutex_unlock(&tree_mutex); + return dir; +} + +/* + * Keep track of how many times we encountered a full subbuffer, to aid + * the user space app in telling how many lost events there were. + */ +static int subbuf_start_callback(struct rchan_buf *buf, void *subbuf, + void *prev_subbuf, size_t prev_padding) +{ + if (!relay_buf_full(buf)) + return 1; + + atomic_inc(&dropped); + + return 0; +} + +static int remove_buf_file_callback(struct dentry *dentry) +{ + debugfs_remove(dentry); + + return 0; +} + +static struct dentry *create_buf_file_callback(const char *filename, + struct dentry *parent, + int mode, + struct rchan_buf *buf, + int *is_global) +{ + return debugfs_create_file(filename, mode, parent, buf, + &relay_file_operations); +} + +static struct rchan_callbacks relay_callbacks = { + .subbuf_start = subbuf_start_callback, + .create_buf_file = create_buf_file_callback, + .remove_buf_file = remove_buf_file_callback, +}; + +/* + * Control files + */ + +static ssize_t buf_size_read(struct file *filp, char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[16]; + + snprintf(buf, sizeof(buf), "%d\n", buf_size); + + return simple_read_from_buffer(buffer, count, ppos, + buf, strlen(buf)); +} + +static ssize_t buf_size_write(struct file *filp, const char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[16]; + char *tmp; + size_t size; + + if (count > sizeof(buf)) + return -EINVAL; + + memset(buf, 0, sizeof(buf)); + + if (copy_from_user(buf, buffer, count)) + return -EFAULT; + + size = simple_strtol(buf, &tmp, 10); + if (tmp == buf) + return -EINVAL; + + buf_size = size; + + return count; +} + +static struct file_operations buf_size_fops = { + .owner = THIS_MODULE, + .read = buf_size_read, + .write = buf_size_write, +}; + +static ssize_t buf_nr_read(struct file *filp, char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[16]; + + snprintf(buf, sizeof(buf), "%d\n", buf_nr); + + return simple_read_from_buffer(buffer, count, ppos, + buf, strlen(buf)); +} + +static ssize_t buf_nr_write(struct file *filp, const char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[16]; + char *tmp; + size_t n; + + if (count > sizeof(buf)) + return -EINVAL; + + memset(buf, 0, sizeof(buf)); + + if (copy_from_user(buf, buffer, count)) + return -EFAULT; + + n = simple_strtol(buf, &tmp, 10); + if (tmp == buf) + return -EINVAL; + + buf_nr = n; + + return count; +} + +static struct file_operations buf_nr_fops = { + .owner = THIS_MODULE, + .read = buf_nr_read, + .write = buf_nr_write, +}; + +static ssize_t create_read(struct file *filp, char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[16]; + + snprintf(buf, sizeof(buf), "%d\n", !!kmemtrace_chan); + + return simple_read_from_buffer(buffer, count, ppos, + buf, strlen(buf)); +} + +static ssize_t create_write(struct file *filp, const char __user *buffer, + size_t count, loff_t *ppos) +{ + int err = create_channel(); + + if (err) + return err; + + return count; +} + +static struct file_operations create_fops = { + .owner = THIS_MODULE, + .read = create_read, + .write = create_write, +}; + +static ssize_t destroy_read(struct file *filp, char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[16]; + + snprintf(buf, sizeof(buf), "%d\n", !kmemtrace_chan); + + return simple_read_from_buffer(buffer, count, ppos, + buf, strlen(buf)); +} + +static ssize_t destroy_write(struct file *filp, const char __user *buffer, + size_t count, loff_t *ppos) +{ + int err = destroy_channel(); + + if (err) + return err; + + return count; +} + +static struct file_operations destroy_fops = { + .owner = THIS_MODULE, + .read = destroy_read, + .write = destroy_write, +}; + +static ssize_t event_mask_read(struct file *filp, char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[16]; + + snprintf(buf, sizeof(buf), "%x\n", event_mask); + + return simple_read_from_buffer(buffer, count, ppos, + buf, strlen(buf)); +} + +static ssize_t event_mask_write(struct file *filp, const char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[16]; + char *tmp; + unsigned int mask; + + if (count > sizeof(buf)) + return -EINVAL; + + memset(buf, 0, sizeof(buf)); + + if (copy_from_user(buf, buffer, count)) + return -EFAULT; + + mask = simple_strtol(buf, &tmp, 10); + if (tmp == buf) + return -EINVAL; + + if (!mask) + return -EINVAL; + + event_mask = mask; + + return count; +} + +static struct file_operations event_mask_fops = { + .owner = THIS_MODULE, + .read = event_mask_read, + .write = event_mask_write, +}; + +static ssize_t start_read(struct file *filp, char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[16]; + + snprintf(buf, sizeof(buf), "%d\n", !!tracing); + + return simple_read_from_buffer(buffer, count, ppos, + buf, strlen(buf)); +} + +static ssize_t start_write(struct file *filp, const char __user *buffer, + size_t count, loff_t *ppos) +{ + int err = start_trace(); + + if (err) + return err; + + return count; +} + +static struct file_operations start_fops = { + .owner = THIS_MODULE, + .read = start_read, + .write = start_write, +}; + +static ssize_t stop_read(struct file *filp, char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[16]; + + snprintf(buf, sizeof(buf), "%d\n", !tracing); + + return simple_read_from_buffer(buffer, count, ppos, + buf, strlen(buf)); +} + +static ssize_t stop_write(struct file *filp, const char __user *buffer, + size_t count, loff_t *ppos) +{ + int err = stop_trace(); + + if (err) + return err; + + return count; +} + +static struct file_operations stop_fops = { + .owner = THIS_MODULE, + .read = stop_read, + .write = stop_write, +}; + +static ssize_t dropped_read(struct file *filp, char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[16]; + + snprintf(buf, sizeof(buf), "%u\n", atomic_read(&dropped)); + + return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf)); +} + +static struct file_operations dropped_fops = { + .owner = THIS_MODULE, + .read = dropped_read, +}; + +/* channel-management control files */ +static struct dentry *buf_size_file; +static struct dentry *buf_nr_file; +static struct dentry *create_file; +static struct dentry *destroy_file; +static struct dentry *event_mask_file; +static struct dentry *start_file; +static struct dentry *stop_file; +static struct dentry *dropped_file; + +static int destroy_trace_controls(void) +{ + if (kmemtrace_chan) + return -EBUSY; + + if (buf_size_file) { + debugfs_remove(buf_size_file); + buf_size_file = NULL; + } + + if (buf_nr_file) { + debugfs_remove(buf_nr_file); + buf_nr_file = NULL; + } + + if (create_file) { + debugfs_remove(create_file); + create_file = NULL; + } + + if (destroy_file) { + debugfs_remove(destroy_file); + destroy_file = NULL; + } + + if (event_mask_file) { + debugfs_remove(event_mask_file); + event_mask_file = NULL; + } + + if (start_file) { + debugfs_remove(start_file); + start_file = NULL; + } + + if (stop_file) { + debugfs_remove(stop_file); + stop_file = NULL; + } + + if (dropped_file) { + debugfs_remove(dropped_file); + dropped_file = NULL; + } + + return 0; +} + +static int create_trace_controls(void) +{ + int ret = -ENOENT; + kmemtrace_dir = create_tree("kmem", "trace"); + if (!kmemtrace_dir) + goto err; + + ret = -EIO; + buf_size_file = debugfs_create_file("buf_size", 0, kmemtrace_dir, + NULL, &buf_size_fops); + if (!buf_size_file) { + printk("Couldn't create relay control file 'buf_size'.\n"); + goto err; + } + + buf_nr_file = debugfs_create_file("buf_nr", 0, kmemtrace_dir, + NULL, &buf_nr_fops); + if (!buf_nr_file) { + printk("Couldn't create relay control file 'buf_nr'.\n"); + goto err; + } + + create_file = debugfs_create_file("create", 0, kmemtrace_dir, + NULL, &create_fops); + if (!create_file) { + printk("Couldn't create relay control file 'create'.\n"); + goto err; + } + + destroy_file = debugfs_create_file("destroy", 0, kmemtrace_dir, + NULL, &destroy_fops); + if (!destroy_file) { + printk("Couldn't create relay control file 'destroy'.\n"); + goto err; + } + + event_mask_file = debugfs_create_file("event_mask", 0, kmemtrace_dir, + NULL, &event_mask_fops); + if (!event_mask_file) { + printk("Couldn't create relay control file 'event-mask'.\n"); + goto err; + } + + start_file = debugfs_create_file("start", 0, kmemtrace_dir, + NULL, &start_fops); + if (!start_file) { + printk("Couldn't create relay control file 'start'.\n"); + goto err; + } + + stop_file = debugfs_create_file("stop", 0, kmemtrace_dir, + NULL, &stop_fops); + if (!stop_file) { + printk("Couldn't create relay control file 'stop'.\n"); + goto err; + } + + dropped_file = debugfs_create_file("dropped", 0444, kmemtrace_dir, + NULL, &dropped_fops); + if (!dropped_file) + goto err; + + return 0; +err: + return destroy_trace_controls(); +} + +/* + * End control files + */ + +static int create_channel(void) +{ + if (kmemtrace_chan) + return -EINVAL; + + kmemtrace_chan = relay_open("trace", kmemtrace_dir, buf_size, buf_nr, + &relay_callbacks, NULL); + if (!kmemtrace_chan) + return -ENOMEM; + + return 0; +} + +static int destroy_channel(void) +{ + if (!kmemtrace_chan) + return -EINVAL; + + relay_close(kmemtrace_chan); + kmemtrace_chan = NULL; + + return 0; +} + +static int start_trace(void) +{ + if (tracing) + return -EINVAL; + + if (!kmemtrace_chan) + return -EINVAL; + + atomic_set(&dropped, 0); + + tracing = event_mask; + + return 0; +} + +static int stop_trace(void) +{ + if (!tracing) + return -EINVAL; + + if (!kmemtrace_chan) + return -EINVAL; + + tracing = 0; + + relay_flush(kmemtrace_chan); + + return 0; +} + +/* end boilerplate */ + +