/*
 * © Copyright 1996-2012 ECMWF.
 * 
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 
 * In applying this licence, ECMWF does not waive the privileges and immunities 
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#include "mars.h"

static timer *timers = NULL;

timer *get_timer(const char* name, const char *statname, boolean elapsed)
{
	timer *t = timers;

	while(t)
	{
		if(strcmp(name,t->name_) == 0)
			return t;
		t = t->next_;
	}

	t = NEW_CLEAR(timer);
	t->name_	= strcache(name);
	t->active_  = false;
	t->count_   = 0;
	t->timer_   = 0;
	t->total_   = 0;

	t->elapsed_    = elapsed; /* Whether to print CPU usage */
	t->cpu_        = 0;
	t->total_cpu_  = 0;

	t->statname_ = 0;
	if(statname)
		t->statname_ = strcache(statname);

	t->next_    = timers;
	timers      = t;

	return t;
}

int timer_start(timer* t)
{
	int e = 0;
	if(mars.notimers || (t == NULL))
		return 0;

	e = gettimeofday(&t->start_,NULL);
	if(e != 0)
		marslog(LOG_WARN|LOG_PERR,"Error starting timer '%s'",t->name_?t->name_:"unnamed");
	t->active_ = true;
	t->cpu_    = proc_cpu();
	return e;
}

int timer_stop(timer* t, long64 total)
{
	struct timeval stop, diff;
	int e;
	double c;

	if(mars.notimers || (t == NULL))
		return 0;

	e = gettimeofday(&stop,NULL);
	c = proc_cpu();

	if(e != 0)
		marslog(LOG_WARN|LOG_PERR,"Error stopping timer '%s'",t->name_?t->name_:"unnamed");

	if(!t->active_)
	{
		marslog(LOG_WARN,"Stopping non-started timer '%s'",t->name_?t->name_:"unnamed");
		return 1;
	}

	diff.tv_sec  = stop.tv_sec  - t->start_.tv_sec;
	diff.tv_usec = stop.tv_usec - t->start_.tv_usec;
	if (diff.tv_usec < 0)
	{
		diff.tv_sec--;
		diff.tv_usec += 1000000;
	}

	t->timer_ += (double)diff.tv_sec + ((double)diff.tv_usec / 1000000.);
	t->total_ += total;
	t->total_cpu_ += (c - t->cpu_);

	t->active_=false;
	t->count_++;

	return e;

}

double timer_value(timer *t)
{
	if(mars.notimers || (t == NULL))
		return 0.0;

	return t->timer_;
}

void timer_print_to_file(timer* t, FILE *f)
{
	fprintf(f,"%g:%d:%s:%d:%lld:%d:%g:%g\n",
				t->timer_, 
				t->active_, 
				t->name_, 
				t->count_, 
				t->total_, 
				t->elapsed_, 
				t->cpu_, 
				t->total_cpu_);
				/* t->statname_ */ /* Not all stats have a name */
}

void timer_print(timer* t)
{
	char *name;
	if(mars.notimers)
	{
		marslog(LOG_WARN,"Timers are disabled");
		return;
	}
	if(t == NULL)
	{
		marslog(LOG_WARN,"Internal error: timer_print() called with NULL timer");
		return;
	}
	name = t->name_?t->name_:"";

	marslog(LOG_DBUG,"timer_=%g:active_=%d:name_=%s:count_=%d:total_=%lld:elapsed_=%d:cpu_=%g:total_cpu_=%g",
				t->timer_, 
				t->active_, 
				name,
				t->count_, 
				t->total_, 
				t->elapsed_, 
				t->cpu_, 
				t->total_cpu_);
	
	/* Print timers longer than 1 second or those that have a rate */
	if((t->timer_>=1) || (t->total_ != 0 && t->timer_>0)) 
	{
		char cpu[1024] = "";

		if(! t->elapsed_ && t->total_cpu_ >= 1.0)
			sprintf(cpu,"cpu: %s",timename(t->total_cpu_));

		if(t->total_ != 0)
		{
			double rate = (double)t->total_/t->timer_;
			char bytes[80];
			sprintf(bytes,"%sbyte(s)",bytename(t->total_));
			marslog(LOG_INFO,"  %s: %s in %s [%sbyte/sec] %s",
							 name,bytes,(t->timer_>=1)?timename(t->timer_):"< 1 sec",bytename(rate),cpu);
		}
		else
			marslog(LOG_INFO,"  %s: wall: %s%s",name,timename(t->timer_),cpu);

		if(t->statname_)
			log_statistics(t->statname_,"%ld",(long)t->timer_);
	}
}

