/* * 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(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(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(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(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(unsigned long call_site, const void *ptr) { kmemtrace_free(KMEMTRACE_TYPE_KMALLOC, call_site, ptr); } static void kmemtrace_kmem_cache_free(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); if (err) return err; err = register_trace_kmem_cache_alloc(kmemtrace_kmem_cache_alloc); if (err) return err; err = register_trace_kmalloc_node(kmemtrace_kmalloc_node); if (err) return err; err = register_trace_kmem_cache_alloc_node(kmemtrace_kmem_cache_alloc_node); if (err) return err; err = register_trace_kfree(kmemtrace_kfree); if (err) return err; err = register_trace_kmem_cache_free(kmemtrace_kmem_cache_free); return err; } static void kmemtrace_stop_probes(void) { unregister_trace_kmalloc(kmemtrace_kmalloc); unregister_trace_kmem_cache_alloc(kmemtrace_kmem_cache_alloc); unregister_trace_kmalloc_node(kmemtrace_kmalloc_node); unregister_trace_kmem_cache_alloc_node(kmemtrace_kmem_cache_alloc_node); unregister_trace_kfree(kmemtrace_kfree); unregister_trace_kmem_cache_free(kmemtrace_kmem_cache_free); } static int kmem_trace_init(struct trace_array *tr) { int cpu; kmemtrace_array = tr; for_each_cpu(cpu, cpu_possible_mask) tracing_reset(tr, cpu); 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_user(struct trace_iterator *iter, struct kmemtrace_alloc_entry *entry) { struct kmemtrace_user_event_alloc *ev_alloc; struct trace_seq *s = &iter->seq; struct kmemtrace_user_event *ev; 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, struct kmemtrace_free_entry *entry) { struct trace_seq *s = &iter->seq; struct kmemtrace_user_event *ev; 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; /* 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 */ ret = trace_seq_printf(s, "%4d ", entry->node); if (!ret) return TRACE_TYPE_PARTIAL_LINE; /* Call site */ ret = seq_print_ip_sym(s, entry->call_site, 0); if (!ret) return TRACE_TYPE_PARTIAL_LINE; if (!trace_seq_printf(s, "\n")) 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; /* 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 */ ret = trace_seq_printf(s, " "); if (!ret) return TRACE_TYPE_PARTIAL_LINE; /* Call site */ ret = seq_print_ip_sym(s, entry->call_site, 0); if (!ret) return TRACE_TYPE_PARTIAL_LINE; if (!trace_seq_printf(s, "\n")) 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; switch (entry->type) { case TRACE_KMEM_ALLOC: { struct kmemtrace_alloc_entry *field; trace_assign_type(field, entry); if (kmem_tracer_flags.val & TRACE_KMEM_OPT_MINIMAL) return kmemtrace_print_alloc_compress(iter, field); else return kmemtrace_print_alloc_user(iter, field); } case TRACE_KMEM_FREE: { struct kmemtrace_free_entry *field; trace_assign_type(field, entry); if (kmem_tracer_flags.val & TRACE_KMEM_OPT_MINIMAL) return kmemtrace_print_free_compress(iter, field); else return kmemtrace_print_free_user(iter, field); } default: return TRACE_TYPE_UNHANDLED; } } 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) { return register_tracer(&kmem_tracer); } device_initcall(init_kmem_tracer);