/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    perfos.c

Abstract:


Author:

    Bob Watson (a-robw) Aug 95

Revision History:

--*/

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <winperf.h>
#include <ntprfctr.h>
#include <assert.h>
#include <perfutil.h>
#include "perfos.h"
#include "perfosmc.h"

// bit field definitions for collect function flags
#define POS_GET_SYS_PERF_INFO       ((DWORD)0x00010000)

#define POS_COLLECT_CACHE_DATA      ((DWORD)0x00010001)
#define POS_COLLECT_CPU_DATA        ((DWORD)0x00000002)
#define POS_COLLECT_MEMORY_DATA     ((DWORD)0x00010004)
#define POS_COLLECT_OBJECTS_DATA    ((DWORD)0x00000008)
#define POS_COLLECT_PAGEFILE_DATA   ((DWORD)0x00000010)
#define POS_COLLECT_SYSTEM_DATA     ((DWORD)0x00010020)

#define POS_COLLECT_FUNCTION_MASK   ((DWORD)0x0000003F)

#define POS_COLLECT_GLOBAL_DATA     ((DWORD)0x0001003F)
#define POS_COLLECT_FOREIGN_DATA    ((DWORD)0)
#define POS_COLLECT_COSTLY_DATA     ((DWORD)0)

// global variables to this DLL

HANDLE  ThisDLLHandle = NULL;
HANDLE  hEventLog     = NULL;
HANDLE  hLibHeap      = NULL;

SYSTEM_BASIC_INFORMATION BasicInfo;
SYSTEM_PERFORMANCE_INFORMATION  SysPerfInfo;

PM_OPEN_PROC    OpenOSObject;
PM_COLLECT_PROC CollectOSObjectData;
PM_CLOSE_PROC   CloseOSObject;

LPWSTR  wszTotal = NULL;

// variables local to this module

static POS_FUNCTION_INFO    posDataFuncInfo[] = {
    {CACHE_OBJECT_TITLE_INDEX,      POS_COLLECT_CACHE_DATA,     0, CollectCacheObjectData},
    {PROCESSOR_OBJECT_TITLE_INDEX,  POS_COLLECT_CPU_DATA,       0, CollectProcessorObjectData},
    {MEMORY_OBJECT_TITLE_INDEX,     POS_COLLECT_MEMORY_DATA,    0, CollectMemoryObjectData},
    {OBJECT_OBJECT_TITLE_INDEX,     POS_COLLECT_OBJECTS_DATA,   0, CollectObjectsObjectData},
    {PAGEFILE_OBJECT_TITLE_INDEX,   POS_COLLECT_PAGEFILE_DATA,  0, CollectPageFileObjectData},
    {SYSTEM_OBJECT_TITLE_INDEX,     POS_COLLECT_SYSTEM_DATA,    0, CollectSystemObjectData}
};

#define POS_NUM_FUNCS   (sizeof(posDataFuncInfo) / sizeof(posDataFuncInfo[1]))

static  bInitOk  = FALSE;
static  bReportedNotOpen = FALSE;

