/* $Id: xmalloc-debug.c,v 1.22 2007/07/25 23:30:07 nicm Exp $ */

/*
 * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF MIND, 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.
 */

#ifdef DEBUG

#include <sys/types.h>

#include <ctype.h>
#include <dlfcn.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include "fdm.h"

/* Single xmalloc allocated block. */
struct xmalloc_blk {
	void	*caller;

	void	*ptr;
	size_t	 size;

	SPLAY_ENTRY(xmalloc_blk) entry;
};

/* Splay tree of allocated blocks. */
SPLAY_HEAD(xmalloc_tree, xmalloc_blk);
struct xmalloc_tree xmalloc_tree = SPLAY_INITIALIZER(&xmalloc_tree);

/* Various statistics. */
size_t	xmalloc_allocated;
size_t	xmalloc_freed;
size_t	xmalloc_peak;
u_int	xmalloc_frees;
u_int	xmalloc_mallocs;
u_int	xmalloc_reallocs;

/* Print function. */
#define XMALLOC_PRINT log_debug3

/* Bytes of unallocated blocks and number of allocated blocks to show. */
#define XMALLOC_BYTES 8
#define XMALLOC_LINES 32

/* Macro to update peek usage variable. */
#define XMALLOC_UPDATE() do {						\
	if (xmalloc_allocated - xmalloc_freed > xmalloc_peak)		\
		xmalloc_peak = xmalloc_allocated - xmalloc_freed;	\
} while (0)

/* Tree functions. */
int xmalloc_cmp(struct xmalloc_blk *, struct xmalloc_blk *);
SPLAY_PROTOTYPE(xmalloc_tree, xmalloc_blk, entry, xmalloc_cmp);
SPLAY_GENERATE(xmalloc_tree, xmalloc_blk, entry, xmalloc_cmp);

/* Compare two blocks. */
int
xmalloc_cmp(struct xmalloc_blk *blk1, struct xmalloc_blk *blk2)
{
	uintptr_t	ptr1 = (uintptr_t) blk1->ptr;
	uintptr_t	ptr2 = (uintptr_t) blk2->ptr;

	if (ptr1 < ptr2)
		return (-1);
	if (ptr1 > ptr2)
		return (1);
	return (0);
}

/* Clear statistics and block list; used to start fresh after fork(2). */
void
xmalloc_clear(void)
{
 	struct xmalloc_blk	*blk;

	xmalloc_allocated = 0;
	xmalloc_freed = 0;
	xmalloc_peak = 0;
	xmalloc_frees = 0;
	xmalloc_mallocs = 0;
	xmalloc_reallocs = 0;

	while (!SPLAY_EMPTY(&xmalloc_tree)) {
		blk = SPLAY_ROOT(&xmalloc_tree);
		SPLAY_REMOVE(xmalloc_tree, &xmalloc_tree, blk);
		free(blk);
	}
}

/* Print report of statistics and unfreed blocks. */
void
xmalloc_report(pid_t pid, const char *hdr)
{
 	struct xmalloc_blk	*blk;
	u_char			*iptr;
 	char	 		 buf[4 * XMALLOC_BYTES + 1], *optr;
 	size_t		 	 len;
  	u_int	 		 n;
	Dl_info			 info;

 	XMALLOC_PRINT("%s: %ld: allocated=%zu, freed=%zu, difference=%zd, "
	    "peak=%zu", hdr, (long) pid, xmalloc_allocated, xmalloc_freed,
	    xmalloc_allocated - xmalloc_freed, xmalloc_peak);
 	XMALLOC_PRINT("%s: %ld: mallocs=%u, reallocs=%u, frees=%u", hdr,
	    (long) pid, xmalloc_mallocs, xmalloc_reallocs, xmalloc_frees);

	n = 0;
	SPLAY_FOREACH(blk, xmalloc_tree, &xmalloc_tree) {
		n++;
		if (n >= XMALLOC_LINES)
			continue;

		len = blk->size;
		if (len > XMALLOC_BYTES)
			len = XMALLOC_BYTES;

		memset(&info, 0, sizeof info);
		if (dladdr(blk->caller, &info) == 0)
			info.dli_sname = info.dli_saddr = NULL;

		optr = buf;
		iptr = blk->ptr;
		for (; len > 0; len--) {
			if (isascii(*iptr) && !iscntrl(*iptr)) {
				*optr++ = *iptr++;
				continue;
			}
			*optr++ = '\\';
			*optr++ = '0' + ((*iptr >> 6) & 07);
			*optr++ = '0' + ((*iptr >> 3) & 07);
			*optr++ = '0' + (*iptr & 07);
			iptr++;
		}
		*optr = '\0';

		XMALLOC_PRINT("%s: %ld: %u, %s+0x%02tx: [%p %zu: %s]", hdr,
		    (long) pid, n, info.dli_sname, ((u_char *) blk->caller) -
		    ((u_char *) info.dli_saddr), blk->ptr, blk->size, buf);
	}
	XMALLOC_PRINT("%s: %ld: %u unfreed blocks", hdr, (long) pid, n);
}

/* Record a newly created block. */
void
xmalloc_new(void *caller, void *ptr, size_t size)
{
	struct xmalloc_blk	*blk;

	xmalloc_allocated += size;
	XMALLOC_UPDATE();

	if ((blk = malloc(sizeof *blk)) == NULL)
		abort();

	blk->ptr = ptr;
	blk->size = size;

	blk->caller = caller;

	SPLAY_INSERT(xmalloc_tree, &xmalloc_tree, blk);

	xmalloc_mallocs++;
	XMALLOC_UPDATE();
}

/* Record changes to a block. */
void
xmalloc_change(void *caller, void *oldptr, void *newptr, size_t newsize)
{
	struct xmalloc_blk	*blk, key;
	ssize_t			 change;

	if (oldptr == NULL) {
		xmalloc_new(caller, newptr, newsize);
		return;
	}

	key.ptr = oldptr;
	blk = SPLAY_FIND(xmalloc_tree, &xmalloc_tree, &key);
	if (blk == NULL)
		return;

	change = newsize - blk->size;
	if (change > 0)
		xmalloc_allocated += change;
	else
		xmalloc_freed -= change;
	XMALLOC_UPDATE();

	SPLAY_REMOVE(xmalloc_tree, &xmalloc_tree, blk);

 	blk->ptr = newptr;
	blk->size = newsize;

	blk->caller = caller;

	SPLAY_INSERT(xmalloc_tree, &xmalloc_tree, blk);

	xmalloc_reallocs++;
	XMALLOC_UPDATE();
}

/* Record a block free. */
void
xmalloc_free(void *ptr)
{
	struct xmalloc_blk	*blk, key;

	key.ptr = ptr;
	blk = SPLAY_FIND(xmalloc_tree, &xmalloc_tree, &key);
	if (blk == NULL)
		return;

	xmalloc_freed += blk->size;

	SPLAY_REMOVE(xmalloc_tree, &xmalloc_tree, blk);
	free(blk);

	xmalloc_frees++;
	XMALLOC_UPDATE();
}

#endif /* DEBUG */
