/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 *  Authors: Jeffrey Stedfast <fejj@ximian.com>
 *
 *  Copyright 2001 Ximian, Inc. (www.ximian.com)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
 *
 */


#ifndef __ZENPROFILER_H__
#define __ZENPROFILER_H__

#include <glib.h>
#include <stdio.h>
#include <ctype.h>
#ifdef ENABLE_THREADS
#include <pthread.h>
#endif

#ifndef ENABLE_ZENTIMER
#ifdef ENABLE_ZENPROFILER
#define ENABLE_ZENTIMER
#endif
#endif /* ENABLE_ZENTIMER */

#include "zentimer.h"

#ifdef __cplusplus
extern "C" {
#pragma }
#endif /* __cplusplus */

#ifdef ENABLE_ZENPROFILER

struct zenfunc_t {
	char *name;
	unsigned num;
	ztime_t total;
};

static struct {
	FILE *log;
	ztimer_t ztimer;
	ztime_t total;
	GHashTable *hash;
	GSList *list;
} zenprof = { NULL, ZTIMER_INITIALIZER, { 0, 0 }, NULL, NULL };

#ifdef ENABLE_THREADS
static pthread_mutex_t zen_lock = PTHREAD_MUTEX_INITIALIZER;
#define ZENPROFILER_LOCK()   pthread_mutex_lock (&zen_lock)
#define ZENPROFILER_UNLOCK() pthread_mutex_unlock (&zen_lock)
#else
#define ZENPROFILER_LOCK()
#define ZENPROFILER_UNLOCK()
#endif /* ENABLE_THREADS */

static char *
zengetname (char *func)
{
	char *ptr;
	
	for (ptr = func; *ptr && *ptr != '('; ptr++);
	
	return g_strstrip (g_strndup (func, ptr - func));
}


/**
 * ZenProfilerInit:
 * @logfile: File to save profiling information (or %NULL to log to stderr)
 *
 * Initializes the Zen Profiler.
 **/
static void
ZenProfilerInit (const char *logfile)
{
	zenprof.hash = g_hash_table_new (g_str_hash, g_str_equal);
	
	ZenTimerMTStart (zenprof.ztimer);
	
	if (logfile)
		zenprof.log = fopen (logfile, "w+");
}


static void
zen_report_internal (char *name, ztimer_t ztimer)
{
	struct zenfunc_t *zenfunc;
	ztime_t diff;
	char *key;
	
	ZENPROFILER_LOCK();
	
	if (!g_hash_table_lookup_extended (zenprof.hash, name, (gpointer *) &key, (gpointer *) &zenfunc)) {
		zenfunc = g_new0 (struct zenfunc_t, 1);
		zenfunc->name = name;
		zenfunc->total.sec = 0;
		zenfunc->total.msec = 0;
		zenfunc->num = 0;
		g_hash_table_insert (zenprof.hash, zenfunc->name, zenfunc);
		zenprof.list = g_slist_prepend (zenprof.list, zenfunc);
	} else
		g_free (name);
	
	ztime_diff (ztimer.start, ztimer.stop, &diff);
	
	zenfunc->total.sec += diff.sec;
	zenfunc->total.msec += diff.msec;
	while (zenfunc->total.msec >= 1000) {
		zenfunc->total.msec -= 1000;
		zenfunc->total.sec += 1;
	}
	zenfunc->num++;
	
	ZENPROFILER_UNLOCK();
}


/**
 * ZenProfilerLazy:
 * @func: Function call to profile.
 *
 * Profiles the provided function call. This is basically the lazy way out.
 *
 * Sample usage:  ZenProfilerLazy(printf ("how long does this take??\n"));
 **/
#define ZenProfilerLazy(func) G_STMT_START {                  \
	{                                                     \
		ztimer_t ztimer;                              \
		char *name;                                   \
		                                              \
		ZenTimerMTStart (ztimer);                     \
		func;                                         \
		ZenTimerMTStop (ztimer);                      \
		                                              \
		name = zengetname (#func);                    \
		                                              \
		zen_report_internal (name, ztimer);           \
	}                                                     \
} G_STMT_END

/**
 * ZenProfilerStart:
 * @ztimer: a ztimer_t
 *
 * Initializes @zstart.
 **/
#define ZenProfilerStart(ztimer) ZenTimerMTStart (ztimer)


/**
 * ZenProfilerStop:
 * @ztimer: a ztimer_t
 *
 * Initializes @zstop.
 **/
#define ZenProfilerStop(ztimer) ZenTimerMTStop (ztimer)


/**
 * ZenProfilerReport:
 * @ztimer: a ztimer_t
 *
 * Cache the results for later reporting.
 **/
#define ZenProfilerReport(ztimer) G_STMT_START {              \
	{                                                     \
		char *name;                                   \
		                                              \
		name = g_strdup (__FUNCTION__);               \
		                                              \
		zen_report_internal (name, ztimer);           \
	}                                                     \
} G_STMT_END


/**
 * ZenProfiler_return:
 * @ztimer: a ztimer_t
 *
 * Stop the timer, report the results, and then return from the function.
 *
 * Note: equivalent to: ZenProfilerStop(@ztimer); ZenProfilerReport(@ztimer); return;
 **/
