/*==========================================================================
 *
 *  Copyright (C) 1999 - 2000 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       perfinfo.h
 *  Content:	Performance tracking related code
 *				
 *  History:
 *   Date		By		Reason
 *   ====		==		======
 *  ??-??-????  rodtoll	Created
 *	12-12-2000	rodtoll	Re-organize performance struct to handle data misalignment errors on IA64
 *
 ***************************************************************************/

#include "dncmni.h"
#include "PerfInfo.h"
#include <initguid.h>

// Name of block 
#define PERF_INSTANCE_BLOCK_NAME "{F10932E0-2556-4620-9ADE-F572406CFAEA}"

// GUID of block
DEFINE_GUID(PERF_INSTANCE_BLOCK_GUID, 
0xf10932e0, 0x2556, 0x4620, 0x9a, 0xde, 0xf5, 0x72, 0x40, 0x6c, 0xfa, 0xea);

#define PERF_INSTANCE_BLOCK_MUTEX_NAME "{2997F0C7-F135-405f-ABD4-8BAF491B3DAD}"

DEFINE_GUID(PERF_INSTANCE_BLOCK_MUTEX_GUID, 
0x2997f0c7, 0xf135, 0x405f, 0xab, 0xd4, 0x8b, 0xaf, 0x49, 0x1b, 0x3d, 0xad);

HANDLE g_hMutexInstanceBlock = NULL;
HANDLE g_hFileInstanceBlock = NULL;
BYTE *g_pbInstanceBlock = NULL;
PPERF_HEADER g_pperfHeader = NULL;
LONG *g_plNumEntries = NULL;
PPERF_APPLICATION g_pperfAppEntries = NULL;

#define PERF_INSTANCE_BLOCK_MAXENTRIES  100
#define PERF_INSTANCE_BLOCK_SIZE        ((sizeof( PERF_APPLICATION ) * PERF_INSTANCE_BLOCK_MAXENTRIES) + sizeof( PERF_HEADER ))
#define PERF_INFO_NAME_LENGTH		    64 // 38 for GUID string + room for Global prefix and mutex suffix

void PERF_Coalesce( DWORD dwProcessID = 0xFFFFFFFF );

#undef DPF_MODNAME
#define DPF_MODNAME "PERF_Initialize"
// PERF_Initialize
//
// Initialize the global "instance" list memory block 
//
HRESULT PERF_Initialize( )
{
    HRESULT hr = S_OK;
    BOOL fAlreadyExists = FALSE;


	if (DNGetOSType() == VER_PLATFORM_WIN32_NT)
	{
	    g_hMutexInstanceBlock = CreateMutexA( DNGetNullDacl(), FALSE, "Global\\" PERF_INSTANCE_BLOCK_MUTEX_NAME );
	}
	else
	{
	    g_hMutexInstanceBlock = CreateMutexA( DNGetNullDacl(), FALSE, PERF_INSTANCE_BLOCK_MUTEX_NAME );
	}

    if( g_hMutexInstanceBlock == NULL )
    {
        hr = GetLastError();
        DPFX(DPFPREP,  0, "Error initializing instance block hr=0x%x", hr );
        goto EXIT_ERROR;
    }

	if (DNGetOSType() == VER_PLATFORM_WIN32_NT)
	{
		g_hFileInstanceBlock = CreateFileMappingA(INVALID_HANDLE_VALUE,
											DNGetNullDacl(),
											PAGE_READWRITE,
											0,
											PERF_INSTANCE_BLOCK_SIZE,
											"Global\\" PERF_INSTANCE_BLOCK_NAME);
	}
	else
	{
		g_hFileInstanceBlock = CreateFileMappingA(INVALID_HANDLE_VALUE,
											DNGetNullDacl(),
											PAGE_READWRITE,
											0,
											PERF_INSTANCE_BLOCK_SIZE,
											PERF_INSTANCE_BLOCK_NAME);
	}
	if (g_hFileInstanceBlock == NULL)
	{
		hr = GetLastError();	    
		DPFX(DPFPREP,  0, "CreateFileMapping() failed hr=0x%x", hr);
        goto EXIT_ERROR;
	}
	
	if (GetLastError() == ERROR_ALREADY_EXISTS)
	{
	    fAlreadyExists = TRUE;
	}

	// Map file
	g_pbInstanceBlock = reinterpret_cast<BYTE*>(MapViewOfFile(g_hFileInstanceBlock,FILE_MAP_ALL_ACCESS,0,0,0));
	if (g_pbInstanceBlock == NULL)
	{
	    hr = GetLastError();
		DPFX(DPFPREP, 0,"MapViewOfFile() failed");
        goto EXIT_ERROR;
	}	

	g_pperfHeader = (PPERF_HEADER) g_pbInstanceBlock;

	g_plNumEntries = &g_pperfHeader->lNumEntries;
	g_pperfAppEntries = (PPERF_APPLICATION) &g_pperfHeader[1];

	// Access to entry count is protected by interlocked exchange
	if( !fAlreadyExists )
	{
        // Wait for the mutex to enter the block
        WaitForSingleObject( g_hMutexInstanceBlock, INFINITE );	    
        
        *g_plNumEntries = 0; 

        // Zero the memory in the block
        ZeroMemory( g_pbInstanceBlock, PERF_INSTANCE_BLOCK_SIZE );

        ReleaseMutex( g_hMutexInstanceBlock );
	}
	else
	{
        PERF_Coalesce();
	}

	return hr;

EXIT_ERROR:

    if( g_pbInstanceBlock )
    {
		UnmapViewOfFile(g_pbInstanceBlock);        
        g_pbInstanceBlock = NULL;
    }

    if( g_hFileInstanceBlock )
    {
        CloseHandle( g_hFileInstanceBlock );
        g_hFileInstanceBlock = NULL;
    }

    if( g_hMutexInstanceBlock )
    {
        CloseHandle( g_hMutexInstanceBlock );
        g_hMutexInstanceBlock = NULL;
    }

    return hr;

}