static
BOOL
DllProcessAttach (
    IN  HANDLE DllHandle
)
/*++

Description:

    perform any initialization function that apply to all object
    modules
   
--*/
{
    BOOL    bReturn = TRUE;
    LONG    status;
    WCHAR   wszTempBuffer[512];
    LONG    lStatus;
    DWORD   dwBufferSize;

    UNREFERENCED_PARAMETER (DllHandle);

    if (hLibHeap == NULL) {
        hLibHeap = HeapCreate (0, 1, 0);
    }

    assert (hLibHeap != NULL);

    if (hLibHeap == NULL) {
        return FALSE;
    }

    // open handle to the event log
    if (hEventLog == NULL) {
        hEventLog = MonOpenEventLog((LPWSTR)L"PerfOS");
        //
        //  collect basic and static processor data
        //

        status = NtQuerySystemInformation(
                     SystemBasicInformation,
                     &BasicInfo,
                     sizeof(SYSTEM_BASIC_INFORMATION),
                     NULL
                     );

        if (!NT_SUCCESS(status)) {
            BasicInfo.PageSize = 0;
            status = (LONG)RtlNtStatusToDosError(status);
            ReportEvent (hEventLog,
                EVENTLOG_ERROR_TYPE,
                0,
                PERFOS_UNABLE_QUERY_BASIC_INFO,
                NULL,
                0,
                sizeof(DWORD),
                NULL,
                (LPVOID)&status);

            bReturn = FALSE;
        }
    }

    lStatus = GetPerflibKeyValue (
        szTotalValue,
        REG_SZ,
        sizeof(wszTempBuffer),
        (LPVOID)&wszTempBuffer[0],
        DEFAULT_TOTAL_STRING_LEN,
        (LPVOID)&szDefaultTotalString[0]);

    if (lStatus == ERROR_SUCCESS) {
        // then a string was returned in the temp buffer
        dwBufferSize = lstrlenW (wszTempBuffer) + 1;
        dwBufferSize *= sizeof (WCHAR);
        wszTotal = ALLOCMEM (hLibHeap, HEAP_ZERO_MEMORY, dwBufferSize);
        if (wszTotal == NULL) {
            // unable to allocate buffer so use static buffer
            wszTotal = (LPWSTR)&szDefaultTotalString[0];
        } else {
            memcpy (wszTotal, wszTempBuffer, dwBufferSize);
        }
    } else {
        // unable to get string from registry so just use static buffer
        wszTotal = (LPWSTR)&szDefaultTotalString[0];
    }

    return bReturn;
}

static
BOOL
DllProcessDetach (
    IN  HANDLE DllHandle
)
{
    UNREFERENCED_PARAMETER (DllHandle);

    if ((dwCpuOpenCount + dwPageOpenCount + dwObjOpenCount) != 0) {
        // close the objects now sinc this is the last chance
        // as the DLL is in the process of being unloaded
        // if any of the open counters are > 1, then set them to 1 
        // to insure the object is closed on this call
        if (dwCpuOpenCount > 1) dwCpuOpenCount = 1;
        if (dwPageOpenCount > 1) dwPageOpenCount = 1;
        if (dwObjOpenCount > 1) dwObjOpenCount = 1;

        CloseOSObject();
    }

    assert ((dwCpuOpenCount + dwPageOpenCount + dwObjOpenCount) == 0);

    if ((wszTotal != NULL) && (wszTotal != &szDefaultTotalString[0])) {
        FREEMEM (hLibHeap, 0, wszTotal);
        wszTotal = NULL;
    }

    if (HeapDestroy (hLibHeap)) hLibHeap = NULL;

    if (hEventLog != NULL) {
        MonCloseEventLog ();
	hEventLog = NULL;	 
    }
    return TRUE;
}

BOOL
__stdcall
DllInit(
    IN HANDLE DLLHandle,
    IN DWORD  Reason,
    IN LPVOID ReservedAndUnused
)
{
    ReservedAndUnused;

    // this will prevent the DLL from getting
    // the DLL_THREAD_* messages
    DisableThreadLibraryCalls (DLLHandle);

    switch(Reason) {
        case DLL_PROCESS_ATTACH:
            return DllProcessAttach (DLLHandle);

        case DLL_PROCESS_DETACH:
            return DllProcessDetach (DLLHandle);

        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        default:
            return TRUE;
    }
}

DWORD APIENTRY
OpenOSObject (
    LPWSTR lpDeviceNames
    )
/*++

Routine Description:

    This routine will initialize the data structures used to pass
    data back to the registry

Arguments:

    Pointer to object ID of each device to be opened (PerfGen)

Return Value:

    None.

--*/
{
    DWORD   status;

    // cache object does not need to be opened

    // open Processor Object
    status = OpenProcessorObject (lpDeviceNames);

    // memory object does not need to be opened

    // open Objects object
    if (status == ERROR_SUCCESS) {
        status = OpenObjectsObject (lpDeviceNames);
        // open Pagefile object
        if (status == ERROR_SUCCESS) {
            status = OpenPageFileObject (lpDeviceNames);
            if (status != ERROR_SUCCESS) {
               // processor & Objects opened & page file did not
               // close the open objects
               CloseProcessorObject ();
               CloseObjectsObject();
            }
         } else {
            // processor Opend and Objects did not
            // close the open objects
            CloseProcessorObject();
         }
    } else {
        // nothing opened
    }

    // System Object does not need to be opened

    if (status == ERROR_SUCCESS) {
        bInitOk = TRUE;
    } else {
        ReportEvent (hEventLog,
            EVENTLOG_ERROR_TYPE,
            0,
            PERFOS_UNABLE_OPEN,
            NULL,
            0,
            sizeof(DWORD),
            NULL,
            (LPVOID)&status);
    }

    return  status;
}

