/*++
*******************************************************************************
* Copyright (c) 1990-98 Digi International Inc., All Rights Reserved				*
* 																										*
* This software contains proprietary and confidential information of Digi		*
* International Inc.  By accepting transfer of this copy, Recipient agrees		*
* to retain this software in confidence, to prevent disclosure to others,		*
* and to make no use of this software other than that for which it was			*
* delivered.  This is an unpublished copyrighted work of Digi International		*
* Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly		*
* prohibited.																						*
* 																										*
* Restricted Rights Legend																		*
*																										*
* Use, duplication, or disclosure by the Government is subject to					*
* restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in			*
* Technical Data and Computer Software clause at DFARS 252.227-7031 or			*
* subparagraphs (c)(1) and (2) of the Commercial Computer Software -				*
* Restricted Rights at 48 CFR 52.227-19, as applicable.								*
*																										*
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343				*
*																										*
*******************************************************************************

Module Name:

   sohperf.c

Abstract:

   Perfmon extension dll support for dxb based dense adapters.

Author:

   Jeff Romatoski

--*/

#define SOH_INCLUDE_GLOBALS

#include <windows.h>
#include <winioctl.h>
#include <stdio.h>
#include <winperf.h>
#include "soherr.h" // error message definition
#include "perfmsg.h"
#include "perfutil.h"
#include "sohdata.h"
#include "sohapi.h"

// Define VERY_LOUD to debug printf a decode of the data presented to perfmon.  It
// prints out everytime Collect is called and is very expensive.  Use only for 
// debugging!
//#define VERY_LOUD 1

#ifndef DBG
#define DBG 0
#endif

