//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
// --------------------------------------------------------------------
//                                                                     
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF 
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A      
// PARTICULAR PURPOSE.                                                 
//                                                                     
// --------------------------------------------------------------------
#include "Performance.h"
#include <windows.h>
#include "utility.h"

using namespace std;

CPerformanceMonitor::CPerformanceMonitor(DWORD period, TCHAR* log)
{
	LOGZONES(TEXT("%S: ENTER %d@%S"), __FUNCTION__, __LINE__, __FILE__);

	m_bEndThread = false;
	
	// Set period
	m_dwSamplingPeriod = period;

	// Reset data
	m_TotalCpuUtil = 0.0;
	m_nRecordings = 0;
	m_bWasPegged = false;
	
	// Current state: paused
	m_sCurrentState = CPerformanceMonitor::Paused;

	// Events for controlling the thread
	if (!(m_evStartThread = CreateEvent(NULL, false, false, _T("PerfMonitor:StartThread"))))
	{
		LOG(TEXT("%S: ERROR %d@%S - Could not create start thread event (GLE: 0x%0x)"), __FUNCTION__, __LINE__, __FILE__, GetLastError());
		throw;
	}
	
	if (!(m_evDestroyThread = CreateEvent(NULL, false, false, _T("PerfMonitor:DestroyThread"))))
	{
		LOG(TEXT("%S: ERROR %d@%S - Could not create destroy thread event (GLE: 0x%0x)"), __FUNCTION__, __LINE__, __FILE__, GetLastError());
		throw;
	}

	// Create the monitor thread
	if (!(m_hPerformanceThread = CreateThread(NULL, 0, StaticThreadProc, this, 0, &m_dwThreadID)))
	{
		LOG(TEXT("%S: ERROR %d@%S - Could not create performance thread (GLE: 0x%0x)"), __FUNCTION__, __LINE__, __FILE__, GetLastError());
		throw;
	}

	// Set this to the highest priority so we never get interrupted when taking the tick and idle counts
	SetThreadPriority(m_hPerformanceThread, THREAD_PRIORITY_HIGHEST);

	if (log)
	{
		wsprintf(m_szLogFile, _T("%s"), log);
		// Share Mode: 0, Sec Desc: NULL, 
		m_hLogFile = CreateFile(m_szLogFile, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		if(m_hLogFile == INVALID_HANDLE_VALUE) {
			LOG(TEXT("%S: ERROR %d@%S - CreateFile for %s failed with (GLE: 0x%0x)"), __FUNCTION__, __LINE__, __FILE__, m_szLogFile, GetLastError());
		}
	}
	else m_hLogFile = INVALID_HANDLE_VALUE;
	
	m_pDroppedFrames = NULL;
	m_pRendererQualProp = NULL;

	dwOverflow = 0;

	//by default we do not calculate memory status
	bMemoryStatus = FALSE;

	LOGZONES(TEXT("%S: EXIT %d@%S"), __FUNCTION__, __LINE__, __FILE__);
	
}

CPerformanceMonitor::~CPerformanceMonitor()
{
	LOGZONES(TEXT("%S: ENTER %d@%S"), __FUNCTION__, __LINE__, __FILE__);

	// Signal that the monitor thread should exit and wait for it to do so
	m_bEndThread = true;

	// Unpause the monitor thread
	UnPause();

	WaitForSingleObject(m_evDestroyThread, INFINITE);

	// Now we are paused
	m_sCurrentState = CPerformanceMonitor::Paused;

	// Close the event handles
	if(m_evStartThread)
		CloseHandle(m_evStartThread);
	if(m_evDestroyThread)
		CloseHandle(m_evDestroyThread);

	//Close thread handle
	if(m_hPerformanceThread)
		CloseHandle(m_hPerformanceThread);

	// Close the log file if there was one
	if (m_hLogFile != INVALID_HANDLE_VALUE)
	{
		FlushLog();
		CloseHandle(m_hLogFile);
	}

	LOGZONES(TEXT("%S: EXIT %d@%S"), __FUNCTION__, __LINE__, __FILE__);
	
}

void CPerformanceMonitor::SetMemoryMonitor()
{
	LOGZONES(TEXT("%S: ENTER %d@%S"), __FUNCTION__, __LINE__, __FILE__);

	bMemoryStatus = TRUE;
	
	LOGZONES(TEXT("%S: EXIT %d@%S"), __FUNCTION__, __LINE__, __FILE__);
}


//set the interface pointers for getting dropped frame information

void CPerformanceMonitor::SetStatsInterfaces(IAMDroppedFrames* pDroppedFrames, IQualProp* pQualProp, IAMNetworkStatus *pNetworkStatus)
{
	LOGZONES(TEXT("%S: ENTER %d@%S"), __FUNCTION__, __LINE__, __FILE__);

	m_pDroppedFrames = pDroppedFrames;
	m_pRendererQualProp = pQualProp;
	m_pNetworkStatus = pNetworkStatus;

	LOGZONES(TEXT("%S: EXIT %d@%S"), __FUNCTION__, __LINE__, __FILE__);
}

void CPerformanceMonitor::ResetData()
{
	LOGZONES(TEXT("%S: ENTER %d@%S"), __FUNCTION__, __LINE__, __FILE__);

	// Reset recordings
	m_TotalCpuUtil = 0.0;
	m_nRecordings = 0;
	dwOverflow = 0;

	LOGZONES(TEXT("%S: EXIT %d@%S"), __FUNCTION__, __LINE__, __FILE__);
}

HRESULT CPerformanceMonitor::Pause()
{
	LOGZONES(TEXT("%S: ENTER %d@%S"), __FUNCTION__, __LINE__, __FILE__);

	// Set as paused - the thread wil eventually pass
	m_sCurrentState = CPerformanceMonitor::Paused;

	LOGZONES(TEXT("%S: EXIT %d@%S"), __FUNCTION__, __LINE__, __FILE__);
	
	return S_OK;
}

HRESULT CPerformanceMonitor::UnPause()
{
	LOGZONES(TEXT("%S: ENTER %d@%S"), __FUNCTION__, __LINE__, __FILE__);

	// Make sure the pause flag is not set
	m_sCurrentState = CPerformanceMonitor::Running;

	// Signal the thread to start
	SetEvent(m_evStartThread);

	LOGZONES(TEXT("%S: EXIT %d@%S"), __FUNCTION__, __LINE__, __FILE__);
	
	return S_OK;
}

CPerformanceMonitor::State CPerformanceMonitor::GetState()
{
	LOGZONES(TEXT("%S: ENTER/EXIT %d@%S"), __FUNCTION__, __LINE__, __FILE__);

	return m_sCurrentState;
}

//what is the avg utalization across all samples

float CPerformanceMonitor::GetAvgUtilization()
{
	float fAvgCPU = 0;

	LOGZONES(TEXT("%S: ENTER %d@%S"), __FUNCTION__, __LINE__, __FILE__);

	//need to account for the number of times we reset m_nRecordings in Monitor( )
	if (m_nRecordings || dwOverflow)
		fAvgCPU = m_TotalCpuUtil/((dwOverflow*MAX_RECORDINGS) + m_nRecordings);

	LOGZONES(TEXT("%S: EXIT %d@%S"), __FUNCTION__, __LINE__, __FILE__);
	
	return fAvgCPU;

}

//did CPU utalization ever go above 90%

bool CPerformanceMonitor::WasPegged()
{
	LOGZONES(TEXT("%S: ENTER/EXIT %d@%S"), __FUNCTION__, __LINE__, __FILE__);
	
	return m_bWasPegged;
}

//write the infromation in our buffer into file

void CPerformanceMonitor::FlushLog()
{
	TCHAR logstr[256];
	int nChars  = 0;
	DWORD nBytesWritten = 0;	

	LOGZONES(TEXT("%S: ENTER %d@%S"), __FUNCTION__, __LINE__, __FILE__);

	//Print out file header, but only print first time we are in here
	if(!dwOverflow) {
		//we always print out this information
		nChars = wsprintf(logstr, _T("Time(ms),CPU Utalization"));

		//no need to print video information if we had none
		if(m_pRendererQualProp || m_pDroppedFrames) 
			nChars = wsprintf(logstr, _T("%s,Frames drawn, Decoder dropped,Renderer dropped"), logstr);

		//no need to print network info if we had none
		if(m_pNetworkStatus) 
			nChars = wsprintf(logstr, _T("%s,Pkt Lost,Pkt Recovered,Pkt Recieved"), logstr);

		//if we are asked for memory information append that too
		if(bMemoryStatus) 
			nChars = wsprintf(logstr, _T("%s,Memory Load(%)"), logstr);

		//finally append the new line
		nChars = wsprintf(logstr, _T("%s\r\n"), logstr);
		
		WriteFile(m_hLogFile, logstr, nChars*sizeof(TCHAR), &nBytesWritten, NULL);
	}
	
	for(int i = 0; i < m_nRecordings; i++)
	{
		//we always print out this information
		nChars = wsprintf(logstr, _T("%d,%d"), 
			recordingBuffer[i].time, 
			recordingBuffer[i].utilization);

		//no need to print video information if we had none
		if(m_pRendererQualProp || m_pDroppedFrames) {
			nChars = wsprintf(logstr, _T("%s,%d,%d,%d"), 
				logstr,
				recordingBuffer[i].drawn, 
				recordingBuffer[i].decdrops,  
				recordingBuffer[i].rendrops);
		}

		//no need to print network info if we had none
		if(m_pNetworkStatus) {
			nChars = wsprintf(logstr, _T("%s,%d,%d,%d"), 
				logstr,
				recordingBuffer[i].lostpkts,
				recordingBuffer[i].recoveredpkts,
				recordingBuffer[i].recvdpkts);
		}

		//if we are asked for memory information append that too
		if(bMemoryStatus) {
			nChars = wsprintf(logstr, _T("%s,%d"), 
				logstr,
				recordingBuffer[i].MemLoad);
		}

		//finally append the new line
		nChars = wsprintf(logstr, _T("%s\r\n"), 
				logstr);
		
		nBytesWritten = 0;
		WriteFile(m_hLogFile, logstr, nChars*sizeof(TCHAR), &nBytesWritten, NULL);
	}
	
	LOGZONES(TEXT("%S: EXIT %d@%S"), __FUNCTION__, __LINE__, __FILE__);
	
}

//threadProc

DWORD CPerformanceMonitor::Monitor()
{
	int utilization = 0.0;	
	DWORD dwInitialIdle, dwIdleEnd, dwIdleStart;
	DWORD dwInitialTick, dwStartTick, dwStopTick;
	DWORD dwIdleCount, dwTickCount, dwTotalTicks;
	MEMORYSTATUS mst;	
	DWORD dwTotalPages, dwPagesFree, dwPagesUsed, dwMemLoad;
	
	LOGZONES(TEXT("%S: ENTER %d@%S"), __FUNCTION__, __LINE__, __FILE__);

	// Initialize variables to get information about our memory state
	memset(&mst, 0, sizeof(mst));
	mst.dwLength = sizeof(mst);

	//continue monitoring until we are asked to stop
	while(1)
	{
		// If we have been signaled to pause - wait on the start event until it is signaled
		if(	m_sCurrentState == CPerformanceMonitor::Paused) {
			
			LOGZONES(TEXT("%S: Waiting on start thread"), __FUNCTION__, __LINE__, __FILE__);
			
			WaitForSingleObject(m_evStartThread, INFINITE);

			LOGZONES(TEXT("%S: Done waiting on start thread"), __FUNCTION__, __LINE__, __FILE__);
			
			// Get the starting tick and idle counts
			dwInitialTick = GetTickCount();
			dwInitialIdle = GetIdleTime();
		}

		//if we are about to exceed the size of our data buffer, flush the information into the log and start over
		//remeber to increment the dwOverflow field, otherwise Avg Utalization calculations will be off
		if (m_nRecordings == (MAX_RECORDINGS - 1))
		{
			FlushLog();
			m_nRecordings = 0;
			dwOverflow++;
			LOGZONES(TEXT("%S: In-memory status buffer full, flushing data to log for the %dth time."), __FUNCTION__, __LINE__, __FILE__, dwOverflow);						
		}

		if (m_bEndThread)
		{
			SetEvent(m_evDestroyThread);
			LOGZONES(TEXT("%S: EXIT %d@%S"), __FUNCTION__, __LINE__, __FILE__);			
			return 0;
		}

		dwStartTick = GetTickCount();
		dwIdleStart = GetIdleTime();

		//sleep for the time specified, this determines our sampling rate, this value is passed into the constructor
		Sleep(m_dwSamplingPeriod);

		dwStopTick = GetTickCount();
		dwIdleEnd = GetIdleTime();

		//how long was the cpu idle while we slept
		dwIdleCount = (dwIdleEnd < dwIdleStart) ? (0xffffffff - (dwIdleStart - dwIdleEnd)) : (dwIdleEnd - dwIdleStart);
		//how long did we sleep
		dwTickCount = (dwStopTick < dwStartTick) ? (0xffffffff - (dwStartTick - dwStopTick)) : (dwStopTick - dwStartTick);
		//how long has it been since we started this thread
		dwTotalTicks = (dwStopTick < dwInitialTick) ? (0xffffffff - (dwInitialTick - dwStopTick)) : (dwStopTick - dwInitialTick);

		//utalization is percentage of time the CPU was not idle
		utilization = 100 - (100*dwIdleCount/dwTickCount);

		//we consider pegged if CPU utalization went over 90%, this can be changed if you want to check a higher or lower bound
		if (utilization >= 90)
			m_bWasPegged = true;

		//we have also been asked for memory status
		if(bMemoryStatus) {
			GlobalMemoryStatus(&mst);
			dwTotalPages = mst.dwTotalPhys/1024;
			dwPagesFree = mst.dwAvailPhys / 1024;
			dwPagesUsed = (mst.dwTotalPhys - mst.dwAvailPhys) / 1024;
			dwMemLoad= mst.dwMemoryLoad;
		}

		if (m_hLogFile)
		{			
			long decdrops = -1;
			int drawn = -1;
			int rendrops = -1;
			long lostPackets = -1;
			long recoveredPackets = -1;
			long receivedPackets = -1;			
			if (!m_bEndThread)
			{
				//if you want to see this information printed out in the debug log, set the ENABLE_LOG variable
				//WARNING: performance can be effected by printing of this information
				LOGZONES( _T("%S: Time:CPU:%d:%d:"), __FUNCTION__, dwTotalTicks, utilization);
				if (m_pRendererQualProp)
				{
					m_pRendererQualProp->get_FramesDrawn(&drawn);
					m_pRendererQualProp->get_FramesDroppedInRenderer(&rendrops);
					LOGZONES( _T("Drawn,Ren: %d,%d:"), drawn, rendrops);					
				}
				if(m_pDroppedFrames) 
				{
					m_pDroppedFrames->GetNumDropped(&decdrops);
					LOGZONES( _T("Dec: %d:"), drawn, decdrops);										
				}
				if (m_pNetworkStatus)
				{	m_pNetworkStatus->get_LostPackets(&lostPackets);
					m_pNetworkStatus->get_RecoveredPackets(&recoveredPackets);
					m_pNetworkStatus->get_ReceivedPackets(&receivedPackets);
					LOGZONES( _T("Net: %d,%d,%d"), lostPackets, recoveredPackets, receivedPackets);
				}
				LOGZONES( _T("\n"));
				
				//print out memory information if asked
				if(bMemoryStatus) {
					LOGZONES(TEXT("%S: Memory (KB):     %5d total  %5d used  %5d free  %%%d loaded\r\n"), __FUNCTION__,
					dwTotalPages, dwPagesUsed, dwPagesFree, dwMemLoad);
				}
			}
			//save off the information in the buffer, we can write off the buffer to file later
			recordingBuffer[m_nRecordings].time = dwTotalTicks;
			recordingBuffer[m_nRecordings].utilization = utilization;
			recordingBuffer[m_nRecordings].drawn = drawn;
			recordingBuffer[m_nRecordings].decdrops = decdrops;
			recordingBuffer[m_nRecordings].rendrops = rendrops;
			recordingBuffer[m_nRecordings].lostpkts = lostPackets;
			recordingBuffer[m_nRecordings].recoveredpkts = recoveredPackets;
			recordingBuffer[m_nRecordings].recvdpkts = receivedPackets;
			
			if(bMemoryStatus) {
				recordingBuffer[m_nRecordings].MemLoad = dwMemLoad;
			}

		}

		// Cumulative utalization, used to calculate avg utalization across all recordings
		m_TotalCpuUtil += utilization;
		m_nRecordings++;
	}

	LOGZONES(TEXT("%S: EXIT %d@%S"), __FUNCTION__, __LINE__, __FILE__);
	
	return NULL;
}

//ThreadProc that will actually call the threadProc function inside the CPerformanceMonitor

DWORD WINAPI CPerformanceMonitor::StaticThreadProc(PVOID pArg)
{
	LOGZONES(TEXT("%S: ENTERING THREAD %d@%S"), __FUNCTION__, __LINE__, __FILE__);
	
	CPerformanceMonitor *pCPM = (CPerformanceMonitor*) pArg;

	DWORD retval = pCPM->Monitor();

	LOGZONES(TEXT("%S: EXITING THREAD %d@%S"), __FUNCTION__, __LINE__, __FILE__);
	
	return retval;
}
