/*
** SellaNMS
** $Id: trace.cxx,v 1.6 2005/11/26 18:42:27 bill Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

static const char rcsid[] = "$Id: trace.cxx,v 1.6 2005/11/26 18:42:27 bill Exp $";

#include "trace.h"
#include "util.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <syslog.h>
#include <sys/time.h>
#include <sys/resource.h>

bool initialized = false;

unsigned long severity = LOG_DEBUG;

unsigned long traceoptions;
pthread_mutex_t traceoptions_mutex;

hash_map<string, time_clock_t *>	timers;
pthread_mutex_t timers_mutex;

struct nmsresource {
	struct timeval timer;
	struct rusage	system;
} resources;

char* file_line = NULL;
const size_t file_line_len = 64;
pthread_mutex_t file_line_mutex;

bool is_initialized() {
	return initialized;
}

void __set_file_line(const char* file, unsigned long line) {
	assert(initialized == true);
	assert(file != NULL);

	pthread_mutex_lock(&file_line_mutex);

	SAFE_FREE(file_line);

	if ((file_line = (char*) malloc(file_line_len)) == NULL) {
		syslog(severity, "failed to allocate memory for file_line in __set_file_line() [trace.c]");
		pthread_mutex_unlock(&file_line_mutex);

		return;
	}

	snprintf(file_line, file_line_len, "%s +%lu", file, line);
}

void __trace_init() {
	assert(initialized == false);

	timers.clear();
	memset(&traceoptions, 0, sizeof(unsigned long));
	file_line = NULL;

	pthread_mutex_init(&traceoptions_mutex, NULL);
	pthread_mutex_init(&timers_mutex, NULL);
	pthread_mutex_init(&file_line_mutex, NULL);

	memset(&resources, 0, sizeof(struct nmsresource));
	gettimeofday(&resources.timer, NULL);

	initialized = true;
}

void __trace_free() {
	hash_map<string, time_clock_t*>::iterator it;

	assert(initialized == true);

	if (file_line != NULL) {
		pthread_mutex_trylock(&file_line_mutex);
		free(file_line);
		file_line = NULL;
		pthread_mutex_unlock(&file_line_mutex);
	}

	while ((it = timers.begin()) != timers.end()) {
		__time_stop(it->first.c_str());
	}
	timers.clear();

	memset(&traceoptions, 0, sizeof(unsigned long));

	pthread_mutex_destroy(&traceoptions_mutex);
	pthread_mutex_destroy(&timers_mutex);
	pthread_mutex_destroy(&file_line_mutex);

	initialized = false;
}

void __trace(unsigned long flags, const char *format, ...) {
	va_list args;

	assert(initialized == true);

	if (traceoptions & flags) {
		va_start(args, format);
		vsyslog(severity, format, args);
		va_end(args);
	}
}

void __trace_debug(unsigned long flags, const char *format, ...) {
	char* ptr;
	size_t len;
	va_list args;

	assert(initialized == true);

	/* Just incase it wasn't locked. */
	pthread_mutex_trylock(&file_line_mutex);

	if (traceoptions & flags) {
		len = strlen(format) + file_line_len + 4;

		if (file_line == NULL) {
			file_line = strdup("-");
		}

		va_start(args, format);
		
		if ((ptr = (char*) malloc(len)) != NULL) {
			snprintf(ptr, len, "%s [%s]", format, file_line);
			vsyslog(severity, ptr, args);
			free(ptr);
		}

		va_end(args);
	}
	SAFE_FREE(file_line);

	pthread_mutex_unlock(&file_line_mutex);
}

void __lmsg(const char *format, ...) {
	va_list	args;

	assert(initialized == true);

	va_start(args, format);
	vsyslog(severity, format, args);
	va_end(args);
}

void __lmsg_debug(const char *format, ...) {
	char* ptr;
	size_t len;
	va_list args;

	assert(initialized == true);

	/* Just incase it wasn't locked. */
	pthread_mutex_trylock(&file_line_mutex);

	len = strlen(format) + file_line_len + 4;

	if (file_line == NULL) {
		file_line = strdup("-:0");
	}

	va_start(args, format);
	
	if ((ptr = (char*) malloc(len)) != NULL && file_line != NULL) {
		snprintf(ptr, len, "%s [%s]", format, file_line);
		vsyslog(severity, ptr, args);
		free(ptr);
	}

	va_end(args);

	SAFE_FREE(file_line);

	pthread_mutex_unlock(&file_line_mutex);
}

