/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    perfjob.c

Abstract:

    This file implements an Performance Job Object that presents
    information on the Job Object

Created:

    Bob Watson  8-Oct-1997

Revision History


--*/
//
//  Include Files
//
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <assert.h>
#include <winperf.h>
#include <ntprfctr.h>
#include <perfutil.h>
#include "perfsprc.h"
#include "perfmsg.h"
#include "procmsg.h"
#include "datajob.h"

#define MAX_STR_CHAR    1024
#define    MAX_STR_SIZE    ((DWORD)((MAX_STR_CHAR - 1)* sizeof(WCHAR)))
#define MAX_NAME_LENGTH    MAX_PATH

#define BUFFERSIZE 1024

DWORD    dwBufferSize = BUFFERSIZE;

const WCHAR szJob[] = L"Job";
const WCHAR szObjDirName[] = L"\\BaseNamedObjects";

#define MAX_EVENT_STRINGS    4
WORD    wEvtStringCount;
LPWSTR    szEvtStringArray[MAX_EVENT_STRINGS];

UNICODE_STRING DirectoryName = {(sizeof(szObjDirName) - sizeof(WCHAR)), // name len - NULL
                                sizeof(szObjDirName),                   // size of buffer    
                                (PWCHAR)szObjDirName};                   // address of buffer

BOOL    bOpenJobErrorLogged = FALSE;

PSYSTEM_PROCESS_INFORMATION APIENTRY
GetProcessPointerFromProcessId (
    IN    ULONG_PTR    dwPid
)
{
    PSYSTEM_PROCESS_INFORMATION ProcessInfo;
    ULONG ProcessBufferOffset = 0;
    BOOLEAN NullProcess;
    
    DWORD    dwIndex = 0;
    BOOL    bNotFound    = TRUE;
    BOOL    bMoreProcesses = FALSE;

    ProcessInfo = (PSYSTEM_PROCESS_INFORMATION) pProcessBuffer;
    if (ProcessInfo) {
        if (ProcessInfo->NextEntryOffset != 0) {
            bMoreProcesses = TRUE;
        }
    }
    while ( bMoreProcesses && bNotFound &&
            (ProcessInfo != NULL)) {
        // check for Live processes
        //  (i.e. name or threads)

        if ((ProcessInfo->ImageName.Buffer != NULL) ||
            (ProcessInfo->NumberOfThreads > 0)){
                // thread is not Dead
            NullProcess = FALSE;
        } else {
            // thread is dead
            NullProcess = TRUE;
        }

        if (( !NullProcess )  && (dwPid == (HandleToUlong(ProcessInfo->UniqueProcessId)))) {
            // found it so return current value
            bNotFound = FALSE;
            continue;
        } else {
            dwIndex++;
        }
        // exit if this was the last process in list
        if (ProcessInfo->NextEntryOffset == 0) {
            bMoreProcesses = FALSE;
            continue;
        }

        // point to next buffer in list
        ProcessBufferOffset += ProcessInfo->NextEntryOffset;
        ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)
                          &pProcessBuffer[ProcessBufferOffset];
    }

    if (bNotFound) {
        return NULL;
    } else {
        return ProcessInfo;
    }
}

