/** 
 * @file llmetricperformancetester.cpp
 * @brief LLMetricPerformanceTesterBasic and LLMetricPerformanceTesterWithSession classes implementation
 *
 * $LicenseInfo:firstyear=2004&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"

#include "indra_constants.h"
#include "llerror.h"
#include "llsdserialize.h"
#include "lltreeiterators.h"
#include "llmetricperformancetester.h"
#include "llfasttimer.h"

//----------------------------------------------------------------------------------------------
// LLMetricPerformanceTesterBasic : static methods and testers management
//----------------------------------------------------------------------------------------------

LLMetricPerformanceTesterBasic::name_tester_map_t LLMetricPerformanceTesterBasic::sTesterMap ;

/*static*/ 
void LLMetricPerformanceTesterBasic::cleanupClass() 
{
	for (name_tester_map_t::value_type& pair : sTesterMap)
	{
		delete pair.second;
	}
	sTesterMap.clear() ;
}

/*static*/ 
BOOL LLMetricPerformanceTesterBasic::addTester(LLMetricPerformanceTesterBasic* tester) 
{
	llassert_always(tester != NULL);	
	std::string name = tester->getTesterName() ;
	if (getTester(name))
	{
		LL_ERRS() << "Tester name is already used by some other tester : " << name << LL_ENDL ;
		return FALSE;
	}

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

/*static*/ 
void LLMetricPerformanceTesterBasic::deleteTester(std::string name)
{
	name_tester_map_t::iterator tester = sTesterMap.find(name);
	if (tester != sTesterMap.end())
	{
		delete tester->second;
		sTesterMap.erase(tester);
	}
}

/*static*/ 
LLMetricPerformanceTesterBasic* LLMetricPerformanceTesterBasic::getTester(std::string name) 
{
	// Check for the requested metric name
	name_tester_map_t::iterator found_it = sTesterMap.find(name) ;
	if (found_it != sTesterMap.end())
	{
		return found_it->second ;
	}
	return NULL ;
}

/*static*/ 
// Return TRUE if this metric is requested or if the general default "catch all" metric is requested
BOOL LLMetricPerformanceTesterBasic::isMetricLogRequested(std::string name)
{
	return (LLTrace::BlockTimer::sMetricLog && ((LLTrace::BlockTimer::sLogName == name) || (LLTrace::BlockTimer::sLogName == DEFAULT_METRIC_NAME)));
}

/*static*/ 
LLSD LLMetricPerformanceTesterBasic::analyzeMetricPerformanceLog(std::istream& is)
{
	LLSD ret;
	LLSD cur;
	
	while (!is.eof() && LLSDParser::PARSE_FAILURE != LLSDSerialize::fromXML(cur, is))
	{
		for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter)
		{
			std::string label = iter->first;
			
			LLMetricPerformanceTesterBasic* tester = LLMetricPerformanceTesterBasic::getTester(iter->second["Name"].asString()) ;
			if(tester)
			{
				ret[label]["Name"] = iter->second["Name"] ;
				
				auto num_of_metrics = tester->getNumberOfMetrics() ;
				for(size_t index = 0 ; index < num_of_metrics ; index++)
				{
					ret[label][ tester->getMetricName(index) ] = iter->second[ tester->getMetricName(index) ] ;
				}
			}
		}
	}
	
	return ret;
}

/*static*/ 
void LLMetricPerformanceTesterBasic::doAnalysisMetrics(std::string baseline, std::string target, std::string output)
{
	if(!LLMetricPerformanceTesterBasic::hasMetricPerformanceTesters())
	{
		return ;
	}
	
	// Open baseline and current target, exit if one is inexistent
	llifstream base_is(baseline.c_str());
	llifstream target_is(target.c_str());
	if (!base_is.is_open() || !target_is.is_open())
	{
		LL_WARNS() << "'-analyzeperformance' error : baseline or current target file inexistent" << LL_ENDL;
		base_is.close();
		target_is.close();
		return;
	}
	
	//analyze baseline
	LLSD base = analyzeMetricPerformanceLog(base_is);
	base_is.close();
	
	//analyze current
	LLSD current = analyzeMetricPerformanceLog(target_is);
	target_is.close();
	
	//output comparision
	llofstream os(output.c_str());
	
	os << "Label, Metric, Base(B), Target(T), Diff(T-B), Percentage(100*T/B)\n"; 
	for (LLMetricPerformanceTesterBasic::name_tester_map_t::value_type& pair : LLMetricPerformanceTesterBasic::sTesterMap)
	{
		LLMetricPerformanceTesterBasic* tester = ((LLMetricPerformanceTesterBasic*)pair.second);
		tester->analyzePerformance(&os, &base, &current) ;
	}
	
	os.flush();
	os.close();
}