#if DBG
#define STATIC
#define ASSERT(x) \
   do  \
   {   \
      if (!(x)) \
      {  \
         char buf[32+sizeof(#x)+sizeof(__FILE__)];  \
         sprintf(buf, "Assert("#x") failed at ("__FILE__":%d)\n", __LINE__); \
         OutputDebugString(buf); \
      } \
   } while (0)
#else
#define STATIC static 
#define ASSERT(x) {}
#endif


typedef struct DIGISOH_INSTANCE
{
	PERF_INSTANCE_DEFINITION StreamsInstance[SOH_STREAMS_COUNT];
	PERF_INSTANCE_DEFINITION DataBlocksInstance[SOH_DATABLOCKS_COUNT];
	HANDLE                   hDevice;	// DigiSOH device handle returned from createFile
} DIGISOH_INSTANCE, *PDIGISOH_INSTANCE;

DIGISOH_INSTANCE	gDigiSohInstance;
PWSTR				wcDeviceName		=  L"\\\\.\\DigiSOH";

DWORD gnOpens              = 0; 		// count of "Open" threads
BOOL  gInitialized         = FALSE;		// true = DLL initialized OK

//
// These proto's used to ensure function have correct calling format.
//
PM_OPEN_PROC    OpenSohPerformanceData;
PM_COLLECT_PROC CollectSohPerformanceData;
PM_CLOSE_PROC   CloseSohPerformanceData;


STATIC LONG
InitializeDigiSOH( 
	)
{
	LONG	Status	= ERROR_SUCCESS;
	int		i;

	gDigiSohInstance.hDevice = CreateFileW( wcDeviceName,
										   GENERIC_READ,
						  				   FILE_SHARE_WRITE,
										   NULL,
										   OPEN_EXISTING,
										   FILE_ATTRIBUTE_NORMAL,
										   NULL);

	if ( gDigiSohInstance.hDevice == INVALID_HANDLE_VALUE )
	{
		Status = GetLastError();

		// Debug Stuff
		ASSERT( Status != ERROR_SUCCESS );
		#if DBG
		  sprintf( DebugBuffer, "CreateFile failed\n");
		  OutputDebugString( DebugBuffer );
		#endif

		ReportEventW( hEventLog,
					  EVENTLOG_ERROR_TYPE,
					  0,
					  SOHPERF_CREATEFILE_ERROR,
					  NULL,
					  1,
					  0,
					  &wcDeviceName,
					  NULL );

		goto Exit;

	}

	// Initialize constant Data for the Streams Object Instances 
	for( i=0; i < SOH_STREAMS_COUNT ; i++ )
	{
		PPERF_INSTANCE_DEFINITION	pInstance;
		DWORD						NameLength;

		pInstance	= &gDigiSohInstance.StreamsInstance[i];

		// Include Trailing NULL in the Name Length
		NameLength = (lstrlenW(StreamsObjectNames[i]) + 1) * sizeof(WCHAR);

		pInstance->ByteLength				= sizeof(PERF_INSTANCE_DEFINITION) + 
											  DWORD_MULTIPLE(NameLength);
		pInstance->ParentObjectTitleIndex	= 0;
		pInstance->ParentObjectInstance		= 0;
		pInstance->UniqueID					= (DWORD) -1;
		pInstance->NameOffset				= sizeof(PERF_INSTANCE_DEFINITION);
		pInstance->NameLength				= NameLength;

	}
	// Initialize constant Data for the Data Block Object Instances 
	for( i=0; i < SOH_DATABLOCKS_COUNT ; i++ )
	{
		PPERF_INSTANCE_DEFINITION	pInstance;
		DWORD						NameLength;

		pInstance	= &gDigiSohInstance.DataBlocksInstance[i];

		// Include Trailing NULL in the Name Length
		NameLength = (lstrlenW(DataBlocksObjectNames[i]) + 1) * sizeof(WCHAR);

		pInstance->ByteLength				= sizeof(PERF_INSTANCE_DEFINITION) + 
											  DWORD_MULTIPLE(NameLength);
		pInstance->ParentObjectTitleIndex	= 0;
		pInstance->ParentObjectInstance		= 0;
		pInstance->UniqueID					= (DWORD) -1;
		pInstance->NameOffset				= sizeof(PERF_INSTANCE_DEFINITION);
		pInstance->NameLength				= NameLength;

	}

	Status = ERROR_SUCCESS;

Exit:

	return Status;
}	



DWORD APIENTRY
OpenSohPerformanceData(
	LPWSTR pDeviceNames )

/*++

Routine Description:

	- Open Dxb device objects.
	- Initialize data structures used to pass data back to perfmon.  

Arguments:

    List of devicenames Dxb adapter devices.

--*/
{

	HKEY	hKeyDriverPerf = NULL;
	LONG	Status;
	DWORD	size;
	DWORD	type;
	DWORD	dwFirstCounter;
	DWORD	dwFirstHelp;
	//
	//  Since SCREG is multi-threaded and will call this routine in
	//  order to service remote performance queries, this library
	//  must keep track of how many times it has been opened (i.e.
	//  how many threads have accessed it). the registry routines will
	//  limit access to the initialization routine to only one thread
	//  at a time so synchronization (i.e. reentrancy) should not be
	//  a problem
	//

	if ( gnOpens++ )
	{
   		Status = ERROR_SUCCESS;
		goto Exit;
	}

	// open Eventlog interface
	hEventLog = MonOpenEventLog();

	// get counter and help index base values from registry
	//      Open key to registry entry
	//      read First Counter and First Help values
	//      update static data strucutures by adding base to
	//          offset value in structure.

	Status = RegOpenKeyEx ( HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\DigiSOH\\Performance", 0L, KEY_ALL_ACCESS, &hKeyDriverPerf );
	if (Status != ERROR_SUCCESS)
	{
	  REPORT_ERROR_DATA (SOHPERF_PERFORMANCE_KEY_NOT_FOUND, LOG_USER, &Status, sizeof(Status));
	  goto Exit;
	}

	size = sizeof (DWORD);
	Status = RegQueryValueEx( hKeyDriverPerf, "First Counter", 0L, &type, (LPBYTE)&dwFirstCounter, &size);
	if (Status != ERROR_SUCCESS)
	{
	  REPORT_ERROR_DATA (SOHPERF_FIRST_COUNTER_NOT_FOUND, LOG_USER, &Status, sizeof(Status));
	  goto Exit;
	}

	size = sizeof (DWORD);
	Status = RegQueryValueEx( hKeyDriverPerf, "First Help", 0L, &type, (LPBYTE)&dwFirstHelp, &size);
	if (Status != ERROR_SUCCESS)
	{
	  REPORT_ERROR_DATA (SOHPERF_FIRST_HELP_NOT_FOUND, LOG_USER, &Status, sizeof(Status));
	  goto Exit;
	}

	//
	//  NOTE: the initialization program could also retrieve
	//      LastCounter and LastHelp if they wanted to do
	//      bounds checking on the new number. e.g.
	//
	//      counter->CounterNameTitleIndex += dwFirstCounter;
	//      if (counter->CounterNameTitleIndex > dwLastCounter) {
	//          LogErrorToEventLog (INDEX_OUT_OF_BOUNDS);
	//      }
	//

	// Set Streams object counters
	gStreamsDefinition.Type.ObjectNameTitleIndex += dwFirstCounter;
	gStreamsDefinition.Type.ObjectHelpTitleIndex += dwFirstHelp;

	{
		ULONG Count;
		PPERF_COUNTER_DEFINITION pCounterDef;

		pCounterDef = (PVOID)( (PCHAR)&gStreamsDefinition.Type + sizeof gStreamsDefinition.Type );
		for ( Count=0; Count < gStreamsDefinition.Type.NumCounters; Count++ )
		{
			pCounterDef[Count].CounterNameTitleIndex += dwFirstCounter;
			pCounterDef[Count].CounterHelpTitleIndex += dwFirstHelp;
		}
	}

	// Set Msg Block object counters
	gDataBlocksDefinition.Type.ObjectNameTitleIndex += dwFirstCounter;
	gDataBlocksDefinition.Type.ObjectHelpTitleIndex += dwFirstHelp;

	{
		ULONG Count;
		PPERF_COUNTER_DEFINITION pCounterDef;
		pCounterDef = (PPERF_COUNTER_DEFINITION)( (PBYTE)&gDataBlocksDefinition.Type+sizeof gDataBlocksDefinition.Type );
		for ( Count=0; Count < gDataBlocksDefinition.Type.NumCounters; Count++ )
		{
		 pCounterDef[Count].CounterNameTitleIndex += dwFirstCounter;
		 pCounterDef[Count].CounterHelpTitleIndex += dwFirstHelp;
		}
	}

	// Connect to the DigiSOH device
	Status = InitializeDigiSOH();
	if (Status == ERROR_SUCCESS)
	{
		gInitialized = TRUE; // ok to use this function
	}

Exit:

	if ( hKeyDriverPerf )
	{
		RegCloseKey( hKeyDriverPerf );
	}

	return Status;

}


STATIC DWORD
CollectStreamsData( 
	IN PVOID			pBuf,
	IN DWORD			cbBuf,
	IN PSOH_GET_STREAMS	pStreams,
	OUT PDWORD			pcbWritten,
	OUT PDWORD			pcbNeeded )
{
	PSOH_STREAMS_DEFINITION pStreamsDefinition = pBuf;
	DWORD	Status		= ERROR_MORE_DATA;
	ULONG	cbWritten	= 0;
	ULONG	cbNeeded	= 0;
	int		i;

	// Write Object header for the DigiSOH Streams Object Type
	{
		cbNeeded += sizeof gStreamsDefinition;
		if ( cbBuf < cbNeeded )
		{
			goto Exit;
		}
		*pStreamsDefinition = gStreamsDefinition;	// Note: structure copy!
		cbWritten += sizeof gStreamsDefinition;
	}


	// Write Adapter Object Instances

	for( i=0; i < SOH_STREAMS_COUNT ; i++ )
	{

		PERF_COUNTER_BLOCK	Counter			= {0};

		// Determine the Size needed for this instance
		cbNeeded += sizeof gDigiSohInstance.StreamsInstance[i] +
			        DWORD_MULTIPLE(gDigiSohInstance.StreamsInstance[i].NameLength) +
					sizeof Counter +
					sizeof(SOH_QUEUE_STATS);
		if ( cbBuf < cbNeeded )
		{
			goto Exit;
		}

		// Write constant Instance definition
		CopyMemory( (PCHAR)pBuf+cbWritten, &gDigiSohInstance.StreamsInstance[i], sizeof gDigiSohInstance.StreamsInstance[i] );
		cbWritten += sizeof gDigiSohInstance.StreamsInstance[i];

		// Write constant Instance name
		CopyMemory( (PCHAR)pBuf+cbWritten, StreamsObjectNames[i], gDigiSohInstance.StreamsInstance[i].NameLength );
		cbWritten += DWORD_MULTIPLE( gDigiSohInstance.StreamsInstance[i].NameLength );

		// Write Counter Block 
		Counter.ByteLength = sizeof Counter + sizeof(SOH_QUEUE_STATS);
		CopyMemory( (PCHAR)pBuf+cbWritten, &Counter, sizeof Counter );
		cbWritten += sizeof Counter;

		// Write Instance data
		CopyMemory( (PCHAR)pBuf+cbWritten, &pStreams->Stats[i], sizeof(SOH_QUEUE_STATS) );
		cbWritten += sizeof(SOH_QUEUE_STATS);

		pStreamsDefinition->Type.NumInstances++;
	}

	ASSERT( DWORD_MULTIPLE(cbWritten) == cbWritten );
	pStreamsDefinition->Type.TotalByteLength = cbWritten;
	Status = ERROR_SUCCESS;

Exit:

	*pcbWritten = cbWritten;
	*pcbNeeded	= cbNeeded;

	return Status;
}


STATIC DWORD
CollectDataBlocksData( 
	IN PVOID				pBuf,
	IN DWORD				cbBuf,
	IN PSOH_GET_DATABLOCKS	pDataBlocks,
	OUT PDWORD				pcbWritten,
	OUT PDWORD				pcbNeeded )
{
	PSOH_DATABLOCKS_DEFINITION pDataBlocksDefinition = pBuf;
	DWORD	Status		= ERROR_MORE_DATA;
	ULONG	cbWritten	= 0;
	ULONG	cbNeeded	= 0;
	int		i;

	// Write Object header for the DigiSOH Streams Object Type
	{
		cbNeeded += sizeof gDataBlocksDefinition;
		if ( cbBuf < cbNeeded )
		{
			goto Exit;
		}
		*pDataBlocksDefinition = gDataBlocksDefinition;	// Note: structure copy!
		cbWritten += sizeof gDataBlocksDefinition;
	}


	// Write Adapter Object Instances

	for( i=0; i < SOH_DATABLOCKS_COUNT ; i++ )
	{

		PERF_COUNTER_BLOCK	Counter			= {0};

		// Determine the Size needed for this instance
		cbNeeded += sizeof gDigiSohInstance.DataBlocksInstance[i] +
			        DWORD_MULTIPLE(gDigiSohInstance.DataBlocksInstance[i].NameLength) +
					sizeof Counter +
					sizeof(SOH_QUEUE_STATS);
		if ( cbBuf < cbNeeded )
		{
			goto Exit;
		}

		// Write constant Instance definition
		CopyMemory( (PCHAR)pBuf+cbWritten, &gDigiSohInstance.DataBlocksInstance[i], sizeof gDigiSohInstance.DataBlocksInstance[i] );
		cbWritten += sizeof gDigiSohInstance.StreamsInstance[i];

		// Write constant Instance name
		CopyMemory( (PCHAR)pBuf+cbWritten, DataBlocksObjectNames[i], gDigiSohInstance.DataBlocksInstance[i].NameLength );
		cbWritten += DWORD_MULTIPLE( gDigiSohInstance.DataBlocksInstance[i].NameLength );

		// Write Counter Block 
		Counter.ByteLength = sizeof Counter + sizeof(SOH_QUEUE_STATS);
		CopyMemory( (PCHAR)pBuf+cbWritten, &Counter, sizeof Counter );
		cbWritten += sizeof Counter;

		// Write Instance data
		CopyMemory( (PCHAR)pBuf+cbWritten, &pDataBlocks->Stats[i], sizeof(SOH_QUEUE_STATS) );
		cbWritten += sizeof(SOH_QUEUE_STATS);

		pDataBlocksDefinition->Type.NumInstances++;
	}

	ASSERT( DWORD_MULTIPLE(cbWritten) == cbWritten );
	pDataBlocksDefinition->Type.TotalByteLength = cbWritten;
	Status = ERROR_SUCCESS;

Exit:

	*pcbWritten = cbWritten;
	*pcbNeeded	= cbNeeded;

	return Status;
}


DWORD APIENTRY 
CollectSohPerformanceData(
   IN      LPWSTR  lpValueName,
   IN OUT  LPVOID  *lppData,
   IN OUT  LPDWORD lpcbTotalBytes,
   IN OUT  LPDWORD lpNumObjectTypes )
/*++

Routine Description:

    This routine will return the data for the DigiSOH counters.

Arguments:

   lpValueName
         pointer to a wide character string passed by registry.

   *lppData
         IN: pointer to the address of the buffer to receive the completed
            PerfDataBlock and subordinate structures. This routine will
            append its data to the buffer starting at the point referenced
            by *lppData.
         OUT: points to the first byte after the data structure added by this
            routine. This routine updated the value at lppdata after appending
            its data.

   lpcbTotalBytes
         IN: the address of the DWORD that tells the size in bytes of the
            buffer referenced by the lppData argument
         OUT: the number of bytes added by this routine is writted to the
            DWORD pointed to by this argument

   NumObjectTypes
         IN: the address of the DWORD to receive the number of objects added
            by this routine
         OUT: the number of objects added by this routine is writted to the
            DWORD pointed to by this argument

Return Value:

   ERROR_MORE_DATA if buffer passed is too small to hold data
      any error conditions encountered are reported to the event log if
      event logging is enabled.

   ERROR_SUCCESS  if success or any other error. Errors, however are
      also reported to the event log.

--*/
{
   //  Variables for reformating the data
	DWORD	Status			= ERROR_SUCCESS;
	LPVOID	lpData			= *lppData;
	ULONG	cbWrittenTotal	= 0;
	ULONG	cbNeededTotal	= 0;
	ULONG	cbRemaining		= *lpcbTotalBytes;
	PVOID	pBuf			= *lppData;
	DWORD	dwQueryType		= GetQueryType( lpValueName );

	*lpNumObjectTypes = 0;

	if ( !gInitialized )
	{
		goto Exit;
	}

	if ( dwQueryType == QUERY_FOREIGN )
	{
		goto Exit;
	}

	// Do We need to include the Streams Object counters?
	if ( dwQueryType == QUERY_GLOBAL || 
	     dwQueryType == QUERY_ITEMS && 
		IsNumberInUnicodeList( gStreamsDefinition.Type.ObjectNameTitleIndex, lpValueName) )

	{

		ULONG			cbWritten = 0;
		ULONG			cbNeeded  = 0;
		DWORD			dwBytes;
		BOOL			bSuccess;
		SOH_GET_STREAMS StreamsStats;

		// Get the data from the Driver
		bSuccess = DeviceIoControl( gDigiSohInstance.hDevice,
									IOCTL_SOH_GET_STREAMS, 
									NULL,
									0,
									&StreamsStats,
									sizeof(SOH_GET_STREAMS),
									&dwBytes,
									NULL );

		if ( !bSuccess )
		{
			Status = GetLastError();
			goto Exit;
		}

		// Package the data in the PERFMON structures
		Status = CollectStreamsData( pBuf,
									 cbRemaining,
									 &StreamsStats,
									 &cbWritten,
									 &cbNeeded );
		if ( cbWritten )
		{
			*lpNumObjectTypes += 1;
		}
		if ( Status != ERROR_SUCCESS || cbNeeded > cbWritten )
		{
			goto Exit;
		}

		// Update Counters on Success
		cbWrittenTotal += cbWritten;
		pBuf = (PCHAR)pBuf + cbWritten;
		cbRemaining -= cbWritten;
		cbNeededTotal += cbNeeded;
	}

	// Do We need to include the Data Block Object counters?
	if ( dwQueryType == QUERY_GLOBAL || 
	     dwQueryType == QUERY_ITEMS && IsNumberInUnicodeList( gDataBlocksDefinition.Type.ObjectNameTitleIndex, lpValueName ) )
	{
		ULONG				cbWritten = 0;
		ULONG				cbNeeded  = 0;
		DWORD				dwBytes;
		BOOL				bSuccess;
		SOH_GET_DATABLOCKS	DataBlocksStats;

		// Get the data from the Driver
		bSuccess = DeviceIoControl( gDigiSohInstance.hDevice,
									IOCTL_SOH_GET_DATABLOCKS, 
									NULL,
									0,
									&DataBlocksStats,
									sizeof(SOH_GET_DATABLOCKS),
									&dwBytes, NULL );

		if ( !bSuccess )
		{
			Status = GetLastError();
			goto Exit;
		}

		// Package the data in the PERFMON structures
		Status = CollectDataBlocksData( pBuf,
										cbRemaining,
										&DataBlocksStats,
										&cbWritten,
										&cbNeeded );
		if ( cbWritten )
		{
			*lpNumObjectTypes += 1;
		}
		if ( Status != ERROR_SUCCESS || cbNeeded > cbWritten )
		{
			goto Exit;
		}

		// Update Counters on Success
		cbWrittenTotal += cbWritten;
		pBuf = (PCHAR)pBuf + cbWritten;
		cbRemaining -= cbWritten;
		cbNeededTotal += cbNeeded;
	}


	ASSERT( (ULONG)pBuf - (ULONG)lpData  == cbWrittenTotal );
	lpData = pBuf;

Exit:

   *lpcbTotalBytes = (ULONG)(lpData) - (ULONG)(*lppData);
   *lppData = lpData;

   return Status;
}

DWORD APIENTRY
CloseSohPerformanceData()

/*++

Routine Description:

    This routine closes the open handles to FEP device performance counters

Arguments:

    None.


Return Value:

    ERROR_SUCCESS

--*/

{
   if (!(--gnOpens))
   { // when this is the last thread...
      MonCloseEventLog();
   }
   return ERROR_SUCCESS;

}