DWORD APIENTRY
CollectJobObjectData (
    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 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

--*/
{
    DWORD   TotalLen;            //  Length of the total return block

    PPERF_INSTANCE_DEFINITION   pPerfInstanceDefinition;
    PJOB_DATA_DEFINITION        pJobDataDefinition;
    PJOB_COUNTER_DATA            pJCD;
    JOB_COUNTER_DATA            jcdTotal;

    NTSTATUS Status     = STATUS_SUCCESS;
    NTSTATUS tmpStatus  = STATUS_SUCCESS;
    HANDLE DirectoryHandle, JobHandle;
    ULONG ReturnedLength;
    POBJECT_DIRECTORY_INFORMATION DirInfo;
    POBJECT_NAME_INFORMATION NameInfo;
    OBJECT_ATTRIBUTES Attributes;
    WCHAR    wszNameBuffer[MAX_STR_CHAR];
    DWORD    dwSize;
    PUCHAR  Buffer;
    BOOL    bStatus;
    JOBOBJECT_BASIC_ACCOUNTING_INFORMATION JobAcctInfo;

    DWORD    dwWin32Status = ERROR_SUCCESS;
    ACCESS_MASK ExtraAccess = 0;
    ULONG Context = 0;
    DWORD    NumJobInstances = 0;

    // get size of a data block that has 1 instance
    TotalLen = sizeof(JOB_DATA_DEFINITION) +        // object def + counter defs
               sizeof (PERF_INSTANCE_DEFINITION) +    // 1 instance def
               MAX_VALUE_NAME_LENGTH +                // 1 instance name
               sizeof(JOB_COUNTER_DATA);            // 1 instance data block

    if ( *lpcbTotalBytes < TotalLen ) {
        *lpcbTotalBytes = 0;
        *lpNumObjectTypes = 0;
        return ERROR_MORE_DATA;
    }

    // cast callers buffer to the object data definition type
    pJobDataDefinition = (JOB_DATA_DEFINITION *) *lppData;

    //
    //  Define Job Object data block
    //

    memcpy(pJobDataDefinition,
           &JobDataDefinition,
           sizeof(JOB_DATA_DEFINITION));

    // set timestamp of this object
    pJobDataDefinition->JobObjectType.PerfTime = PerfTime;

    // Now collect data for each job object found in system
    //
    //  Perform initial setup
    //
    Buffer = ALLOCMEM(hLibHeap, HEAP_ZERO_MEMORY, dwBufferSize);
    if ((Buffer == NULL)) {
        ReportEvent (hEventLog,
            EVENTLOG_ERROR_TYPE,
            0,
            PERFPROC_UNABLE_ALLOCATE_JOB_DATA,
            NULL,
            0,
            0,
            NULL,
            NULL);
        *lpcbTotalBytes = 0;
        *lpNumObjectTypes = 0;
        return ERROR_SUCCESS;
    }

    pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)
                                  &pJobDataDefinition[1];

    // adjust TotalLen to be the size of the buffer already in use
    TotalLen = sizeof (JOB_DATA_DEFINITION);

    // zero the total instance buffer
    memset (&jcdTotal, 0, sizeof (jcdTotal));

    //
    //  Open the directory for list directory access
    //
    // this should always succeed since it's a system name we
    // will be querying
    //
    InitializeObjectAttributes( &Attributes,
                                &DirectoryName,
                                OBJ_CASE_INSENSITIVE,
                                NULL,
                                NULL );
    
    Status = NtOpenDirectoryObject( &DirectoryHandle,
                                    DIRECTORY_QUERY | ExtraAccess,
                                    &Attributes
                                  );
    if (NT_SUCCESS( Status )) {
        //
        // Get the actual name of the object directory object.
        //

        NameInfo = (POBJECT_NAME_INFORMATION) &Buffer[0];
        Status = NtQueryObject( DirectoryHandle,
                                 ObjectNameInformation,
                                 NameInfo,
                                 dwBufferSize,
                                 (PULONG) NULL );
    }

    if (NT_SUCCESS( Status )) {
        //
        //  Query the entire directory in one sweep
        //
        for (Status = NtQueryDirectoryObject( DirectoryHandle,
                                              Buffer,
                                              dwBufferSize,
                                              FALSE,
                                              FALSE,
                                              &Context,
                                              &ReturnedLength );
             NT_SUCCESS( Status );
             Status = NtQueryDirectoryObject( DirectoryHandle,
                                              Buffer,
                                              dwBufferSize,
                                              FALSE,
                                              FALSE,
                                              &Context,
                                              &ReturnedLength ) ) {

            //
            //  Check the status of the operation.
            //

            if (!NT_SUCCESS( Status )) {
                break;
            }

            //
            //  For every record in the buffer type out the directory information
            //

            //
            //  Point to the first record in the buffer, we are guaranteed to have
            //  one otherwise Status would have been No More Files
            //

            DirInfo = (POBJECT_DIRECTORY_INFORMATION) &Buffer[0];

            //
            //  Continue while there's a valid record.  
            //
            while (DirInfo->Name.Length != 0) {

                //
                //  Print out information about the Job
                //

                if (wcsncmp ( DirInfo->TypeName.Buffer, &szJob[0], ((sizeof(szJob)/sizeof(WCHAR)) - 1)) == 0) {
                    ULONG len;
                    UNICODE_STRING JobName;
                    NTSTATUS Status;

                    // this is really a job, so list the name
                    dwSize = DirInfo->Name.Length;
                    if (dwSize > (MAX_STR_SIZE - sizeof(szObjDirName))) {
                        dwSize = MAX_STR_SIZE - sizeof(szObjDirName);
                    }
                    len = wcslen(szObjDirName);
                    wcscpy(wszNameBuffer, szObjDirName);
                    wszNameBuffer[len] = L'\\';
                    len++;
                    memcpy (&wszNameBuffer[len], DirInfo->Name.Buffer, dwSize);
                    wszNameBuffer[dwSize/sizeof(WCHAR)+len] = 0;

                    // now query the process ID's for this job

                    RtlInitUnicodeString(&JobName, wszNameBuffer);
                    InitializeObjectAttributes(
                        &Attributes,
                        &JobName,
                        0,
                        NULL, NULL);
                    Status = NtOpenJobObject(
                                &JobHandle,
                                JOB_OBJECT_QUERY,
                                &Attributes);
                    if (NT_SUCCESS(Status)) {

                        // strip Job name prefix for instance name
                        memcpy (wszNameBuffer, DirInfo->Name.Buffer, dwSize);
                        wszNameBuffer[dwSize/sizeof(WCHAR)] = 0;

                        bStatus = QueryInformationJobObject (
                            JobHandle,
                            JobObjectBasicAccountingInformation,
                            &JobAcctInfo,
                            sizeof(JobAcctInfo),
                            &ReturnedLength);

                        ASSERT (ReturnedLength == sizeof(JobAcctInfo));

                        if (bStatus) {
                            // *** create and initialize perf data instance here ***

                            // see if this instance will fit
                            TotalLen += sizeof(PERF_INSTANCE_DEFINITION) +
                                       DWORD_MULTIPLE ((DirInfo->Name.Length + sizeof(WCHAR))) +
                                       sizeof (JOB_COUNTER_DATA);

                            if ( *lpcbTotalBytes < TotalLen ) {
                                *lpcbTotalBytes = 0;
                                *lpNumObjectTypes = 0;
                                Status = STATUS_NO_MEMORY;
                                dwWin32Status =  ERROR_MORE_DATA;
                                break;
                            }

                            MonBuildInstanceDefinition(pPerfInstanceDefinition,
                                (PVOID *) &pJCD,
                                0,
                                0,
                                (DWORD)-1,
                                wszNameBuffer);

                            // test structure for Quadword Alignment
                            assert (((DWORD)(pJCD) & 0x00000007) == 0);

                            //
                            //  Format and collect Process data
                            //

                            pJCD->CounterBlock.ByteLength = sizeof (JOB_COUNTER_DATA);

                            jcdTotal.CurrentProcessorTime +=                     
                                pJCD->CurrentProcessorTime =
                                    JobAcctInfo.TotalUserTime.QuadPart +
                                    JobAcctInfo.TotalKernelTime.QuadPart;

                            jcdTotal.CurrentUserTime +=
                                pJCD->CurrentUserTime = JobAcctInfo.TotalUserTime.QuadPart;
                            jcdTotal.CurrentKernelTime +=
                                pJCD->CurrentKernelTime = JobAcctInfo.TotalKernelTime.QuadPart;

#ifdef _DATAJOB_INCLUDE_TOTAL_COUNTERS
                            // convert these times from 100 ns Time base to 1 mS time base
                            jcdTotal.TotalProcessorTime +=
                                pJCD->TotalProcessorTime =
                                    (JobAcctInfo.ThisPeriodTotalUserTime.QuadPart +
                                    JobAcctInfo.ThisPeriodTotalKernelTime.QuadPart) / 10000;
                            jcdTotal.TotalUserTime +=
                                pJCD->TotalUserTime =
                                    JobAcctInfo.ThisPeriodTotalUserTime.QuadPart / 10000;
                            jcdTotal.TotalKernelTime +=
                                pJCD->TotalKernelTime =
                                    JobAcctInfo.ThisPeriodTotalKernelTime.QuadPart / 1000;
                            jcdTotal.CurrentProcessorUsage +=
                                pJCD->CurrentProcessorUsage =
                                    (JobAcctInfo.TotalUserTime.QuadPart +
                                     JobAcctInfo.TotalKernelTime.QuadPart) / 10000;

                            jcdTotal.CurrentUserUsage +=
                                pJCD->CurrentUserUsage =
                                    JobAcctInfo.TotalUserTime.QuadPart / 10000;

                            jcdTotal.CurrentKernelUsage +=
                                pJCD->CurrentKernelUsage =
                                    JobAcctInfo.TotalKernelTime.QuadPart / 10000;
#endif
                            jcdTotal.PageFaults +=
                                pJCD->PageFaults = JobAcctInfo.TotalPageFaultCount;
                            jcdTotal.TotalProcessCount +=
                                pJCD->TotalProcessCount = JobAcctInfo.TotalProcesses;
                            jcdTotal.ActiveProcessCount +=
                                pJCD->ActiveProcessCount = JobAcctInfo.ActiveProcesses;
                            jcdTotal.TerminatedProcessCount +=
                                pJCD->TerminatedProcessCount = JobAcctInfo.TotalTerminatedProcesses;

                            NumJobInstances++;

                            CloseHandle (JobHandle);

                            // set perfdata pointer to next byte
                            pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)&pJCD[1];
                        } else {
                            // unable to query job accounting info
                            dwWin32Status = GetLastError();
                            tmpStatus     = Status;
                            Status        = STATUS_SUCCESS;
                            if (bOpenJobErrorLogged == FALSE && MESSAGE_LEVEL >= LOG_VERBOSE) {
                                wEvtStringCount = 0;
                                szEvtStringArray[wEvtStringCount++] = wszNameBuffer;
                                // unable to open this Job
                                ReportEventW (hEventLog,
                                        EVENTLOG_WARNING_TYPE,
                                        0,
                                        PERFPROC_UNABLE_QUERY_JOB_ACCT,
                                        NULL,
                                        wEvtStringCount,
                                        sizeof(DWORD),
                                        szEvtStringArray,
                                        (LPVOID) & dwWin32Status);
                                bOpenJobErrorLogged = TRUE;
                            }
                        }
                    } else {
                        dwWin32Status = GetLastError();
                        tmpStatus     = Status;
                        Status        = STATUS_SUCCESS;
                        if (bOpenJobErrorLogged == FALSE && MESSAGE_LEVEL >= LOG_VERBOSE) {
                            wEvtStringCount = 0;
                            szEvtStringArray[wEvtStringCount++] = wszNameBuffer;
                            // unable to open this Job
                            ReportEventW (hEventLog,
                                    EVENTLOG_WARNING_TYPE,
                                    0,
                                    PERFPROC_UNABLE_OPEN_JOB,
                                    NULL,
                                    wEvtStringCount,
                                    sizeof(DWORD),
                                    szEvtStringArray,
                                    (LPVOID) & dwWin32Status);
                            bOpenJobErrorLogged = TRUE;
                        }
                    }
                }

                //
                //  There is another record so advance DirInfo to the next entry
                //

                DirInfo = (POBJECT_DIRECTORY_INFORMATION) (((PUCHAR) DirInfo) +
                              sizeof( OBJECT_DIRECTORY_INFORMATION ) );

            }

            RtlZeroMemory( Buffer, dwBufferSize );

        }

        if ((Status == STATUS_NO_MORE_FILES) ||
            (Status == STATUS_NO_MORE_ENTRIES)) {
            // this is OK
            Status = STATUS_SUCCESS;
        }

        if (Status == STATUS_SUCCESS && NumJobInstances == 0
                                     && bOpenJobErrorLogged == TRUE
                                     && dwWin32Status != ERROR_SUCCESS) {
            Status = tmpStatus;
        }

        if (Buffer) FREEMEM(hLibHeap, 0, Buffer);

        //
        //  Now close the directory object
        //

        (VOID) NtClose( DirectoryHandle );
    }

    if (NT_SUCCESS(Status)) {
        if (NumJobInstances > 0) {
            // see if the total instance will fit
            TotalLen += sizeof(PERF_INSTANCE_DEFINITION) +
                        (MAX_NAME_LENGTH+1+sizeof(DWORD))*
                          sizeof(WCHAR) +
                       sizeof (JOB_COUNTER_DATA);

            if ( *lpcbTotalBytes < TotalLen ) {
                *lpcbTotalBytes = 0;
                *lpNumObjectTypes = 0;
                return ERROR_MORE_DATA;
            }

            // it looks like it will fit so create "total" instance

            MonBuildInstanceDefinition(pPerfInstanceDefinition,
                (PVOID *) &pJCD,
                0,
                0,
                (DWORD)-1,
                wszTotal);

            // test structure for Quadword Alignment
            assert (((DWORD)(pJCD) & 0x00000007) == 0);

            //
            //  transfer total info
            //
            memcpy (pJCD, &jcdTotal, sizeof (jcdTotal));
            pJCD->CounterBlock.ByteLength = sizeof (JOB_COUNTER_DATA);

            pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)&pJCD[1];
            NumJobInstances++;
        }

        pJobDataDefinition->JobObjectType.NumInstances =
            NumJobInstances;
        //
        //  Now we know how large an area we used for the
        //  data, so we can update the offset
        //  to the next object definition
        //

        *lpcbTotalBytes =
            pJobDataDefinition->JobObjectType.TotalByteLength =
            (DWORD)((PCHAR) pPerfInstanceDefinition -
            (PCHAR) pJobDataDefinition);