DWORD APIENTRY
ReadOSObjectData (
    IN      DWORD   FunctionCallMask,
    IN OUT  LPVOID  *lppData,
    IN OUT  LPDWORD lpcbTotalBytes,
    IN OUT  LPDWORD lpNumObjectTypes
)
/*++

Routine Description:

    This routine will return the data for the OS object

Arguments:

   IN       DWORD FunctionCallMask
            bit mask of functions to call

   IN OUT   LPVOID   *lppData
         IN: pointer to the address of the buffer to receive the completed
            data structure. In the case of an item list, Global or Costly
            query, this will be a collection of one or more perf data objects.
            In the case of a PERF_QUERY_OBJECTS request, this will be an array
            of DWORDs listing the object ID's of the perf data objects
            supported by this DLL.

         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.

   IN OUT   LPDWORD  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

   IN OUT   LPDWORD  NumObjectTypes
         IN: the number of objects listed in the array of DWORDs referenced
            by the pObjList argument
            
         OUT: the number of objects returned by this routine is writted to the
            DWORD pointed to by this argument

   Returns:

             0 if successful, else Win 32 error code of failure

--*/
{
    NTSTATUS    Status;
    DWORD       lReturn = ERROR_SUCCESS;

    DWORD       FunctionIndex;

    DWORD       dwNumObjectsFromFunction;
    DWORD       dwOrigBuffSize;
    DWORD       dwByteSize;

    DWORD       dwReturnedBufferSize;

    // collect data 
    if (FunctionCallMask & POS_GET_SYS_PERF_INFO) {
        Status = NtQuerySystemInformation(
            SystemPerformanceInformation,
            &SysPerfInfo,
            sizeof(SysPerfInfo),
            &dwReturnedBufferSize
            );

        if (!NT_SUCCESS(Status)) {
            ReportEvent (hEventLog,
                EVENTLOG_ERROR_TYPE,
                0,
                PERFOS_UNABLE_QUERY_SYS_PERF_INFO,
                NULL,
                0,
                sizeof(DWORD),
                NULL,
                (LPVOID)&Status);
            memset (&SysPerfInfo, 0, sizeof(SysPerfInfo));
        }
    } else {
        memset (&SysPerfInfo, 0, sizeof(SysPerfInfo));
    }

    *lpNumObjectTypes = 0;
    dwOrigBuffSize = dwByteSize = *lpcbTotalBytes;
    *lpcbTotalBytes = 0;

    // remove query bits
    FunctionCallMask &= POS_COLLECT_FUNCTION_MASK;

    for (FunctionIndex = 0; FunctionIndex < POS_NUM_FUNCS; FunctionIndex++) {
        if (posDataFuncInfo[FunctionIndex].dwCollectFunctionBit &
            FunctionCallMask) {
            dwNumObjectsFromFunction = 0;

            // check for QUADWORD alignment of data buffer
            assert (((ULONG_PTR)(*lppData) & 0x00000007) == 0);

            lReturn = (*posDataFuncInfo[FunctionIndex].pCollectFunction) (
                lppData,
                &dwByteSize,
                &dwNumObjectsFromFunction);

            if (lReturn == ERROR_SUCCESS) {
                *lpNumObjectTypes += dwNumObjectsFromFunction;
                *lpcbTotalBytes += dwByteSize;
                dwOrigBuffSize -= dwByteSize;
                dwByteSize = dwOrigBuffSize;
            } else {
                break;
            }
        }
        // *lppData is updated by each function
        // *lpcbTotalBytes is updated after each successful function
        // *lpNumObjects is updated after each successful function
    }

    return lReturn;
}   