void timer_partial_rate(timer *t, double start, long64 total)
{
	if(mars.notimers)
	{
		marslog(LOG_WARN,"Timers are disabled");
		return;
	}
	if(t == NULL)
	{
		marslog(LOG_WARN,"Internal error: timer_partial_rate() called with NULL timer");
		return;
	}

	{
	double ptime = t->timer_ - start;
	long64 ptotal = total;
	char *name = t->name_?t->name_:"";
	if(ptime>=1)
	{
		double rate = (double)ptotal/ptime;
		char bytes[80];
		sprintf(bytes,"%sbyte(s)",bytename(ptotal));
		marslog(LOG_INFO,"  %s: %s in %s [%sbyte/sec]",
						name,bytes,timename(ptime),bytename(rate));
	}
	}
}

void print_all_timers()
{
	timer *t = timers;
	FILE  *f = NULL;

	if(mars.timers_file)
	{
		if((f = fopen(mars.timers_file,"a")) == NULL)
		{
			marslog(LOG_WARN|LOG_PERR,"Cannot open '%s'",mars.timers_file);
			marslog(LOG_WARN,"Disable printing timers to file");
			mars.timers_file = NULL;
		}
		else
			marslog(LOG_INFO,"Printing timers to file '%s'",mars.timers_file);
	}
	
	while(t)
	{
		timer_print(t);
		if(mars.timers_file)
			timer_print_to_file(t,f);
		t = t->next_;
	}

	if(mars.timers_file)
	{
		long long bytes = proc_mem();
		fprintf(f,"%lld:%s\n",bytes,"Memory used");
	}
}

void reset_all_timers()
{
	timer *t = timers;
	while(t)
	{
		t->count_     = 0;
		t->timer_     = 0;
		t->active_    = 0;
		t->total_     = 0;
		t->total_cpu_ = 0;
		t->cpu_       = 0;
		t->elapsed_   = 0;
		t = t->next_;
	}
}


/*************************************************
* Timed functions
**************************************************/

int timed_fread(char *buffer, int n, int length, FILE *f, timer *t)
{
	int r = 0;
	long64 total = 0;
	timer_start(t);
	if((r = fread(buffer,n,length,f)) > 0)
		total = r*n;

	timer_stop(t,total);

	return r;
}

int timed_wind_next(wind* w, FILE *f, char *buffer, long *length, timer *t)
{
	int r = 0;
	long64 total = 0;
	if((r = wind_next(w,f,buffer,length,t)) == 0)
		total = *length;

	return r;
}

int timed_fwrite(char *buffer, int n, int length, FILE *f, timer *t)
{
	int r = 0;
	long64 total = 0;
	timer_start(t);
	if((r = fwrite(buffer,n,length,f)) > 0)
		total = r*n;
	timer_stop(t,total);

	return r;
}

int timed_fclose(FILE *f, timer *t)
{
	int r = 0;
	timer_start(t);
	r = fclose(f);
	timer_stop(t,0);

	return r;
}

int timed_writetcp(void *data, char *buffer, int n, timer *t)
{
	int r = 0;
	long64 total = 0;
	timer_start(t);
	if((r = writetcp(data,buffer,n)) > 0)
		total = r;
	timer_stop(t,total);

	return r;
}

int timed_readtcp(void *data, char *buffer, int n, timer *t)
{
	int r = 0;
	long64 total = 0;
	timer_start(t);
	if((r = readtcp(data,buffer,n)) > 0)
		total = r;
	timer_stop(t,total);
	return r;
}

int timed_readany(FILE *f, char *buffer, long *length, timer *t)
{
	int e;
	long original = *length;
	long64 total = 0;
	timer_start(t);
	if(((e = _readany(f,buffer,length)) == NOERR) || (e == BUF_TO_SMALL))
		total = (e == BUF_TO_SMALL)?original:*length;
	timer_stop(t,total);

	if(e != NOERR && e != BUF_TO_SMALL && e != EOF)
	{
		if(e == NOT_FOUND_7777)
			marslog(LOG_WARN,"Group 7777 not found by readany",e);
		else
			marslog(LOG_WARN,"Error %d returned by readany",e);
	}

	return e;
}