#undef DPF_MODNAME
#define DPF_MODNAME "PERF_CleanupEntry"
// PERF_CleanupEntry
//
// Cleanup global memory block for the entry
// 
void PERF_CleanupEntry( PPERF_APPLICATION pperfApplication, PPERF_APPLICATION_INFO pperfApplicationInfo  )
{
    if( pperfApplication->dwFlags & PERF_APPLICATION_VALID )
    {
        // This entry belongs to this process
        if( pperfApplicationInfo )
        {
            if( pperfApplicationInfo->pbMemoryBlock )
            {
                UnmapViewOfFile( pperfApplicationInfo->pbMemoryBlock );
            }

            if( pperfApplicationInfo->hFileMap )
            {
                CloseHandle( pperfApplicationInfo->hFileMap );
            }
        }

        ZeroMemory( pperfApplication, sizeof( PERF_APPLICATION ) );        

        (*g_plNumEntries)--;        
    }
    
}

#undef DPF_MODNAME
#define DPF_MODNAME "PERF_Coalesce"
// PERF_Coalesce
//
// Several routines run this function, the purpose is to run the list of processes 
// and remove dead processes from the list.
//
// WARNING: You must have the mutex when you enter this function
//
// Should be called when adding entries or reading entries.
//
// If called with a valid process ID then all entries for that process are removed
//  
void PERF_Coalesce( DWORD dwProcessID )
{
    if( !g_pbInstanceBlock )
        return;

    HANDLE hProcess;

    for( LONG lIndex = 0; lIndex < PERF_INSTANCE_BLOCK_MAXENTRIES; lIndex++ )
    {
		if( g_pperfAppEntries[lIndex].dwFlags & PERF_APPLICATION_VALID )
		{
			if( dwProcessID != 0xFFFFFFFF )
			{
				if( g_pperfAppEntries[lIndex].dwProcessID == dwProcessID )
					PERF_CleanupEntry( &g_pperfAppEntries[lIndex], NULL );
				break;
			}
			else
			{
				hProcess = OpenProcess( PROCESS_DUP_HANDLE, FALSE,  (DWORD) g_pperfAppEntries[lIndex].dwProcessID );

				// Check for process existance -- if it exists then leave entry, otherwise leave
    			if( !hProcess )
    				PERF_CleanupEntry( &g_pperfAppEntries[lIndex], NULL);     
    			else
    				CloseHandle( hProcess );   		    
			}
		}
    }
}