#if DBG
        if (*lpcbTotalBytes > TotalLen ) {
            DbgPrint ("\nPERFPROC: Job Perf Ctr. Instance Size Underestimated:");
            DbgPrint ("\nPERFPROC:   Estimated size: %d, Actual Size: %d", TotalLen, *lpcbTotalBytes);
        }
#endif

        *lppData = (LPVOID) pPerfInstanceDefinition;

        *lpNumObjectTypes = 1;

    } else {
        *lpcbTotalBytes = 0;
        *lpNumObjectTypes = 0;
        if (bOpenJobErrorLogged == FALSE && MESSAGE_LEVEL >= LOG_VERBOSE) {
            wEvtStringCount = 0;
            szEvtStringArray[wEvtStringCount++] = DirectoryName.Buffer;
            // unable to query the object directory
            ReportEventW (hEventLog,
                    EVENTLOG_WARNING_TYPE,
                    0,
                    PERFPROC_UNABLE_QUERY_OBJECT_DIR,
                    NULL,
                    wEvtStringCount,
                    sizeof(DWORD),
                    szEvtStringArray,
                    (LPVOID)&Status);
            bOpenJobErrorLogged = TRUE;
        }
    }

    return ERROR_SUCCESS;
}

DWORD APIENTRY
CollectJobDetailData (
    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 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

--*/
{
    PSYSTEM_PROCESS_INFORMATION ProcessInfo;
    PUNICODE_STRING pProcessName;

    DWORD   TotalLen;            //  Length of the total return block

    PPERF_INSTANCE_DEFINITION   pPerfInstanceDefinition;
    PJOB_DETAILS_DATA_DEFINITION        pJobDetailsDataDefinition;
    PJOB_DETAILS_COUNTER_DATA            pJDCD;
    JOB_DETAILS_COUNTER_DATA            jdcdTotal;
    JOB_DETAILS_COUNTER_DATA            jdcdGrandTotal;


    NTSTATUS Status    = STATUS_SUCCESS;
    NTSTATUS tmpStatus = STATUS_SUCCESS;
    HANDLE DirectoryHandle, JobHandle;
    ULONG ReturnedLength;
    POBJECT_DIRECTORY_INFORMATION DirInfo;
    POBJECT_NAME_INFORMATION NameInfo;
    OBJECT_ATTRIBUTES Attributes;
    WCHAR    wszNameBuffer[MAX_STR_CHAR];
    DWORD    i, dwSize;
    PUCHAR  Buffer;
    BOOL    bStatus;
    PJOBOBJECT_BASIC_PROCESS_ID_LIST pJobPidList;

    DWORD    dwWin32Status = ERROR_SUCCESS;
    ACCESS_MASK ExtraAccess = 0;
    ULONG Context = 0;
    DWORD    NumJobObjects = 0;
    DWORD    NumJobDetailInstances = 0;

    // get size of a data block that has 1 instance
    TotalLen = sizeof(JOB_DETAILS_DATA_DEFINITION) +        // object def + counter defs
               sizeof (PERF_INSTANCE_DEFINITION) +    // 1 instance def
               MAX_VALUE_NAME_LENGTH +                // 1 instance name
               sizeof(JOB_DETAILS_COUNTER_DATA);            // 1 instance data block

    if ( *lpcbTotalBytes < TotalLen ) {
        *lpcbTotalBytes = 0;
        *lpNumObjectTypes = 0;
        return ERROR_MORE_DATA;
    }

    // cast callers buffer to the object data definition type
    pJobDetailsDataDefinition = (JOB_DETAILS_DATA_DEFINITION *) *lppData;

    //
    //  Define Job Details Object data block
    //

    memcpy(pJobDetailsDataDefinition,
           &JobDetailsDataDefinition,
           sizeof(JOB_DETAILS_DATA_DEFINITION));

    // set timestamp of this object
    pJobDetailsDataDefinition->JobDetailsObjectType.PerfTime = PerfTime;

    // Now collect data for each job object found in system
    //
    //  Perform initial setup
    //
    Buffer = NULL;
    pJobPidList = NULL;
    if (hLibHeap) {
        Buffer = ALLOCMEM(hLibHeap, HEAP_ZERO_MEMORY, dwBufferSize);
        pJobPidList = ALLOCMEM(hLibHeap, HEAP_ZERO_MEMORY, dwBufferSize);
    }
    if ((Buffer == NULL) || (pJobPidList == NULL)) {
        *lpcbTotalBytes = 0;
        *lpNumObjectTypes = 0;
        // free the one that got allocated (if any)
        if (Buffer != NULL) FREEMEM(hLibHeap, 0, Buffer);
        if (pJobPidList != NULL) FREEMEM(hLibHeap, 0, pJobPidList);
        ReportEventW(hEventLog,
            EVENTLOG_ERROR_TYPE,
            0,
            PERFPROC_UNABLE_ALLOCATE_JOB_DATA,
            NULL,
            0,
            0,
            szEvtStringArray,
            NULL);
        return ERROR_SUCCESS;
    }

    pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)
                                  &pJobDetailsDataDefinition[1];

    // adjust TotalLen to be the size of the buffer already in use
    TotalLen = sizeof (JOB_DETAILS_DATA_DEFINITION);

    // zero the total instance buffer
    memset (&jdcdGrandTotal, 0, sizeof (jdcdGrandTotal));

    //
    //  Open the directory for list directory access
    //
    // this should always succeed since it's a system name we
    // will be querying
    //
    InitializeObjectAttributes( &Attributes,
                                &DirectoryName,
                                OBJ_CASE_INSENSITIVE,
                                NULL,
                                NULL );
    
    Status = NtOpenDirectoryObject( &DirectoryHandle,
                                    DIRECTORY_QUERY | ExtraAccess,
                                    &Attributes
                                  );
    if (NT_SUCCESS( Status )) {
        //
        // Get the actual name of the object directory object.
        //

        NameInfo = (POBJECT_NAME_INFORMATION) &Buffer[0];
        Status = NtQueryObject( DirectoryHandle,
                                 ObjectNameInformation,
                                 NameInfo,
                                 dwBufferSize,
                                 (PULONG) NULL );
    }

    if (NT_SUCCESS( Status )) {
        //
        //  Query the entire directory in one sweep
        //
        for (Status = NtQueryDirectoryObject( DirectoryHandle,
                                              Buffer,
                                              dwBufferSize,
                                              FALSE,
                                              FALSE,
                                              &Context,
                                              &ReturnedLength );
             NT_SUCCESS( Status );
             Status = NtQueryDirectoryObject( DirectoryHandle,
                                              Buffer,
                                              dwBufferSize,
                                              FALSE,
                                              FALSE,
                                              &Context,
                                              &ReturnedLength ) ) {

            //
            //  Check the status of the operation.
            //

            if (!NT_SUCCESS( Status )) {
                break;
            }

            //
            //  For every record in the buffer type out the directory information
            //

            //
            //  Point to the first record in the buffer, we are guaranteed to have
            //  one otherwise Status would have been No More Files
            //

            DirInfo = (POBJECT_DIRECTORY_INFORMATION) &Buffer[0];

            //
            //  contine while there's a valid record
            //

            while (DirInfo->Name.Length != 0) {

                //
                //  Print out information about the Job
                //

                if (wcsncmp ( DirInfo->TypeName.Buffer, &szJob[0], ((sizeof(szJob)/sizeof(WCHAR)) - 1)) == 0) {
                    ULONG len;
                    UNICODE_STRING JobName;
                    NTSTATUS Status;

                    // this is really a job, so list the name
                    dwSize = DirInfo->Name.Length;
                    if (dwSize > (MAX_STR_SIZE - sizeof(szObjDirName))) {
                        dwSize = MAX_STR_SIZE - sizeof(szObjDirName);
                    }
                    len = wcslen(szObjDirName);
                    wcscpy(wszNameBuffer, szObjDirName);
                    wszNameBuffer[len] = L'\\';
                    len++;
                    memcpy (&wszNameBuffer[len], DirInfo->Name.Buffer, dwSize);
                    wszNameBuffer[dwSize/sizeof(WCHAR)+len] = 0;

                    // now query the process ID's for this job

                    RtlInitUnicodeString(&JobName, wszNameBuffer);
                    InitializeObjectAttributes(
                        &Attributes,
                        &JobName,
                        0,
                        NULL, NULL);
                    Status = NtOpenJobObject(
                                &JobHandle,
                                JOB_OBJECT_QUERY,
                                &Attributes);

                    // clear the Job total counter block
                    memset (&jdcdTotal, 0, sizeof (jdcdTotal));

                    if (NT_SUCCESS(Status)) {
                        // strip Job name prefix for instance name
                        memcpy (wszNameBuffer, DirInfo->Name.Buffer, dwSize);
                        wszNameBuffer[dwSize/sizeof(WCHAR)] = 0;

                        // now query the process ID's for this job

                        bStatus = QueryInformationJobObject (
                            JobHandle,
                            JobObjectBasicProcessIdList,
                            pJobPidList,
                            dwBufferSize,
                            &ReturnedLength);

//                        ASSERT (bStatus == TRUE);
                        ASSERT (ReturnedLength <= BUFFERSIZE);
                        ASSERT (pJobPidList->NumberOfAssignedProcesses ==
                            pJobPidList->NumberOfProcessIdsInList);

                        // test to see if there was enough room in the first buffer
                        // for everything, if not, expand the buffer and retry

                        if ((bStatus) && (pJobPidList->NumberOfAssignedProcesses >
                            pJobPidList->NumberOfProcessIdsInList))    {
                            PJOBOBJECT_BASIC_PROCESS_ID_LIST pOldBuffer;
                            dwBufferSize +=
                                (pJobPidList->NumberOfAssignedProcesses -
                                 pJobPidList->NumberOfProcessIdsInList) *
                                 sizeof (DWORD);
                            pOldBuffer = pJobPidList;
                            pJobPidList = REALLOCMEM (hLibHeap, 0,
                                pJobPidList, dwBufferSize);
//                            ASSERT (pJobPidList != NULL);
                            if (pJobPidList != NULL) {
                                bStatus = QueryInformationJobObject (
                                    JobHandle,
                                    JobObjectBasicProcessIdList,
                                    pJobPidList,
                                    dwBufferSize,
                                    &ReturnedLength);
                            } else {        
                                bStatus = FALSE;
                                FREEMEM(hLibHeap, 0, pOldBuffer);
                                SetLastError ( ERROR_OUTOFMEMORY );
                            }
                        }

                        if (bStatus) {

                            for (i=0;i < pJobPidList->NumberOfProcessIdsInList; i++) {
                                // *** create and initialize perf data instance here ***
                                // get process data object from ID
                                ProcessInfo = GetProcessPointerFromProcessId (pJobPidList->ProcessIdList[i]);
                                
//                                ASSERT (ProcessInfo != NULL);

                                if (ProcessInfo != NULL) {

                                    // get process name
                                    if (lProcessNameCollectionMethod == PNCM_MODULE_FILE) {
                                        pProcessName = GetProcessSlowName (ProcessInfo);
                                    } else {
                                       pProcessName = GetProcessShortName (ProcessInfo);
                                    }
                                    ReturnedLength = pProcessName->Length + sizeof(WCHAR);

                                    // see if this instance will fit
                                    TotalLen += sizeof(PERF_INSTANCE_DEFINITION) +
                                               DWORD_MULTIPLE (ReturnedLength) +
                                               sizeof (JOB_DETAILS_COUNTER_DATA);

                                    if ( *lpcbTotalBytes < TotalLen ) {
                                        *lpcbTotalBytes = 0;
                                        *lpNumObjectTypes = 0;
                                        Status = STATUS_NO_MEMORY;
                                        dwWin32Status =  ERROR_MORE_DATA;
                                        break;
                                    }

                                    MonBuildInstanceDefinition(pPerfInstanceDefinition,
                                        (PVOID *) &pJDCD,
                                        JOB_OBJECT_TITLE_INDEX,
                                        NumJobObjects,
                                        (DWORD)-1,
                                        pProcessName->Buffer);

                                    // test structure for Quadword Alignment
                                    assert (((DWORD)(pJDCD) & 0x00000007) == 0);

                                    //
                                    //  Format and collect Process data
                                    //

                                    pJDCD->CounterBlock.ByteLength = sizeof (JOB_DETAILS_COUNTER_DATA);
                                    //
                                    //  Convert User time from 100 nsec units to counter frequency.
                                    //
                                    jdcdTotal.ProcessorTime +=
                                        pJDCD->ProcessorTime = ProcessInfo->KernelTime.QuadPart +
                                                            ProcessInfo->UserTime.QuadPart;
                                    jdcdTotal.UserTime +=
                                        pJDCD->UserTime = ProcessInfo->UserTime.QuadPart;
                                    jdcdTotal.KernelTime +=
                                        pJDCD->KernelTime = ProcessInfo->KernelTime.QuadPart;

                                    jdcdTotal.PeakVirtualSize +=
                                        pJDCD->PeakVirtualSize = ProcessInfo->PeakVirtualSize;
                                    jdcdTotal.VirtualSize +=
                                        pJDCD->VirtualSize = ProcessInfo->VirtualSize;

                                    jdcdTotal.PageFaults +=
                                        pJDCD->PageFaults = ProcessInfo->PageFaultCount;
                                    jdcdTotal.PeakWorkingSet +=
                                        pJDCD->PeakWorkingSet = ProcessInfo->PeakWorkingSetSize;
                                    jdcdTotal.TotalWorkingSet +=
                                        pJDCD->TotalWorkingSet = ProcessInfo->WorkingSetSize;

#ifdef _DATAPROC_PRIVATE_WS_
                                    jdcdTotal.PrivateWorkingSet +=
                                        pJDCD->PrivateWorkingSet = ProcessInfo->PrivateWorkingSetSize;
                                    jdcdTotal.SharedWorkingSet +=
                                        pJDCD->SharedWorkingSet =
                                            ProcessInfo->WorkingSetSize -
                                            ProcessInfo->PrivateWorkingSetSize;
#endif
                                    jdcdTotal.PeakPageFile +=
                                        pJDCD->PeakPageFile = ProcessInfo->PeakPagefileUsage;
                                    jdcdTotal.PageFile +=
                                        pJDCD->PageFile = ProcessInfo->PagefileUsage;

                                    jdcdTotal.PrivatePages +=
                                        pJDCD->PrivatePages = ProcessInfo->PrivatePageCount;

                                    jdcdTotal.ThreadCount +=
                                        pJDCD->ThreadCount = ProcessInfo->NumberOfThreads;

                                    // base priority is not totaled
                                    pJDCD->BasePriority = ProcessInfo->BasePriority;

                                    // elpased time is not totaled
                                    pJDCD->ElapsedTime = ProcessInfo->CreateTime.QuadPart;

                                    pJDCD->ProcessId = HandleToUlong(ProcessInfo->UniqueProcessId);
                                    pJDCD->CreatorProcessId = HandleToUlong(ProcessInfo->InheritedFromUniqueProcessId);

                                    jdcdTotal.PagedPool +=
                                        pJDCD->PagedPool = (DWORD)ProcessInfo->QuotaPagedPoolUsage;
                                    jdcdTotal.NonPagedPool +=
                                        pJDCD->NonPagedPool = (DWORD)ProcessInfo->QuotaNonPagedPoolUsage;
                                    jdcdTotal.HandleCount +=
                                        pJDCD->HandleCount = (DWORD)ProcessInfo->HandleCount;

                                    // update I/O counters
                                    jdcdTotal.ReadOperationCount +=
                                        pJDCD->ReadOperationCount = ProcessInfo->ReadOperationCount.QuadPart;
                                    jdcdTotal.DataOperationCount += 
                                        pJDCD->DataOperationCount = ProcessInfo->ReadOperationCount.QuadPart;
                                    jdcdTotal.WriteOperationCount +=
                                        pJDCD->WriteOperationCount = ProcessInfo->WriteOperationCount.QuadPart;
                                    jdcdTotal.DataOperationCount += ProcessInfo->WriteOperationCount.QuadPart;
                                        pJDCD->DataOperationCount += ProcessInfo->WriteOperationCount.QuadPart;
                                    jdcdTotal.OtherOperationCount +=
                                        pJDCD->OtherOperationCount = ProcessInfo->OtherOperationCount.QuadPart;

                                    jdcdTotal.ReadTransferCount +=
                                        pJDCD->ReadTransferCount = ProcessInfo->ReadTransferCount.QuadPart;
                                    jdcdTotal.DataTransferCount +=
                                        pJDCD->DataTransferCount = ProcessInfo->ReadTransferCount.QuadPart;
                                    jdcdTotal.WriteTransferCount +=
                                        pJDCD->WriteTransferCount = ProcessInfo->WriteTransferCount.QuadPart;
                                    jdcdTotal.DataTransferCount += ProcessInfo->WriteTransferCount.QuadPart;
                                        pJDCD->DataTransferCount += ProcessInfo->WriteTransferCount.QuadPart;
                                    jdcdTotal.OtherTransferCount +=
                                        pJDCD->OtherTransferCount = ProcessInfo->OtherTransferCount.QuadPart;
                        
                                    // set perfdata pointer to next byte
                                    pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)&pJDCD[1];

                                    NumJobDetailInstances++;
                                } else {
                                    // unable to locate info on this process
                                    // for now, we'll ignore this...
                                }
                            }
                        
                            CloseHandle (JobHandle);

                            // see if this instance will fit
                            TotalLen += sizeof(PERF_INSTANCE_DEFINITION) +
                                       DWORD_MULTIPLE (MAX_STR_SIZE) +
                                       sizeof (JOB_DETAILS_COUNTER_DATA);

                            if ( *lpcbTotalBytes < TotalLen ) {
                                *lpcbTotalBytes = 0;
                                *lpNumObjectTypes = 0;
                                Status = STATUS_NO_MEMORY;
                                dwWin32Status =  ERROR_MORE_DATA;
                                break;
                            }

                            MonBuildInstanceDefinition(pPerfInstanceDefinition,
                                (PVOID *) &pJDCD,
                                JOB_OBJECT_TITLE_INDEX,
                                NumJobObjects,
                                (DWORD)-1,
                                wszTotal);

                            // test structure for Quadword Alignment
                            assert (((DWORD)(pJDCD) & 0x00000007) == 0);

                            // copy total data to caller's buffer

                            memcpy (pJDCD, &jdcdTotal, sizeof (jdcdTotal));
                            pJDCD->CounterBlock.ByteLength = sizeof (JOB_DETAILS_COUNTER_DATA);

                            // update grand total instance
                            //
                            jdcdGrandTotal.ProcessorTime += jdcdTotal.ProcessorTime;
                            jdcdGrandTotal.UserTime += jdcdTotal.UserTime;
                            jdcdGrandTotal.KernelTime += jdcdTotal. KernelTime;
                            jdcdGrandTotal.PeakVirtualSize += jdcdTotal.PeakVirtualSize;
                            jdcdGrandTotal.VirtualSize += jdcdTotal.VirtualSize;

                            jdcdGrandTotal.PageFaults += jdcdTotal.PageFaults;
                            jdcdGrandTotal.PeakWorkingSet += jdcdTotal.PeakWorkingSet;
                            jdcdGrandTotal.TotalWorkingSet += jdcdTotal.TotalWorkingSet;

#ifdef _DATAPROC_PRIVATE_WS_
                            jdcdGrandTotal.PrivateWorkingSet += jdcdTotal.PrivateWorkingSet;
                            jdcdGrandTotal.SharedWorkingSet += jdcdTotal.SharedWorkingSet;
#endif

                            jdcdGrandTotal.PeakPageFile += jdcdTotal.PeakPageFile;
                            jdcdGrandTotal.PageFile += jdcdTotal.PageFile;
                            jdcdGrandTotal.PrivatePages += jdcdTotal.PrivatePages;
                            jdcdGrandTotal.ThreadCount += jdcdTotal.ThreadCount;

                            jdcdGrandTotal.PagedPool += jdcdTotal.PagedPool;
                            jdcdGrandTotal.NonPagedPool += jdcdTotal.NonPagedPool;
                            jdcdGrandTotal.HandleCount += jdcdTotal.HandleCount;

                            // set perfdata pointer to next byte
                            pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)&pJDCD[1];

                            NumJobDetailInstances++;
                            NumJobObjects++;
                        } else {
                            // unable to read PID list from Job
                            dwWin32Status = GetLastError();
                            tmpStatus     = Status;
                            Status        = STATUS_SUCCESS;
                            if (bOpenJobErrorLogged == FALSE && MESSAGE_LEVEL >= LOG_VERBOSE) {
                                wEvtStringCount = 0;
                                szEvtStringArray[wEvtStringCount++] = wszNameBuffer;
                                // unable to open this Job
                                ReportEventW (hEventLog,
                                        EVENTLOG_WARNING_TYPE,
                                        0,
                                        PERFPROC_UNABLE_QUERY_JOB_PIDS,
                                        NULL,
                                        wEvtStringCount,
                                        sizeof(DWORD),
                                        szEvtStringArray,
                                        (LPVOID) & dwWin32Status);
                                bOpenJobErrorLogged = TRUE;
                            }
                        }
                    } else {
                        dwWin32Status = GetLastError();
                        tmpStatus     = Status;
                        Status        = STATUS_SUCCESS;
                        if (bOpenJobErrorLogged == FALSE && MESSAGE_LEVEL >= LOG_VERBOSE) {
                            wEvtStringCount = 0;
                            szEvtStringArray[wEvtStringCount++] = wszNameBuffer;
                            // unable to open this Job
                            ReportEventW (hEventLog,
                                    EVENTLOG_WARNING_TYPE,
                                    0,
                                    PERFPROC_UNABLE_OPEN_JOB,
                                    NULL,
                                    wEvtStringCount,
                                    sizeof(DWORD),
                                    szEvtStringArray,
                                    (LPVOID) & dwWin32Status);
                            bOpenJobErrorLogged = TRUE;
                        }
                    }
                }

                //
                //  There is another record so advance DirInfo to the next entry
                //

                DirInfo = (POBJECT_DIRECTORY_INFORMATION) (((PUCHAR) DirInfo) +
                              sizeof( OBJECT_DIRECTORY_INFORMATION ) );

            }

            RtlZeroMemory( Buffer, dwBufferSize );

        }

        if ((Status == STATUS_NO_MORE_FILES) ||
            (Status == STATUS_NO_MORE_ENTRIES)) {
            // this is OK
            Status = STATUS_SUCCESS;
        }

        if (Status == STATUS_SUCCESS && NumJobDetailInstances == 0
                                     && bOpenJobErrorLogged == TRUE
                                     && dwWin32Status != ERROR_SUCCESS) {
            Status = tmpStatus;
        }

        if (Buffer) FREEMEM(hLibHeap, 0, Buffer);
        if (pJobPidList) FREEMEM(hLibHeap, 0, pJobPidList);

        //
        //  Now close the directory object
        //

        (VOID) NtClose( DirectoryHandle );

        if (NumJobDetailInstances > 0) {
            // see if this instance will fit
            TotalLen += sizeof(PERF_INSTANCE_DEFINITION) +
                       DWORD_MULTIPLE (MAX_STR_SIZE) +
                       sizeof (JOB_DETAILS_COUNTER_DATA);

            if ( *lpcbTotalBytes < TotalLen ) {
                *lpcbTotalBytes = 0;
                *lpNumObjectTypes = 0;
                Status = STATUS_NO_MEMORY;
                dwWin32Status =  ERROR_MORE_DATA;
            } else {

                // set the Total Elapsed Time to be the current time so that it will
                // show up as 0 when displayed.
                jdcdGrandTotal.ElapsedTime = pJobDetailsDataDefinition->JobDetailsObjectType.PerfTime.QuadPart;

                // build the grand total instance
                MonBuildInstanceDefinition(pPerfInstanceDefinition,
                    (PVOID *) &pJDCD,
                    JOB_OBJECT_TITLE_INDEX,
                    NumJobObjects,
                    (DWORD)-1,
                    wszTotal);

                // test structure for Quadword Alignment
                ASSERT (((ULONG_PTR)(pJDCD) & 0x00000007) == 0);

                // copy total data to caller's buffer

                memcpy (pJDCD, &jdcdGrandTotal, sizeof (jdcdGrandTotal));
                pJDCD->CounterBlock.ByteLength = sizeof (JOB_DETAILS_COUNTER_DATA);

                // update pointers
                pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)&pJDCD[1];
                NumJobDetailInstances++;
            }
        }

        pJobDetailsDataDefinition->JobDetailsObjectType.NumInstances =
            NumJobDetailInstances;

        //
        //  Now we know how large an area we used for the
        //  Process definition, so we can update the offset
        //  to the next object definition
        //

        *lpcbTotalBytes =
             pJobDetailsDataDefinition->JobDetailsObjectType.TotalByteLength =
            (DWORD)((PCHAR) pPerfInstanceDefinition -
            (PCHAR) pJobDetailsDataDefinition);

#if DBG
        if (*lpcbTotalBytes > TotalLen ) {
            DbgPrint ("\nPERFPROC: Job Perf Ctr. Instance Size Underestimated:");
            DbgPrint ("\nPERFPROC:   Estimated size: %d, Actual Size: %d", TotalLen, *lpcbTotalBytes);
        }
#endif

        *lppData = (LPVOID) pPerfInstanceDefinition;

        *lpNumObjectTypes = 1;
    
    } else {
        *lpcbTotalBytes = 0;
        *lpNumObjectTypes = 0;
        if (bOpenJobErrorLogged == FALSE && MESSAGE_LEVEL >= LOG_VERBOSE) {
            wEvtStringCount = 0;
            szEvtStringArray[wEvtStringCount++] = DirectoryName.Buffer;
            // unable to query the object directory
            ReportEventW (hEventLog,
                    EVENTLOG_WARNING_TYPE,
                    0,
                    PERFPROC_UNABLE_QUERY_OBJECT_DIR,
                    NULL,
                    wEvtStringCount,
                    sizeof(DWORD),
                    szEvtStringArray,
                    (LPVOID)&Status);
            bOpenJobErrorLogged = TRUE;

        }
    }

    return ERROR_SUCCESS;
}
