/** 
 * @file llmetricperformancetester.cpp
 * @brief LLMetricPerformanceTester class implementation
 *
 * $LicenseInfo:firstyear=2004&license=viewergpl$
 * 
 * Copyright (c) 2004-2009, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at
 * http://secondlifegrid.net/programs/open_source/licensing/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"

#include "indra_constants.h"
#include "llerror.h"
#include "llmath.h"
#include "llfontgl.h"
#include "llsdserialize.h"
#include "llstat.h"
#include "lltreeiterators.h"
#include "llmetricperformancetester.h"

LLMetricPerformanceTester::name_tester_map_t LLMetricPerformanceTester::sTesterMap ;

//static 
void LLMetricPerformanceTester::initClass() 
{
}
//static 
void LLMetricPerformanceTester::cleanClass() 
{
	for(name_tester_map_t::iterator iter = sTesterMap.begin() ; iter != sTesterMap.end() ; ++iter)
	{
		delete iter->second ;
	}
	sTesterMap.clear() ;
}

//static 
void LLMetricPerformanceTester::addTester(LLMetricPerformanceTester* tester) 
{
	if(!tester)
	{
		llerrs << "invalid tester!" << llendl ;
		return ;
	}
	
	std::string name = tester->getName() ;
	if(getTester(name))
	{
		llerrs << "Tester name is used by some other tester: " << name << llendl ;
		return ;
	}

	sTesterMap.insert(std::make_pair(name, tester));

	return ;
}
	
//static 
LLMetricPerformanceTester* LLMetricPerformanceTester::getTester(std::string label) 
{
	name_tester_map_t::iterator found_it = sTesterMap.find(label) ;
	if(found_it != sTesterMap.end())
	{
		return found_it->second ;
	}

	return NULL ;
}
	
LLMetricPerformanceTester::LLMetricPerformanceTester(std::string name, BOOL use_default_performance_analysis)
	: mName(name),
	mBaseSessionp(NULL),
	mCurrentSessionp(NULL),
	mCount(0),
	mUseDefaultPerformanceAnalysis(use_default_performance_analysis)
{
	if(mName == std::string())
	{
		llerrs << "invalid name." << llendl ;
	}

	LLMetricPerformanceTester::addTester(this) ;
}

/*virtual*/ 
LLMetricPerformanceTester::~LLMetricPerformanceTester() 
{
	if(mBaseSessionp)
	{
		delete mBaseSessionp ;
		mBaseSessionp = NULL ;
	}
	if(mCurrentSessionp)
	{
		delete mCurrentSessionp ;
		mCurrentSessionp = NULL ;
	}
}

void LLMetricPerformanceTester::incLabel()
{
	mCurLabel = llformat("%s-%d", mName.c_str(), mCount++) ;
}
void LLMetricPerformanceTester::preOutputTestResults(LLSD* sd) 
{
	incLabel() ;
	(*sd)[mCurLabel]["Name"] = mName ;
}
void LLMetricPerformanceTester::postOutputTestResults(LLSD* sd)
{
	LLMutexLock lock(LLFastTimer::sLogLock);
	LLFastTimer::sLogQueue.push((*sd));
}

void LLMetricPerformanceTester::outputTestResults() 
{
	LLSD sd ;
	preOutputTestResults(&sd) ; 

	outputTestRecord(&sd) ;

	postOutputTestResults(&sd) ;
}

void LLMetricPerformanceTester::addMetricString(std::string str)
{
	mMetricStrings.push_back(str) ;
}

const std::string& LLMetricPerformanceTester::getMetricString(U32 index) const 
{
	return mMetricStrings[index] ;
}

void LLMetricPerformanceTester::prePerformanceAnalysis() 
{
	mCount = 0 ;
	incLabel() ;
}

//
//default analyzing the performance
//
/*virtual*/ 
void LLMetricPerformanceTester::analyzePerformance(std::ofstream* os, LLSD* base, LLSD* current) 
{
	if(mUseDefaultPerformanceAnalysis)//use default performance analysis
	{
		prePerformanceAnalysis() ;

		BOOL in_base = (*base).has(mCurLabel) ;
		BOOL in_current = (*current).has(mCurLabel) ;

		while(in_base || in_current)
		{
			LLSD::String label = mCurLabel ;		
			
			if(in_base && in_current)
			{				
				*os << llformat("%s\n", label.c_str()) ;

				for(U32 index = 0 ; index < mMetricStrings.size() ; index++)
				{
					switch((*current)[label][ mMetricStrings[index] ].type())
					{
					case LLSD::TypeInteger:
						compareTestResults(os, mMetricStrings[index], 
							(S32)((*base)[label][ mMetricStrings[index] ].asInteger()), (S32)((*current)[label][ mMetricStrings[index] ].asInteger())) ;
						break ;
					case LLSD::TypeReal:
						compareTestResults(os, mMetricStrings[index], 
							(F32)((*base)[label][ mMetricStrings[index] ].asReal()), (F32)((*current)[label][ mMetricStrings[index] ].asReal())) ;
						break;
					default:
						llerrs << "unsupported metric " << mMetricStrings[index] << " LLSD type: " << (S32)(*current)[label][ mMetricStrings[index] ].type() << llendl ;
					}
				}	
			}

			incLabel() ;
			in_base = (*base).has(mCurLabel) ;
			in_current = (*current).has(mCurLabel) ;
		}
	}//end of default
	else
	{
		//load the base session
		prePerformanceAnalysis() ;
		mBaseSessionp = loadTestSession(base) ;

		//load the current session
		prePerformanceAnalysis() ;
		mCurrentSessionp = loadTestSession(current) ;

		if(!mBaseSessionp || !mCurrentSessionp)
		{
			llerrs << "memory error during loading test sessions." << llendl ;
		}

		//compare
		compareTestSessions(os) ;

		//release memory
		if(mBaseSessionp)
		{
			delete mBaseSessionp ;
			mBaseSessionp = NULL ;
		}
		if(mCurrentSessionp)
		{
			delete mCurrentSessionp ;
			mCurrentSessionp = NULL ;
		}
	}
}

//virtual 
void LLMetricPerformanceTester::compareTestResults(std::ofstream* os, std::string metric_string, S32 v_base, S32 v_current) 
{
	*os << llformat(" ,%s, %d, %d, %d, %.4f\n", metric_string.c_str(), v_base, v_current, 
						v_current - v_base, (v_base != 0) ? 100.f * v_current / v_base : 0) ;
}

//virtual 
void LLMetricPerformanceTester::compareTestResults(std::ofstream* os, std::string metric_string, F32 v_base, F32 v_current) 
{
	*os << llformat(" ,%s, %.4f, %.4f, %.4f, %.4f\n", metric_string.c_str(), v_base, v_current,						
						v_current - v_base, (fabs(v_base) > 0.0001f) ? 100.f * v_current / v_base : 0.f ) ;
}

//virtual 
LLMetricPerformanceTester::LLTestSession::~LLTestSession() 
{
}