DWORD APIENTRY
QueryOSObjectData (
    IN      LPDWORD pObjList,
    IN OUT  LPVOID  *lppData,
    IN OUT  LPDWORD lpcbTotalBytes,
    IN OUT  LPDWORD lpNumObjectTypes
)
/*++

Routine Description:

    This routine will return the data for the processor object

Arguments:

   IN       LPDWORD *pObjList
            pointer to an array of Performance Objects that are
            to be returned to the caller. Each object is referenced by its
            DWORD value. If the first element in the array is one of the
            following then only the first item is read and the following
            data is returned:

                PERF_QUERY_OBJECTS   an array of object id's supported
                                by this function is returned in the data

                PERF_QUERY_GLOBAL    all perf objects supported by this
                                function are returned (Except COSTLY objects)

                PERF_QUERY_COSTLY    all COSTLY perf objects supported
                                by this function are returned

                Foreign objects are not supported by this API

   IN OUT   LPVOID   *lppData
         IN: pointer to the address of the buffer to receive the completed
            data structure. In the case of an item list, Global or Costly
            query, this will be a collection of one or more perf data objects.
            In the case of a PERF_QUERY_OBJECTS request, this will be an array
            of DWORDs listing the object ID's of the perf data objects
            supported by this DLL.

         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.

   IN OUT   LPDWORD  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

   IN OUT   LPDWORD  NumObjectTypes
         IN: the number of objects listed in the array of DWORDs referenced
            by the pObjList argument
            
         OUT: the number of objects returned by this routine is writted to the
            DWORD pointed to by this argument

   Returns:

             0 if successful, else Win 32 error code of failure

--*/
{
    LONG        lReturn = ERROR_SUCCESS;
    DWORD       FunctionCallMask = 0;
    DWORD       FunctionIndex;
    LPDWORD     pdwRetBuffer;

    DWORD       ObjectIndex;
    
    if (!bInitOk) {
        ReportEvent (hEventLog,
            EVENTLOG_ERROR_TYPE,
            0,
            PERFOS_NOT_OPEN,
            NULL,
            0,
            0,
            NULL,
            NULL);
        *lpcbTotalBytes = (DWORD) 0;
        *lpNumObjectTypes = (DWORD) 0;
        lReturn = ERROR_SUCCESS;
        goto QUERY_BAIL_OUT;
    }

    // evaluate the object list

    if (*lpNumObjectTypes == 1) {
        // then see if it's a predefined request value
        if (pObjList[0] == PERF_QUERY_GLOBAL) {
            FunctionCallMask = POS_COLLECT_GLOBAL_DATA;
        } else if (pObjList[0] == PERF_QUERY_COSTLY) {
            FunctionCallMask = POS_COLLECT_COSTLY_DATA;
        } else if (pObjList[0] == PERF_QUERY_OBJECTS) {
            if (*lpcbTotalBytes < (POS_NUM_FUNCS * sizeof(DWORD))) {
                lReturn = ERROR_MORE_DATA;
            } else {
                pdwRetBuffer = (LPDWORD)*lppData;
                for (FunctionIndex = 0; FunctionIndex < POS_NUM_FUNCS; FunctionIndex++) {
                    pdwRetBuffer[FunctionIndex] =
                        posDataFuncInfo[FunctionIndex].dwObjectId;
                }
                *lppData = &pdwRetBuffer[FunctionIndex];
                *lpcbTotalBytes = (POS_NUM_FUNCS * sizeof(DWORD));
                *lpNumObjectTypes = FunctionIndex;
                lReturn = ERROR_SUCCESS;
                goto QUERY_BAIL_OUT;
            }
        }
    }

    if (FunctionCallMask == 0) {
        // it's not a predfined value so run through the list
        // read the object list and build the call mask
        ObjectIndex = 0;
        while (ObjectIndex < *lpNumObjectTypes) {
            // search for this object in the list of object id's 
            // supported by this DLL
            for (FunctionIndex = 0; FunctionIndex < POS_NUM_FUNCS; FunctionIndex++) {
                if (pObjList[ObjectIndex] ==
                    posDataFuncInfo[FunctionIndex].dwObjectId) {
                    FunctionCallMask |=
                        posDataFuncInfo[FunctionIndex].dwCollectFunctionBit;
                    break; // out of inner loop
                }
            }
            ObjectIndex++;
        }
    }

    if (FunctionCallMask != 0) {
        lReturn = ReadOSObjectData (FunctionCallMask,
                                lppData,    
                                lpcbTotalBytes,
                                lpNumObjectTypes);
    } else {
        *lpcbTotalBytes = (DWORD) 0;
        *lpNumObjectTypes = (DWORD) 0;
        lReturn = ERROR_SUCCESS;
    }

QUERY_BAIL_OUT:
    return  lReturn;
}

