a9a5776380
Multiple events may use the same method to print their data. Instead of having all events have a pointer to their print funtions, the trace_event structure now points to a trace_event_functions structure that will hold the way to print ouf the event. The event itself is now passed to the print function to let the print function know what kind of event it should print. This opens the door to consolidating the way several events print their output. text data bss dec hex filename 4913961 1088356 861512 6863829 68bbd5 vmlinux.orig 4900382 1048964 861512 6810858 67ecea vmlinux.init 4900446 1049028 861512 6810986 67ed6a vmlinux.preprint This change slightly increases the size but is needed for the next change. v3: Fix the branch tracer events to handle this change. v2: Fix the new function graph tracer event calls to handle this change. Acked-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com> Acked-by: Masami Hiramatsu <mhiramat@redhat.com> Acked-by: Frederic Weisbecker <fweisbec@gmail.com> Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
529 lines
13 KiB
C
529 lines
13 KiB
C
/*
|
|
* Memory allocator tracing
|
|
*
|
|
* Copyright (C) 2008 Eduard - Gabriel Munteanu
|
|
* Copyright (C) 2008 Pekka Enberg <penberg@cs.helsinki.fi>
|
|
* Copyright (C) 2008 Frederic Weisbecker <fweisbec@gmail.com>
|
|
*/
|
|
|
|
#include <linux/tracepoint.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/dcache.h>
|
|
#include <linux/fs.h>
|
|
|
|
#include <linux/kmemtrace.h>
|
|
|
|
#include "trace_output.h"
|
|
#include "trace.h"
|
|
|
|
/* Select an alternative, minimalistic output than the original one */
|
|
#define TRACE_KMEM_OPT_MINIMAL 0x1
|
|
|
|
static struct tracer_opt kmem_opts[] = {
|
|
/* Default disable the minimalistic output */
|
|
{ TRACER_OPT(kmem_minimalistic, TRACE_KMEM_OPT_MINIMAL) },
|
|
{ }
|
|
};
|
|
|
|
static struct tracer_flags kmem_tracer_flags = {
|
|
.val = 0,
|
|
.opts = kmem_opts
|
|
};
|
|
|
|
static struct trace_array *kmemtrace_array;
|
|
|
|
/* Trace allocations */
|
|
static inline void kmemtrace_alloc(enum kmemtrace_type_id type_id,
|
|
unsigned long call_site,
|
|
const void *ptr,
|
|
size_t bytes_req,
|
|
size_t bytes_alloc,
|
|
gfp_t gfp_flags,
|
|
int node)
|
|
{
|
|
struct ftrace_event_call *call = &event_kmem_alloc;
|
|
struct trace_array *tr = kmemtrace_array;
|
|
struct kmemtrace_alloc_entry *entry;
|
|
struct ring_buffer_event *event;
|
|
|
|
event = ring_buffer_lock_reserve(tr->buffer, sizeof(*entry));
|
|
if (!event)
|
|
return;
|
|
|
|
entry = ring_buffer_event_data(event);
|
|
tracing_generic_entry_update(&entry->ent, 0, 0);
|
|
|
|
entry->ent.type = TRACE_KMEM_ALLOC;
|
|
entry->type_id = type_id;
|
|
entry->call_site = call_site;
|
|
entry->ptr = ptr;
|
|
entry->bytes_req = bytes_req;
|
|
entry->bytes_alloc = bytes_alloc;
|
|
entry->gfp_flags = gfp_flags;
|
|
entry->node = node;
|
|
|
|
if (!filter_check_discard(call, entry, tr->buffer, event))
|
|
ring_buffer_unlock_commit(tr->buffer, event);
|
|
|
|
trace_wake_up();
|
|
}
|
|
|
|
static inline void kmemtrace_free(enum kmemtrace_type_id type_id,
|
|
unsigned long call_site,
|
|
const void *ptr)
|
|
{
|
|
struct ftrace_event_call *call = &event_kmem_free;
|
|
struct trace_array *tr = kmemtrace_array;
|
|
struct kmemtrace_free_entry *entry;
|
|
struct ring_buffer_event *event;
|
|
|
|
event = ring_buffer_lock_reserve(tr->buffer, sizeof(*entry));
|
|
if (!event)
|
|
return;
|
|
entry = ring_buffer_event_data(event);
|
|
tracing_generic_entry_update(&entry->ent, 0, 0);
|
|
|
|
entry->ent.type = TRACE_KMEM_FREE;
|
|
entry->type_id = type_id;
|
|
entry->call_site = call_site;
|
|
entry->ptr = ptr;
|
|
|
|
if (!filter_check_discard(call, entry, tr->buffer, event))
|
|
ring_buffer_unlock_commit(tr->buffer, event);
|
|
|
|
trace_wake_up();
|
|
}
|
|
|
|
static void kmemtrace_kmalloc(void *ignore,
|
|
unsigned long call_site,
|
|
const void *ptr,
|
|
size_t bytes_req,
|
|
size_t bytes_alloc,
|
|
gfp_t gfp_flags)
|
|
{
|
|
kmemtrace_alloc(KMEMTRACE_TYPE_KMALLOC, call_site, ptr,
|
|
bytes_req, bytes_alloc, gfp_flags, -1);
|
|
}
|
|
|
|
static void kmemtrace_kmem_cache_alloc(void *ignore,
|
|
unsigned long call_site,
|
|
const void *ptr,
|
|
size_t bytes_req,
|
|
size_t bytes_alloc,
|
|
gfp_t gfp_flags)
|
|
{
|
|
kmemtrace_alloc(KMEMTRACE_TYPE_CACHE, call_site, ptr,
|
|
bytes_req, bytes_alloc, gfp_flags, -1);
|
|
}
|
|
|
|
static void kmemtrace_kmalloc_node(void *ignore,
|
|
unsigned long call_site,
|
|
const void *ptr,
|
|
size_t bytes_req,
|
|
size_t bytes_alloc,
|
|
gfp_t gfp_flags,
|
|
int node)
|
|
{
|
|
kmemtrace_alloc(KMEMTRACE_TYPE_KMALLOC, call_site, ptr,
|
|
bytes_req, bytes_alloc, gfp_flags, node);
|
|
}
|
|
|
|
static void kmemtrace_kmem_cache_alloc_node(void *ignore,
|
|
unsigned long call_site,
|
|
const void *ptr,
|
|
size_t bytes_req,
|
|
size_t bytes_alloc,
|
|
gfp_t gfp_flags,
|
|
int node)
|
|
{
|
|
kmemtrace_alloc(KMEMTRACE_TYPE_CACHE, call_site, ptr,
|
|
bytes_req, bytes_alloc, gfp_flags, node);
|
|
}
|
|
|
|
static void
|
|
kmemtrace_kfree(void *ignore, unsigned long call_site, const void *ptr)
|
|
{
|
|
kmemtrace_free(KMEMTRACE_TYPE_KMALLOC, call_site, ptr);
|
|
}
|
|
|
|
static void kmemtrace_kmem_cache_free(void *ignore,
|
|
unsigned long call_site, const void *ptr)
|
|
{
|
|
kmemtrace_free(KMEMTRACE_TYPE_CACHE, call_site, ptr);
|
|
}
|
|
|
|
static int kmemtrace_start_probes(void)
|
|
{
|
|
int err;
|
|
|
|
err = register_trace_kmalloc(kmemtrace_kmalloc, NULL);
|
|
if (err)
|
|
return err;
|
|
err = register_trace_kmem_cache_alloc(kmemtrace_kmem_cache_alloc, NULL);
|
|
if (err)
|
|
return err;
|
|
err = register_trace_kmalloc_node(kmemtrace_kmalloc_node, NULL);
|
|
if (err)
|
|
return err;
|
|
err = register_trace_kmem_cache_alloc_node(kmemtrace_kmem_cache_alloc_node, NULL);
|
|
if (err)
|
|
return err;
|
|
err = register_trace_kfree(kmemtrace_kfree, NULL);
|
|
if (err)
|
|
return err;
|
|
err = register_trace_kmem_cache_free(kmemtrace_kmem_cache_free, NULL);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void kmemtrace_stop_probes(void)
|
|
{
|
|
unregister_trace_kmalloc(kmemtrace_kmalloc, NULL);
|
|
unregister_trace_kmem_cache_alloc(kmemtrace_kmem_cache_alloc, NULL);
|
|
unregister_trace_kmalloc_node(kmemtrace_kmalloc_node, NULL);
|
|
unregister_trace_kmem_cache_alloc_node(kmemtrace_kmem_cache_alloc_node, NULL);
|
|
unregister_trace_kfree(kmemtrace_kfree, NULL);
|
|
unregister_trace_kmem_cache_free(kmemtrace_kmem_cache_free, NULL);
|
|
}
|
|
|
|
static int kmem_trace_init(struct trace_array *tr)
|
|
{
|
|
kmemtrace_array = tr;
|
|
|
|
tracing_reset_online_cpus(tr);
|
|
|
|
kmemtrace_start_probes();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void kmem_trace_reset(struct trace_array *tr)
|
|
{
|
|
kmemtrace_stop_probes();
|
|
}
|
|
|
|
static void kmemtrace_headers(struct seq_file *s)
|
|
{
|
|
/* Don't need headers for the original kmemtrace output */
|
|
if (!(kmem_tracer_flags.val & TRACE_KMEM_OPT_MINIMAL))
|
|
return;
|
|
|
|
seq_printf(s, "#\n");
|
|
seq_printf(s, "# ALLOC TYPE REQ GIVEN FLAGS "
|
|
" POINTER NODE CALLER\n");
|
|
seq_printf(s, "# FREE | | | | "
|
|
" | | | |\n");
|
|
seq_printf(s, "# |\n\n");
|
|
}
|
|
|
|
/*
|
|
* The following functions give the original output from kmemtrace,
|
|
* plus the origin CPU, since reordering occurs in-kernel now.
|
|
*/
|
|
|
|
#define KMEMTRACE_USER_ALLOC 0
|
|
#define KMEMTRACE_USER_FREE 1
|
|
|
|
struct kmemtrace_user_event {
|
|
u8 event_id;
|
|
u8 type_id;
|
|
u16 event_size;
|
|
u32 cpu;
|
|
u64 timestamp;
|
|
unsigned long call_site;
|
|
unsigned long ptr;
|
|
};
|
|
|
|
struct kmemtrace_user_event_alloc {
|
|
size_t bytes_req;
|
|
size_t bytes_alloc;
|
|
unsigned gfp_flags;
|
|
int node;
|
|
};
|
|
|
|
static enum print_line_t
|
|
kmemtrace_print_alloc(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct trace_seq *s = &iter->seq;
|
|
struct kmemtrace_alloc_entry *entry;
|
|
int ret;
|
|
|
|
trace_assign_type(entry, iter->ent);
|
|
|
|
ret = trace_seq_printf(s, "type_id %d call_site %pF ptr %lu "
|
|
"bytes_req %lu bytes_alloc %lu gfp_flags %lu node %d\n",
|
|
entry->type_id, (void *)entry->call_site, (unsigned long)entry->ptr,
|
|
(unsigned long)entry->bytes_req, (unsigned long)entry->bytes_alloc,
|
|
(unsigned long)entry->gfp_flags, entry->node);
|
|
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
return TRACE_TYPE_HANDLED;
|
|
}
|
|
|
|
static enum print_line_t
|
|
kmemtrace_print_free(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct trace_seq *s = &iter->seq;
|
|
struct kmemtrace_free_entry *entry;
|
|
int ret;
|
|
|
|
trace_assign_type(entry, iter->ent);
|
|
|
|
ret = trace_seq_printf(s, "type_id %d call_site %pF ptr %lu\n",
|
|
entry->type_id, (void *)entry->call_site,
|
|
(unsigned long)entry->ptr);
|
|
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
return TRACE_TYPE_HANDLED;
|
|
}
|
|
|
|
static enum print_line_t
|
|
kmemtrace_print_alloc_user(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct trace_seq *s = &iter->seq;
|
|
struct kmemtrace_alloc_entry *entry;
|
|
struct kmemtrace_user_event *ev;
|
|
struct kmemtrace_user_event_alloc *ev_alloc;
|
|
|
|
trace_assign_type(entry, iter->ent);
|
|
|
|
ev = trace_seq_reserve(s, sizeof(*ev));
|
|
if (!ev)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
ev->event_id = KMEMTRACE_USER_ALLOC;
|
|
ev->type_id = entry->type_id;
|
|
ev->event_size = sizeof(*ev) + sizeof(*ev_alloc);
|
|
ev->cpu = iter->cpu;
|
|
ev->timestamp = iter->ts;
|
|
ev->call_site = entry->call_site;
|
|
ev->ptr = (unsigned long)entry->ptr;
|
|
|
|
ev_alloc = trace_seq_reserve(s, sizeof(*ev_alloc));
|
|
if (!ev_alloc)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
ev_alloc->bytes_req = entry->bytes_req;
|
|
ev_alloc->bytes_alloc = entry->bytes_alloc;
|
|
ev_alloc->gfp_flags = entry->gfp_flags;
|
|
ev_alloc->node = entry->node;
|
|
|
|
return TRACE_TYPE_HANDLED;
|
|
}
|
|
|
|
static enum print_line_t
|
|
kmemtrace_print_free_user(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct trace_seq *s = &iter->seq;
|
|
struct kmemtrace_free_entry *entry;
|
|
struct kmemtrace_user_event *ev;
|
|
|
|
trace_assign_type(entry, iter->ent);
|
|
|
|
ev = trace_seq_reserve(s, sizeof(*ev));
|
|
if (!ev)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
ev->event_id = KMEMTRACE_USER_FREE;
|
|
ev->type_id = entry->type_id;
|
|
ev->event_size = sizeof(*ev);
|
|
ev->cpu = iter->cpu;
|
|
ev->timestamp = iter->ts;
|
|
ev->call_site = entry->call_site;
|
|
ev->ptr = (unsigned long)entry->ptr;
|
|
|
|
return TRACE_TYPE_HANDLED;
|
|
}
|
|
|
|
/* The two other following provide a more minimalistic output */
|
|
static enum print_line_t
|
|
kmemtrace_print_alloc_compress(struct trace_iterator *iter)
|
|
{
|
|
struct kmemtrace_alloc_entry *entry;
|
|
struct trace_seq *s = &iter->seq;
|
|
int ret;
|
|
|
|
trace_assign_type(entry, iter->ent);
|
|
|
|
/* Alloc entry */
|
|
ret = trace_seq_printf(s, " + ");
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
/* Type */
|
|
switch (entry->type_id) {
|
|
case KMEMTRACE_TYPE_KMALLOC:
|
|
ret = trace_seq_printf(s, "K ");
|
|
break;
|
|
case KMEMTRACE_TYPE_CACHE:
|
|
ret = trace_seq_printf(s, "C ");
|
|
break;
|
|
case KMEMTRACE_TYPE_PAGES:
|
|
ret = trace_seq_printf(s, "P ");
|
|
break;
|
|
default:
|
|
ret = trace_seq_printf(s, "? ");
|
|
}
|
|
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
/* Requested */
|
|
ret = trace_seq_printf(s, "%4zu ", entry->bytes_req);
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
/* Allocated */
|
|
ret = trace_seq_printf(s, "%4zu ", entry->bytes_alloc);
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
/* Flags
|
|
* TODO: would be better to see the name of the GFP flag names
|
|
*/
|
|
ret = trace_seq_printf(s, "%08x ", entry->gfp_flags);
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
/* Pointer to allocated */
|
|
ret = trace_seq_printf(s, "0x%tx ", (ptrdiff_t)entry->ptr);
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
/* Node and call site*/
|
|
ret = trace_seq_printf(s, "%4d %pf\n", entry->node,
|
|
(void *)entry->call_site);
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
return TRACE_TYPE_HANDLED;
|
|
}
|
|
|
|
static enum print_line_t
|
|
kmemtrace_print_free_compress(struct trace_iterator *iter)
|
|
{
|
|
struct kmemtrace_free_entry *entry;
|
|
struct trace_seq *s = &iter->seq;
|
|
int ret;
|
|
|
|
trace_assign_type(entry, iter->ent);
|
|
|
|
/* Free entry */
|
|
ret = trace_seq_printf(s, " - ");
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
/* Type */
|
|
switch (entry->type_id) {
|
|
case KMEMTRACE_TYPE_KMALLOC:
|
|
ret = trace_seq_printf(s, "K ");
|
|
break;
|
|
case KMEMTRACE_TYPE_CACHE:
|
|
ret = trace_seq_printf(s, "C ");
|
|
break;
|
|
case KMEMTRACE_TYPE_PAGES:
|
|
ret = trace_seq_printf(s, "P ");
|
|
break;
|
|
default:
|
|
ret = trace_seq_printf(s, "? ");
|
|
}
|
|
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
/* Skip requested/allocated/flags */
|
|
ret = trace_seq_printf(s, " ");
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
/* Pointer to allocated */
|
|
ret = trace_seq_printf(s, "0x%tx ", (ptrdiff_t)entry->ptr);
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
/* Skip node and print call site*/
|
|
ret = trace_seq_printf(s, " %pf\n", (void *)entry->call_site);
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
return TRACE_TYPE_HANDLED;
|
|
}
|
|
|
|
static enum print_line_t kmemtrace_print_line(struct trace_iterator *iter)
|
|
{
|
|
struct trace_entry *entry = iter->ent;
|
|
|
|
if (!(kmem_tracer_flags.val & TRACE_KMEM_OPT_MINIMAL))
|
|
return TRACE_TYPE_UNHANDLED;
|
|
|
|
switch (entry->type) {
|
|
case TRACE_KMEM_ALLOC:
|
|
return kmemtrace_print_alloc_compress(iter);
|
|
case TRACE_KMEM_FREE:
|
|
return kmemtrace_print_free_compress(iter);
|
|
default:
|
|
return TRACE_TYPE_UNHANDLED;
|
|
}
|
|
}
|
|
|
|
static struct trace_event_functions kmem_trace_alloc_funcs = {
|
|
.trace = kmemtrace_print_alloc,
|
|
.binary = kmemtrace_print_alloc_user,
|
|
};
|
|
|
|
static struct trace_event kmem_trace_alloc = {
|
|
.type = TRACE_KMEM_ALLOC,
|
|
.funcs = &kmem_trace_alloc_funcs,
|
|
};
|
|
|
|
static struct trace_event_functions kmem_trace_free_funcs = {
|
|
.trace = kmemtrace_print_free,
|
|
.binary = kmemtrace_print_free_user,
|
|
};
|
|
|
|
static struct trace_event kmem_trace_free = {
|
|
.type = TRACE_KMEM_FREE,
|
|
.funcs = &kmem_trace_free_funcs,
|
|
};
|
|
|
|
static struct tracer kmem_tracer __read_mostly = {
|
|
.name = "kmemtrace",
|
|
.init = kmem_trace_init,
|
|
.reset = kmem_trace_reset,
|
|
.print_line = kmemtrace_print_line,
|
|
.print_header = kmemtrace_headers,
|
|
.flags = &kmem_tracer_flags
|
|
};
|
|
|
|
void kmemtrace_init(void)
|
|
{
|
|
/* earliest opportunity to start kmem tracing */
|
|
}
|
|
|
|
static int __init init_kmem_tracer(void)
|
|
{
|
|
if (!register_ftrace_event(&kmem_trace_alloc)) {
|
|
pr_warning("Warning: could not register kmem events\n");
|
|
return 1;
|
|
}
|
|
|
|
if (!register_ftrace_event(&kmem_trace_free)) {
|
|
pr_warning("Warning: could not register kmem events\n");
|
|
return 1;
|
|
}
|
|
|
|
if (register_tracer(&kmem_tracer) != 0) {
|
|
pr_warning("Warning: could not register the kmem tracer\n");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
device_initcall(init_kmem_tracer);
|