#undef DPF_MODNAME
#define DPF_MODNAME "PERF_AddEntry"
// PERF_AddEntry
//
// Requests the application be given a statistics block.  If succesful a memory block is allocated of the given size and 
// an entry is added to the central memory block.  The format of the datablock is application defined.
//
// Parameters for the entry to be created are specified in pperfApplication.  All entries in this structure should be 
// initialized except for dwProcessID which will be filled in by the system.
//  
// This function will fill the specified pperfApplicationInfo structure with information about the entry that was
// created.  This structure should be stored because it is needed in the call to PERF_RemoveEntry.
// 
HRESULT PERF_AddEntry( PPERF_APPLICATION pperfApplication, PPERF_APPLICATION_INFO pperfApplicationInfo )
{
    if( !g_pbInstanceBlock )
        return S_OK;

	// Zero the pperfApplicationInfo structture so in case of error it can be cleaned up properly
    ZeroMemory( pperfApplicationInfo, sizeof( PERF_APPLICATION_INFO ) );

    HRESULT hr = S_OK;

    // We need the mutex to access the block
    WaitForSingleObject( g_hMutexInstanceBlock, INFINITE );

    // Remove dead entries
    PERF_Coalesce();

    if( *g_plNumEntries >= PERF_INSTANCE_BLOCK_MAXENTRIES )
    {
        DPFX(DPFPREP,  0, "Instance block is full!" );
        hr = E_FAIL;
        ReleaseMutex( g_hMutexInstanceBlock );
        return hr;
    }

    for( LONG lIndex = 0; lIndex < PERF_INSTANCE_BLOCK_MAXENTRIES; lIndex++ )
    {
        // We've found a slot
        if( !(g_pperfAppEntries[lIndex].dwFlags & PERF_APPLICATION_VALID) )
        {
            DPFX(DPFPREP,  0, "Placing entry in index %i, pid=0x%x, flags=0x%x", lIndex, pperfApplication->dwProcessID, pperfApplication->dwFlags );
            memcpy( &g_pperfAppEntries[lIndex], pperfApplication, sizeof( PERF_APPLICATION ) );
            break;
        }
    }

    // Instance block is full -- wow. Shouldn't happen -- afterall we checked above.  
    if( lIndex == PERF_INSTANCE_BLOCK_MAXENTRIES )
    {
        // This should not happen, we have mutex AND checked entry count
        DNASSERT( FALSE );
        DPFX(DPFPREP,  0, "Unable to find an entry in the list" );
        hr = E_FAIL;
        ReleaseMutex( g_hMutexInstanceBlock );        
        return hr;
    }

    char szNameBuffer[PERF_INFO_NAME_LENGTH];

	// Build name for shared memory block
	if (DNGetOSType() == VER_PLATFORM_WIN32_NT)
	{
	    wsprintfA( szNameBuffer, "Global\\{%-08.8X-%-04.4X-%-04.4X-%02.2X%02.2X-%02.2X%02.2X%02.2X%02.2X%02.2X%02.2X}",
	    	     g_pperfAppEntries[lIndex].guidInternalInstance.Data1, g_pperfAppEntries[lIndex].guidInternalInstance.Data2, 
	    	     g_pperfAppEntries[lIndex].guidInternalInstance.Data3, g_pperfAppEntries[lIndex].guidInternalInstance.Data4[0], 
		         g_pperfAppEntries[lIndex].guidInternalInstance.Data4[1], g_pperfAppEntries[lIndex].guidInternalInstance.Data4[2], 
		         g_pperfAppEntries[lIndex].guidInternalInstance.Data4[3], g_pperfAppEntries[lIndex].guidInternalInstance.Data4[4], 
		         g_pperfAppEntries[lIndex].guidInternalInstance.Data4[5], g_pperfAppEntries[lIndex].guidInternalInstance.Data4[6], 
	             g_pperfAppEntries[lIndex].guidInternalInstance.Data4[7] );
	}
	else
	{
	    wsprintfA( szNameBuffer, "{%-08.8X-%-04.4X-%-04.4X-%02.2X%02.2X-%02.2X%02.2X%02.2X%02.2X%02.2X%02.2X}",
	    	     g_pperfAppEntries[lIndex].guidInternalInstance.Data1, g_pperfAppEntries[lIndex].guidInternalInstance.Data2, 
	    	     g_pperfAppEntries[lIndex].guidInternalInstance.Data3, g_pperfAppEntries[lIndex].guidInternalInstance.Data4[0], 
		         g_pperfAppEntries[lIndex].guidInternalInstance.Data4[1], g_pperfAppEntries[lIndex].guidInternalInstance.Data4[2], 
		         g_pperfAppEntries[lIndex].guidInternalInstance.Data4[3], g_pperfAppEntries[lIndex].guidInternalInstance.Data4[4], 
		         g_pperfAppEntries[lIndex].guidInternalInstance.Data4[5], g_pperfAppEntries[lIndex].guidInternalInstance.Data4[6], 
	             g_pperfAppEntries[lIndex].guidInternalInstance.Data4[7] );
	}

	// Create file mapping for memory block
	pperfApplicationInfo->hFileMap = CreateFileMappingA(INVALID_HANDLE_VALUE,
													DNGetNullDacl(),
													PAGE_READWRITE,
													0,
													pperfApplication->dwMemoryBlockSize,
													szNameBuffer);
	if (pperfApplicationInfo->hFileMap == NULL)
	{
		hr = GetLastError();	    
		DPFX(DPFPREP,  0, "CreateFileMapping() failed hr=0x%x", hr);
		goto EXIT_ERROR;
	}
    	
	// Create the shared memory block
    pperfApplicationInfo->pbMemoryBlock = reinterpret_cast<BYTE*>(MapViewOfFile(pperfApplicationInfo->hFileMap,FILE_MAP_ALL_ACCESS,0,0,0));
	
	if (pperfApplicationInfo->pbMemoryBlock == NULL)
	{
	    hr = GetLastError();
		DPFX(DPFPREP, 0,"MapViewOfFile() failed hr=0x%x", hr);
		goto EXIT_ERROR;
	}	

	strcat( szNameBuffer, "_M" );

	// Create mutex to protect the mutex.
	pperfApplicationInfo->hMutex = CreateMutexA( DNGetNullDacl(), FALSE, szNameBuffer );

	if( !pperfApplicationInfo->hMutex )
	{
		hr = GetLastError();
		DPFX(DPFPREP, 0,"MapViewOfFile() failed hr=0x%x", hr);
		goto EXIT_ERROR;
	}

	// Copy all the settings back to the app specified structure
	memcpy( pperfApplication, &g_pperfAppEntries[lIndex], sizeof( PERF_APPLICATION ) );

	(*g_plNumEntries)++;
	
    ReleaseMutex( g_hMutexInstanceBlock );

	return S_OK;
	
EXIT_ERROR:
	
	PERF_CleanupEntry( &g_pperfAppEntries[lIndex], pperfApplicationInfo );		
    ReleaseMutex( g_hMutexInstanceBlock );		
    return hr;			
}

