blob: 0573b5080cc49249ebaadf19c9b7981e61cd0c99 [file] [log] [blame]
/*
* Copyright (C) 2008 Pekka Enberg, Eduard - Gabriel Munteanu
*
* This file is released under GPL version 2.
*/
#include <linux/string.h>
#include <linux/debugfs.h>
#include <linux/relay.h>
#include <linux/module.h>
#include <linux/marker.h>
#include <linux/gfp.h>
#include <trace/kmemtrace.h>
#define KMEMTRACE_SUBBUF_SIZE 524288
#define KMEMTRACE_DEF_N_SUBBUFS 20
static struct rchan *kmemtrace_chan;
static u32 kmemtrace_buf_overruns;
static unsigned int kmemtrace_n_subbufs;
/* disabled by default */
static unsigned int kmemtrace_enabled;
/*
* The sequence number is used for reordering kmemtrace packets
* in userspace, since they are logged as per-CPU data.
*
* atomic_t should always be a 32-bit signed integer. Wraparound is not
* likely to occur, but userspace can deal with it by expecting a certain
* sequence number in the next packet that will be read.
*/
static atomic_t kmemtrace_seq_num;
#define KMEMTRACE_ABI_VERSION 1
static u32 kmemtrace_abi_version __read_mostly = KMEMTRACE_ABI_VERSION;
enum kmemtrace_event_id {
KMEMTRACE_EVENT_ALLOC = 0,
KMEMTRACE_EVENT_FREE,
};
struct kmemtrace_event {
u8 event_id;
u8 type_id;
u16 event_size;
s32 seq_num;
u64 call_site;
u64 ptr;
} __attribute__ ((__packed__));
struct kmemtrace_stats_alloc {
u64 bytes_req;
u64 bytes_alloc;
u32 gfp_flags;
s32 numa_node;
} __attribute__ ((__packed__));
static void kmemtrace_probe_alloc(void *probe_data, void *call_data,
const char *format, va_list *args)
{
unsigned long flags;
struct kmemtrace_event *ev;
struct kmemtrace_stats_alloc *stats;
void *buf;
local_irq_save(flags);
buf = relay_reserve(kmemtrace_chan,
sizeof(struct kmemtrace_event) +
sizeof(struct kmemtrace_stats_alloc));
if (!buf)
goto failed;
/*
* Don't convert this to use structure initializers,
* C99 does not guarantee the rvalues evaluation order.
*/
ev = buf;
ev->event_id = KMEMTRACE_EVENT_ALLOC;
ev->type_id = va_arg(*args, int);
ev->event_size = sizeof(struct kmemtrace_event) +
sizeof(struct kmemtrace_stats_alloc);
ev->seq_num = atomic_add_return(1, &kmemtrace_seq_num);
ev->call_site = va_arg(*args, unsigned long);
ev->ptr = va_arg(*args, unsigned long);
stats = buf + sizeof(struct kmemtrace_event);
stats->bytes_req = va_arg(*args, unsigned long);
stats->bytes_alloc = va_arg(*args, unsigned long);
stats->gfp_flags = va_arg(*args, unsigned long);
stats->numa_node = va_arg(*args, int);
failed:
local_irq_restore(flags);
}
static void kmemtrace_probe_free(void *probe_data, void *call_data,
const char *format, va_list *args)
{
unsigned long flags;
struct kmemtrace_event *ev;
local_irq_save(flags);
ev = relay_reserve(kmemtrace_chan, sizeof(struct kmemtrace_event));
if (!ev)
goto failed;
/*
* Don't convert this to use structure initializers,
* C99 does not guarantee the rvalues evaluation order.
*/
ev->event_id = KMEMTRACE_EVENT_FREE;
ev->type_id = va_arg(*args, int);
ev->event_size = sizeof(struct kmemtrace_event);
ev->seq_num = atomic_add_return(1, &kmemtrace_seq_num);
ev->call_site = va_arg(*args, unsigned long);
ev->ptr = va_arg(*args, unsigned long);
failed:
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_subbuf_start(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_subbuf_start,
};
static struct dentry *kmemtrace_dir;
static struct dentry *kmemtrace_overruns_dentry;
static struct dentry *kmemtrace_abi_version_dentry;
static struct dentry *kmemtrace_enabled_dentry;
static int kmemtrace_start_probes(void)
{
int err;
err = marker_probe_register("kmemtrace_alloc", "type_id %d "
"call_site %lu ptr %lu "
"bytes_req %lu bytes_alloc %lu "
"gfp_flags %lu node %d",
kmemtrace_probe_alloc, NULL);
if (err)
return err;
err = marker_probe_register("kmemtrace_free", "type_id %d "
"call_site %lu ptr %lu",
kmemtrace_probe_free, NULL);
return err;
}
static void kmemtrace_stop_probes(void)
{
marker_probe_unregister("kmemtrace_alloc",
kmemtrace_probe_alloc, NULL);
marker_probe_unregister("kmemtrace_free",
kmemtrace_probe_free, NULL);
}
static int kmemtrace_enabled_get(void *data, u64 *val)
{
*val = *((int *) data);
return 0;
}
static int kmemtrace_enabled_set(void *data, u64 val)
{
u64 old_val = kmemtrace_enabled;
*((int *) data) = !!val;
if (old_val == val)
return 0;
if (val)
kmemtrace_start_probes();
else
kmemtrace_stop_probes();
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(kmemtrace_enabled_fops,
kmemtrace_enabled_get,
kmemtrace_enabled_set, "%llu\n");
static void kmemtrace_cleanup(void)
{
if (kmemtrace_enabled_dentry)
debugfs_remove(kmemtrace_enabled_dentry);
kmemtrace_stop_probes();
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;
if (kmemtrace_dir)
debugfs_remove(kmemtrace_dir);
}
static int __init kmemtrace_setup_late(void)
{
if (!kmemtrace_chan)
goto failed;
kmemtrace_dir = debugfs_create_dir("kmemtrace", NULL);
if (!kmemtrace_dir)
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;
kmemtrace_enabled_dentry =
debugfs_create_file("enabled", S_IRUSR | S_IWUSR,
kmemtrace_dir, &kmemtrace_enabled,
&kmemtrace_enabled_fops);
if (!kmemtrace_enabled_dentry)
goto cleanup;
if (relay_late_setup_files(kmemtrace_chan, "cpu", kmemtrace_dir))
goto cleanup;
printk(KERN_INFO "kmemtrace: fully up.\n");
return 0;
cleanup:
kmemtrace_cleanup();
failed:
return 1;
}
late_initcall(kmemtrace_setup_late);
static int __init kmemtrace_set_boot_enabled(char *str)
{
if (!str)
return -EINVAL;
if (!strcmp(str, "yes"))
kmemtrace_enabled = 1;
else if (!strcmp(str, "no"))
kmemtrace_enabled = 0;
else
return -EINVAL;
return 0;
}
early_param("kmemtrace.enable", kmemtrace_set_boot_enabled);
static int __init kmemtrace_set_subbufs(char *str)
{
get_option(&str, &kmemtrace_n_subbufs);
return 0;
}
early_param("kmemtrace.subbufs", kmemtrace_set_subbufs);
void kmemtrace_init(void)
{
if (!kmemtrace_n_subbufs)
kmemtrace_n_subbufs = KMEMTRACE_DEF_N_SUBBUFS;
kmemtrace_chan = relay_open(NULL, NULL, KMEMTRACE_SUBBUF_SIZE,
kmemtrace_n_subbufs, &relay_callbacks,
NULL);
if (!kmemtrace_chan) {
printk(KERN_ERR "kmemtrace: could not open relay channel.\n");
return;
}
if (!kmemtrace_enabled) {
printk(KERN_INFO "kmemtrace: disabled. Pass "
"kemtrace.enable=yes as kernel parameter for "
"boot-time tracing.\n");
return;
}
if (kmemtrace_start_probes()) {
printk(KERN_ERR "kmemtrace: could not register marker probes!\n");
kmemtrace_cleanup();
return;
}
printk(KERN_INFO "kmemtrace: enabled.\n");
}