void __set_traceoption(unsigned long flags) {
	assert(initialized == true);

	pthread_mutex_lock(&traceoptions_mutex);
	traceoptions |= flags;
	pthread_mutex_unlock(&traceoptions_mutex);
}

void __unset_traceoption(unsigned long flags) {
	assert(initialized == true);

	pthread_mutex_lock(&traceoptions_mutex);
	traceoptions &= ~flags;
	pthread_mutex_unlock(&traceoptions_mutex);
}

bool __isset_traceoption(unsigned long flags) {
	assert(initialized == true);

	return (traceoptions & flags) ? true : false;
}

void __time_start(const char *name) {
	time_clock_t	*timer = new time_clock_t;

	assert(initialized == true);

	if (timers.find(name) == timers.end()) {
		memset(timer, 0, sizeof(time_clock_t));
		gettimeofday(&(timer->start), NULL);

		pthread_mutex_lock(&timers_mutex);
		timers[name] = timer;
		pthread_mutex_unlock(&timers_mutex);
	} else {
		TRACE(TRACE_DEBUG, "Timer start failed: attempted to start timer on an already existing timer name, '%s'.", name);
	}
}

void __time_stop(const char *name) {
	time_clock_t	*timer = NULL;

	assert(initialized == true);

	if (timers.find(name) != timers.end()) {
		timer = timers.find(name)->second;
		gettimeofday(&(timer->stop), NULL);

		pthread_mutex_lock(&timers_mutex);
		timers.erase(name);
		pthread_mutex_unlock(&timers_mutex);
	} else {
		TRACE(TRACE_DEBUG, "Timer stop failed: attempted to stop timer on a non-existant timer name, '%s'.", name);
		return;
	}
	if (timer != NULL) {
		subtract_timeval(timer->stop, timer->start);
		TRACE(TRACE_TIMER, "Timer '%s' completed in %d.%06d seconds.", name, timer->stop.tv_sec, timer->stop.tv_usec);

		delete timer;
	}
}

void __time(const char *name) {
	assert(initialized == true);

	if (timers.find(name) == timers.end()) {
		TIME_START(name);
	} else {
		TIME_STOP(name);
		TIME_START(name);
	}
}

void __set_severity(unsigned long s) {
	severity = s;
}

unsigned long __get_severity() {
	return severity;
}

unsigned long __get_traceoptions() {
	return traceoptions;
}

double __get_procload(double trigger) {
	struct rusage	ru;
	struct timeval	timer;

	struct timeval used;
	struct timeval now;
	struct timeval beta;

	double			realtime;
	double			cputime;
	double			systime;
	
	double			load = 0.0;

	if (getrusage(RUSAGE_SELF, &ru) == 0) {
		gettimeofday(&now, NULL);

		/* Determine how much time has passed since the last call. */
		memcpy(&timer, &now, sizeof(struct timeval));
		subtract_timeval(timer, resources.timer);
		realtime = 0.0 + ((double)timer.tv_sec * 1.0) + ((double)timer.tv_usec * 0.000001);

		/* getrusage() is not very precise (typically .01 sec).  Short time periods generate innacurate information. */
		if (realtime > 5.0) {
			/* Determine how much cpu time has been used since the last call. */
			memcpy(&used, &ru.ru_utime, sizeof(struct timeval));
			subtract_timeval(used, resources.system.ru_utime);
			cputime = 0.0 + ((double)used.tv_sec * 1.0) + ((double)used.tv_usec * 0.000001);

			/* DEBUG */
			memcpy(&beta, &ru.ru_stime, sizeof(struct timeval));
			subtract_timeval(beta, resources.system.ru_stime);
			systime = 0.0 + ((double)beta.tv_sec * 1.0) + ((double)beta.tv_usec * 0.000001);

			/* Determine the load average since the last call. */
			fprintf(stderr, "systime: %f\n", systime);
			fprintf(stderr, "cputime: %f\n", cputime);
			fprintf(stderr, "realtime: %f\n", realtime);

			load = (cputime / realtime) * 100.0;
			if (load > trigger) {
				fprintf(stderr, "\nProcess load exceeds the warning level of %f%%; it is currently %f%%.\n\n", trigger, load);
			}

			/* Save current measurments for next run. */
			memcpy(&resources.system, &ru, sizeof(struct rusage));
			memcpy(&resources.timer, &now, sizeof(struct timeval));
		}
	}
	return load;
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim600: fdm=marker
** vim: noet ts=3 sw=3
*/