#undef DPF_MODNAME
#define DPF_MODNAME "PERF_RemoveEntry"
// PERF_RemoveENtry
//
// When an entity no longer requires the statistics block they MUST call this function to free up the 
// resources and to allow their slot in the central memory block to be used by other applications.
//
void PERF_RemoveEntry( GUID &guidInternalInstance, PPERF_APPLICATION_INFO pperfApplicationInfo  )
{
    if( !g_pbInstanceBlock )
        return;

    HRESULT hr = S_OK;

    DPFX(DPFPREP,  DVF_INFOLEVEL, "PERF: Shutting down perf info" );

    // We need the mutex to access the block
    WaitForSingleObject( g_hMutexInstanceBlock, INFINITE );

    DPFX(DPFPREP,  DVF_INFOLEVEL, "PERF: Removing dead entries" );    

    // Remove dead entries
    PERF_Coalesce();
    
    for( LONG lIndex = 0; lIndex < PERF_INSTANCE_BLOCK_MAXENTRIES; lIndex++ )
    {
        // We've found the entry, mark it as empty
        if( g_pperfAppEntries[lIndex].guidInternalInstance == guidInternalInstance )
        {
		    DPFX(DPFPREP,  DVF_INFOLEVEL, "PERF: Removing our entry" );            	
            PERF_CleanupEntry( &g_pperfAppEntries[lIndex], pperfApplicationInfo );
			break;
        }
    }

    ReleaseMutex( g_hMutexInstanceBlock );

    DPFX(DPFPREP,  DVF_INFOLEVEL, "PERF: Exiting" );            	    
    
}

#undef DPF_MODNAME
#define DPF_MODNAME "PERF_DeInitialize"
// PERF_DeInitialize
//
// Free up the global memory map.  Must be called when process exits to cleanup
// 
void PERF_DeInitialize()
{
    if( g_hMutexInstanceBlock )
    {
        // We need the mutex to access the block
        WaitForSingleObject( g_hMutexInstanceBlock, INFINITE );
        PERF_Coalesce( GetCurrentProcessId() );
        ReleaseMutex( g_hMutexInstanceBlock );
       
        CloseHandle( g_hMutexInstanceBlock );
        g_hMutexInstanceBlock = NULL;
    }
        
    if( g_pbInstanceBlock )
    {
		UnmapViewOfFile(g_pbInstanceBlock);        
        g_pbInstanceBlock = NULL;
    }

    if( g_hFileInstanceBlock )
    {
        CloseHandle( g_hFileInstanceBlock );
        g_hFileInstanceBlock = NULL;
    }
}