DWORD APIENTRY
CollectOSObjectData (
    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 processor object

Arguments:

   IN       LPWSTR   lpValueName
            pointer to a wide character string passed by registry.

   IN OUT   LPVOID   *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.

   IN OUT   LPDWORD  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

   IN OUT   LPDWORD  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

   Returns:

             0 if successful, else Win 32 error code of failure

--*/
{
    LONG    lReturn = ERROR_SUCCESS;

    // build bit mask of functions to call

    DWORD       dwQueryType;
    DWORD       FunctionCallMask = 0;
    DWORD       FunctionIndex;

    if (!bInitOk) {
        if (!bReportedNotOpen) {
            bReportedNotOpen = ReportEvent (hEventLog,
                EVENTLOG_ERROR_TYPE,
                0,
                PERFOS_NOT_OPEN,
                NULL,
                0,
                0,
                NULL,
                NULL);
        }
        *lpcbTotalBytes = (DWORD) 0;
        *lpNumObjectTypes = (DWORD) 0;
        lReturn = ERROR_SUCCESS;
        goto COLLECT_BAIL_OUT;
    }

    dwQueryType = GetQueryType (lpValueName);

    switch (dwQueryType) {
        case QUERY_ITEMS:
            for (FunctionIndex = 0; FunctionIndex < POS_NUM_FUNCS; FunctionIndex++) {
                if (IsNumberInUnicodeList (
                    posDataFuncInfo[FunctionIndex].dwObjectId, lpValueName)) {
                    FunctionCallMask |=
                        posDataFuncInfo[FunctionIndex].dwCollectFunctionBit;
                }
            }
            break;

        case QUERY_GLOBAL:
            FunctionCallMask = POS_COLLECT_GLOBAL_DATA;
            break;

        case QUERY_FOREIGN:
            FunctionCallMask = POS_COLLECT_FOREIGN_DATA;
            break;

        case QUERY_COSTLY:
            FunctionCallMask = POS_COLLECT_COSTLY_DATA;
            break;

        default:
            FunctionCallMask = POS_COLLECT_COSTLY_DATA;
            break;
    }

    if (FunctionCallMask != 0) {
        lReturn = ReadOSObjectData (FunctionCallMask,
                                lppData,    
                                lpcbTotalBytes,
                                lpNumObjectTypes);
    } else {
        *lpcbTotalBytes = (DWORD) 0;
        *lpNumObjectTypes = (DWORD) 0;
        lReturn = ERROR_SUCCESS;
    }

COLLECT_BAIL_OUT:
    
    return lReturn;
}

DWORD APIENTRY
CloseOSObject (
)
/*++

Routine Description:

    This routine closes the open handles to the Signal Gen counters.

Arguments:

    None.


Return Value:

    ERROR_SUCCESS

--*/

{
    DWORD   status;
    DWORD   dwReturn = ERROR_SUCCESS;

    // cache object does not need to be closeed

    // close Processor Object
    status = CloseProcessorObject ();
    assert (status == ERROR_SUCCESS);
    if (status != ERROR_SUCCESS) dwReturn = status;

    // memory object does not need to be closeed

    // close Objects object
    status = CloseObjectsObject ();
    assert (status == ERROR_SUCCESS);
    if (status != ERROR_SUCCESS) dwReturn = status;

    // close Pagefile object
    status = ClosePageFileObject ();
    assert (status == ERROR_SUCCESS);
    if (status != ERROR_SUCCESS) dwReturn = status;

    // System Object does not need to be closeed

    return  dwReturn;

}