//----------------------------------------------------------------------------------------------
// LLMetricPerformanceTesterBasic : Tester instance methods
//----------------------------------------------------------------------------------------------

LLMetricPerformanceTesterBasic::LLMetricPerformanceTesterBasic(std::string name) : 
	mName(name),
	mCount(0)
{
	if (mName == std::string())
	{
		LL_ERRS() << "LLMetricPerformanceTesterBasic construction invalid : Empty name passed to constructor" << LL_ENDL ;
	}

	mValidInstance = LLMetricPerformanceTesterBasic::addTester(this) ;
}

LLMetricPerformanceTesterBasic::~LLMetricPerformanceTesterBasic() 
{
}

void LLMetricPerformanceTesterBasic::preOutputTestResults(LLSD* sd) 
{
	incrementCurrentCount() ;
}

void LLMetricPerformanceTesterBasic::postOutputTestResults(LLSD* sd)
{
	LLTrace::BlockTimer::pushLog(*sd);
}

void LLMetricPerformanceTesterBasic::outputTestResults() 
{
	LLSD sd;

	preOutputTestResults(&sd) ; 
	outputTestRecord(&sd) ;
	postOutputTestResults(&sd) ;
}

void LLMetricPerformanceTesterBasic::addMetric(std::string str)
{
	mMetricStrings.push_back(str) ;
}

/*virtual*/ 
void LLMetricPerformanceTesterBasic::analyzePerformance(llofstream* os, LLSD* base, LLSD* current) 
{
	resetCurrentCount() ;

	std::string current_label = getCurrentLabelName();
	BOOL in_base = (*base).has(current_label) ;
	BOOL in_current = (*current).has(current_label) ;

	while(in_base || in_current)
	{
		LLSD::String label = current_label ;		

		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:
					LL_ERRS() << "unsupported metric " << mMetricStrings[index] << " LLSD type: " << (S32)(*current)[label][ mMetricStrings[index] ].type() << LL_ENDL ;
				}
			}	
		}

		incrementCurrentCount();
		current_label = getCurrentLabelName();
		in_base = (*base).has(current_label) ;
		in_current = (*current).has(current_label) ;
	}
}

/*virtual*/ 
void LLMetricPerformanceTesterBasic::compareTestResults(llofstream* 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 LLMetricPerformanceTesterBasic::compareTestResults(llofstream* 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 ) ;
}

//----------------------------------------------------------------------------------------------
// LLMetricPerformanceTesterWithSession
//----------------------------------------------------------------------------------------------

LLMetricPerformanceTesterWithSession::LLMetricPerformanceTesterWithSession(std::string name) : 
	LLMetricPerformanceTesterBasic(name),
	mBaseSessionp(NULL),
	mCurrentSessionp(NULL)
{
}

LLMetricPerformanceTesterWithSession::~LLMetricPerformanceTesterWithSession()
{
	if (mBaseSessionp)
	{
		delete mBaseSessionp ;
		mBaseSessionp = NULL ;
	}
	if (mCurrentSessionp)
	{
		delete mCurrentSessionp ;
		mCurrentSessionp = NULL ;
	}
}

/*virtual*/ 
void LLMetricPerformanceTesterWithSession::analyzePerformance(llofstream* os, LLSD* base, LLSD* current) 
{
	// Load the base session
	resetCurrentCount() ;
	mBaseSessionp = loadTestSession(base) ;

	// Load the current session
	resetCurrentCount() ;
	mCurrentSessionp = loadTestSession(current) ;

	if (!mBaseSessionp || !mCurrentSessionp)
	{
		LL_ERRS() << "Error loading test sessions." << LL_ENDL ;
	}

	// Compare
	compareTestSessions(os) ;

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


//----------------------------------------------------------------------------------------------
// LLTestSession
//----------------------------------------------------------------------------------------------

LLMetricPerformanceTesterWithSession::LLTestSession::~LLTestSession() 
{
}