blob: 55ed51cad6ca0ccc1086a3e971b868cb8f1ad038 [file] [log] [blame]
/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
/*
* Copyright © 2007 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software
* and its documentation for any purpose is hereby granted without
* fee, provided that the above copyright notice appear in all copies
* and that both that copyright notice and this permission notice
* appear in supporting documentation, and that the name of
* Red Hat, Inc. not be used in advertising or publicity pertaining to
* distribution of the software without specific, written prior
* permission. Red Hat, Inc. makes no representations about the
* suitability of this software for any purpose. It is provided "as
* is" without express or implied warranty.
*
* RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Author: Behdad Esfahbod <behdad@behdad.org>
*/
/* A simple malloc wrapper that prints out statistics on termination */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
/* caller-logging */
#include <string.h>
struct alloc_stat_t {
unsigned int num;
unsigned long long size;
};
struct alloc_stats_t {
struct alloc_stat_t malloc, realloc, total;
};
struct func_stat_t {
struct func_stat_t *next;
const void *addr;
const char *name;
struct alloc_stats_t stat;
};
static struct alloc_stats_t total_allocations;
static struct func_stat_t *func_stats[31627];
static int func_stats_num;
#ifndef ARRAY_LENGTH
#define ARRAY_LENGTH(__array) ((int) (sizeof (__array) / sizeof (__array[0])))
#endif
static void
alloc_stats_add (struct alloc_stats_t *stats, int is_realloc, size_t size)
{
struct alloc_stat_t *stat = is_realloc ? &stats->realloc : &stats->malloc;
stats->total.num++;
stats->total.size += size;
stat->num++;
stat->size += size;
}
#include <execinfo.h>
static void *
_perm_alloc (size_t size)
{
static uint8_t *ptr;
static size_t rem;
void *ret;
#define SUPERBLOCK_SIZE (1<<23)
#define align(x, y) (((x) + ((y)-1)) & ~((y)-1))
size = align (size, 2 * sizeof (void *));
if (size > rem || rem == 0) {
ptr = malloc (SUPERBLOCK_SIZE);
if (ptr == NULL)
exit (1);
rem = SUPERBLOCK_SIZE;
}
#undef SUPERBLOCK_SIZE
#undef align
ret = ptr;
rem -= size;
ptr += size;
return ret;
}
static void
resolve_addrs (struct func_stat_t *func_stats, int num)
{
int i;
void **addrs;
char **strings;
addrs = malloc (num * sizeof (void *));
for (i = 0; i < num; i++)
addrs[i] = (void *) func_stats[i].addr;
strings = backtrace_symbols (addrs, num);
for (i = 0; i < num; i++) {
char *p;
char *name;
int len;
p = strchr (strings[i], '\t');
if (p)
p++;
else
p = strings[i];
len = strlen (p) + 1;
name = _perm_alloc (len);
memcpy (name, p, len);
func_stats[i].name = name;
}
free (strings);
free (addrs);
}
static void
func_stats_add (const void *caller, int is_realloc, size_t size)
{
int i;
struct func_stat_t *elt;
alloc_stats_add (&total_allocations, is_realloc, size);
i = ((uintptr_t) caller ^ 1215497) % ARRAY_LENGTH (func_stats);
for (elt = func_stats[i]; elt != NULL; elt = elt->next) {
if (elt->addr == caller)
break;
}
if (elt == NULL) {
func_stats_num++;
elt = _perm_alloc (sizeof (struct func_stat_t));
elt->next = func_stats[i];
func_stats[i] = elt;
elt->addr = caller;
elt->name = NULL;
memset (&elt->stat, 0, sizeof (struct alloc_stats_t));
}
alloc_stats_add (&elt->stat, is_realloc, size);
}
/* wrapper stuff */
#include <malloc.h>
static void *(*old_malloc)(size_t, const void *);
static void *(*old_realloc)(void *, size_t, const void *);
static void *my_malloc(size_t, const void *);
static void *my_realloc(void *, size_t, const void *);
static void
save_hooks (void)
{
old_malloc = __malloc_hook;
old_realloc = __realloc_hook;
}
static void
old_hooks (void)
{
__malloc_hook = old_malloc;
__realloc_hook = old_realloc;
}
static void
my_hooks (void)
{
/* should always save the current value */
save_hooks ();
__malloc_hook = my_malloc;
__realloc_hook = my_realloc;
}
static void *
my_malloc(size_t size, const void *caller)
{
void *ret;
old_hooks ();
func_stats_add (caller, 0, size);
ret = malloc (size);
my_hooks ();
return ret;
}
static void *
my_realloc(void *ptr, size_t size, const void *caller)
{
void *ret;
old_hooks ();
func_stats_add (caller, 1, size);
ret = realloc (ptr, size);
my_hooks ();
return ret;
}
static void
my_init_hook(void) {
my_hooks ();
}
void (*__volatile __malloc_initialize_hook) (void) = my_init_hook;
/* reporting */
#include <locale.h>
static void
add_alloc_stats (struct alloc_stats_t *a, struct alloc_stats_t *b)
{
a->total.num += b->total.num;
a->total.size += b->total.size;
a->malloc.num += b->malloc.num;
a->malloc.size += b->malloc.size;
a->realloc.num += b->realloc.num;
a->realloc.size += b->realloc.size;
}
static void
dump_alloc_stats (struct alloc_stats_t *stats, const char *name)
{
printf ("%8u %'11llu %8u %'11llu %8u %'11llu %s\n",
stats->total.num, stats->total.size,
stats->malloc.num, stats->malloc.size,
stats->realloc.num, stats->realloc.size,
name);
}
static int
compare_func_stats_name (const void *pa, const void *pb)
{
const struct func_stat_t *a = pa, *b = pb;
int i;
i = strcmp (a->name, b->name);
if (i)
return i;
return ((char *) a->addr - (char *) b->addr);
}
static int
compare_func_stats (const void *pa, const void *pb)
{
const struct func_stat_t *a = pa, *b = pb;
if (a->stat.total.num != b->stat.total.num)
return (a->stat.total.num - b->stat.total.num);
if (a->stat.total.size != b->stat.total.size)
return (a->stat.total.size - b->stat.total.size);
return compare_func_stats_name (pa, pb);
}
static int
merge_similar_entries (struct func_stat_t *func_stats, int num)
{
int i, j;
j = 0;
for (i = 1; i < num; i++) {
if (i != j && 0 == strcmp (func_stats[i].name, func_stats[j].name)) {
add_alloc_stats (&func_stats[j].stat, &func_stats[i].stat);
} else {
j++;
if (i != j)
func_stats[j] = func_stats[i];
}
}
j++;
return j;
}
__attribute__ ((destructor))
void
malloc_stats (void)
{
unsigned int i, j;
struct func_stat_t *sorted_func_stats;
old_hooks ();
if (! func_stats_num)
return;
sorted_func_stats = malloc (sizeof (struct func_stat_t) * (func_stats_num + 1));
if (sorted_func_stats == NULL)
return;
j = 0;
for (i = 0; i < ARRAY_LENGTH (func_stats); i++) {
struct func_stat_t *elt;
for (elt = func_stats[i]; elt != NULL; elt = elt->next)
sorted_func_stats[j++] = *elt;
}
resolve_addrs (sorted_func_stats, j);
/* merge entries with same name */
qsort (sorted_func_stats, j,
sizeof (struct func_stat_t), compare_func_stats_name);
j = merge_similar_entries (sorted_func_stats, j);
qsort (sorted_func_stats, j,
sizeof (struct func_stat_t), compare_func_stats);
/* add total */
sorted_func_stats[j].next = NULL;
sorted_func_stats[j].addr = (void *) -1;
sorted_func_stats[j].name = "(total)";
sorted_func_stats[j].stat = total_allocations;
j++;
setlocale (LC_ALL, "");
printf (" TOTAL MALLOC REALLOC\n");
printf (" num size num size num size\n");
for (i = 0; i < j; i++) {
dump_alloc_stats (&sorted_func_stats[i].stat,
sorted_func_stats[i].name);
}
/* XXX free other stuff? */
free (sorted_func_stats);
}