#define ZenProfiler_return(ztimer) G_STMT_START {             \
	ZenProfilerStop (ztimer);                             \
	ZenProfilerReport (ztimer);                           \
	return;                                               \
} G_STMT_END


/**
 * ZenProfiler_return_val:
 * @ztimer: a ztimer_t
 *
 * Same as ZenProfiler_return() but returns the specified value.
 **/
#define ZenProfiler_return_val(ztimer, retval) G_STMT_START { \
	ZenProfilerStop (ztimer);                             \
	ZenProfilerReport (ztimer);                           \
	return retval;                                        \
} G_STMT_END


#define ZENLOG (zenprof.log ? zenprof.log : stderr)

static void
zen_log (gpointer key, gpointer value, gpointer user_data)
{
	struct zenfunc_t *zenfunc = (struct zenfunc_t *) value;
	char percent[7], total[20], calls[20], average[20];
	unsigned long secs, millisecs;
	double pcnt = 0.0;
	
	if (zenfunc) {
		millisecs = zenfunc->total.sec * 1000 + zenfunc->total.msec;
		pcnt = ((double) millisecs) / ((double) zenprof.total.sec * 1000 + zenprof.total.msec) * 100;
		millisecs = (unsigned long) (millisecs / zenfunc->num);
		
		secs = 0;
		while (millisecs >= 1000) {
			millisecs -= 1000;
			secs++;
		}
		
		sprintf (percent, "%2.2f", pcnt);
		sprintf (calls, "%d", zenfunc->num);
		sprintf (total, "%lu.%03d", zenfunc->total.sec, zenfunc->total.msec);
		sprintf (average, "%lu.%03d", secs, millisecs);
		
		fprintf (ZENLOG, "%6s %9s  %7s  %7s %7s %7s  %s\n",
			 percent, total, "n/a", calls, "n/a", average, zenfunc->name);
		
		g_free (zenfunc->name);
		g_free (zenfunc);
	}
}


static int
zenfunccmp (gconstpointer a, gconstpointer b)
{
	const struct zenfunc_t *afunc = (const struct zenfunc_t *) a;
	const struct zenfunc_t *bfunc = (const struct zenfunc_t *) b;
	unsigned long amsec, bmsec;
	
	amsec = afunc->total.sec * 1000 + afunc->total.msec;
	bmsec = bfunc->total.sec * 1000 + bfunc->total.msec;
	
	return bmsec - amsec;
}


/**
 * ZenProfilerShutdown:
 *
 * Write out our profiling info and cleanup the mess we made.
 **/
static void
ZenProfilerShutdown (void)
{
	GSList *slist;
	
	ZenTimerMTStop (zenprof.ztimer);
	
	ztime_diff (zenprof.ztimer.start, zenprof.ztimer.stop, &zenprof.total);
	
	fprintf (ZENLOG, "ZenProfiler\n\n");
	fprintf (ZENLOG, "Flat profile:\n\n");
	fprintf (ZENLOG, "  %%   cumulative   self              self   total\n");
	fprintf (ZENLOG, " time   seconds   seconds    calls  s/call  s/call  name\n");
	fprintf (ZENLOG, "------ ---------  -------  ------- ------- -------  ----\n");
	zenprof.list = g_slist_sort (zenprof.list, zenfunccmp);
	for (slist = zenprof.list; slist; slist = slist->next)
		zen_log (NULL, slist->data, NULL);
	g_hash_table_destroy (zenprof.hash);
	g_slist_free (zenprof.list);
	fprintf (ZENLOG, "\n %%         the percentage of the total running time of the\n");
	fprintf (ZENLOG, "time       program used by this function.\n\n");
	fprintf (ZENLOG, "cumulative a running sum of the number of seconds accounted\n");
	fprintf (ZENLOG, " seconds   for by this function and those listed above it.\n\n");
	fprintf (ZENLOG, " self      the number of seconds accounted for by this\n");
	fprintf (ZENLOG, "seconds    function alone.  This is the major sort for this\n");
	fprintf (ZENLOG, "           listing.\n\n");
	fprintf (ZENLOG, "calls      the number of times this function was invoked, if\n");
	fprintf (ZENLOG, "           this function is profiled, else blank.\n\n");
	fprintf (ZENLOG, " self      the average number of seconds spent in this\n");
	fprintf (ZENLOG, "s/call     function per call, if this function is profiled,\n");
	fprintf (ZENLOG, "           else blank.\n\n");
	fprintf (ZENLOG, " total     the average number of seconds spent in this\n");
	fprintf (ZENLOG, " s/call    function and its descendents per call, if this \n");
	fprintf (ZENLOG, "           function is profiled, else blank.\n\n");
	fprintf (ZENLOG, "name       the name of the function.\n\n");
	
	if (zenprof.log)
		fclose (zenprof.log);
}

#else /* ENABLE_ZENPROFILER */

#define ZenProfilerInit(logfile)
#define ZenProfilerLazy(func) func
#define ZenProfilerStart(zstart)
#define ZenProfilerStop(zstop)
#define ZenProfilerReport(ztimer)
#define ZenProfiler_return(ztimer) return
#define ZenProfiler_return_val(ztimer, retval) return (retval)
#define ZenShutdown()

#endif /* ENABLE_ZENPROFILER */

#define zen(func) ZenProfilerLazy(func)

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* __ZENPROFILER_H__ */


syntax highlighted by Code2HTML, v. 0.9.1