#undef DPF_MODNAME
#define DPF_MODNAME "PERF_DumpTable"
// PERF_DumpTable
//
// Helper function that calls the specified callback function once for each entry in the central performance
// table. 
void PERF_DumpTable( BOOL fGrabMutex, PVOID pvContext, PFNDUMPPERFTABLE pperfAppEntry )
{
	BOOL fSelfInitialized = FALSE;
	HANDLE hMapInstance = NULL;
    char szNameBuffer[PERF_INFO_NAME_LENGTH];	
    PBYTE pbDataBlob = NULL;
	
	if( !g_pbInstanceBlock )
	{
		PERF_Initialize();
		fSelfInitialized = TRUE;
	}

    if( fGrabMutex )
		WaitForSingleObject( g_hMutexInstanceBlock, INFINITE );

    for( LONG lIndex = 0; lIndex < PERF_INSTANCE_BLOCK_MAXENTRIES; lIndex++ )
    {
		// Build name for shared memory block
		if (DNGetOSType() == VER_PLATFORM_WIN32_NT)
		{
		    wsprintfA( szNameBuffer, "Global\\{%-08.8X-%-04.4X-%-04.4X-%02.2X%02.2X-%02.2X%02.2X%02.2X%02.2X%02.2X%02.2X}",
		    	     g_pperfAppEntries[lIndex].guidInternalInstance.Data1, g_pperfAppEntries[lIndex].guidInternalInstance.Data2, 
		    	     g_pperfAppEntries[lIndex].guidInternalInstance.Data3, g_pperfAppEntries[lIndex].guidInternalInstance.Data4[0], 
			         g_pperfAppEntries[lIndex].guidInternalInstance.Data4[1], g_pperfAppEntries[lIndex].guidInternalInstance.Data4[2], 
			         g_pperfAppEntries[lIndex].guidInternalInstance.Data4[3], g_pperfAppEntries[lIndex].guidInternalInstance.Data4[4], 
			         g_pperfAppEntries[lIndex].guidInternalInstance.Data4[5], g_pperfAppEntries[lIndex].guidInternalInstance.Data4[6], 
		             g_pperfAppEntries[lIndex].guidInternalInstance.Data4[7] );
		}
		else
		{
		    wsprintfA( szNameBuffer, "{%-08.8X-%-04.4X-%-04.4X-%02.2X%02.2X-%02.2X%02.2X%02.2X%02.2X%02.2X%02.2X}",
		    	     g_pperfAppEntries[lIndex].guidInternalInstance.Data1, g_pperfAppEntries[lIndex].guidInternalInstance.Data2, 
		    	     g_pperfAppEntries[lIndex].guidInternalInstance.Data3, g_pperfAppEntries[lIndex].guidInternalInstance.Data4[0], 
			         g_pperfAppEntries[lIndex].guidInternalInstance.Data4[1], g_pperfAppEntries[lIndex].guidInternalInstance.Data4[2], 
			         g_pperfAppEntries[lIndex].guidInternalInstance.Data4[3], g_pperfAppEntries[lIndex].guidInternalInstance.Data4[4], 
			         g_pperfAppEntries[lIndex].guidInternalInstance.Data4[5], g_pperfAppEntries[lIndex].guidInternalInstance.Data4[6], 
		             g_pperfAppEntries[lIndex].guidInternalInstance.Data4[7] );
		}

		// Create file mapping for memory block
		hMapInstance = OpenFileMappingA(PAGE_READWRITE,FALSE,szNameBuffer);

		if( hMapInstance )
		{
		    pbDataBlob = reinterpret_cast<BYTE*>(MapViewOfFile(hMapInstance,FILE_MAP_READ,0,0,0));

			HRESULT hr = GetLastError();
		}
    	
        if( FAILED( (*pperfAppEntry)( pvContext, &g_pperfAppEntries[lIndex], pbDataBlob ) ) )
        	break;

        if( pbDataBlob )
        {
        	UnmapViewOfFile( pbDataBlob );
        	pbDataBlob = NULL;
        }

        if( hMapInstance )
        {
        	CloseHandle( hMapInstance );
        	hMapInstance = NULL;
        }

    }    

	if( fGrabMutex )
	    ReleaseMutex( g_hMutexInstanceBlock );
    

	if( fSelfInitialized )
		PERF_DeInitialize();
}