/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    physdisk.c

Abstract:

    This file implements a Performance Object that presents
    Physical Disk Performance object data

Created:

    Bob Watson  22-Oct-1996

Revision History


--*/
//
//  Include Files
//
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#pragma warning ( disable : 4201 ) 
#include <ntdddisk.h>
#include <windows.h>
#include <ole2.h>
#include <wmium.h>
#pragma warning ( default : 4201 )
#include <assert.h>
#include <winperf.h>
#include <ntprfctr.h>
#include <perfutil.h>
#include "perfdisk.h"
#if _DBG_PRINT_INSTANCES
#include <wtypes.h>
#include <stdio.h>
#include <stdlib.h>
#endif
#include "diskmsg.h"
#include "dataphys.h"

DWORD APIENTRY
CollectPDiskObjectData(
    IN OUT  LPVOID  *lppData,
    IN OUT  LPDWORD lpcbTotalBytes,
    IN OUT  LPDWORD lpNumObjectTypes
)
/*++

Routine Description:

    This routine will return the data for the logical disk 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

--*/
{
    PPDISK_DATA_DEFINITION      pPhysicalDiskDataDefinition;
    DWORD  TotalLen;            //  Length of the total return block
    PDISK_COUNTER_DATA          pcdTotal;

    DWORD   dwStatus    = ERROR_SUCCESS;
    PPERF_INSTANCE_DEFINITION   pPerfInstanceDefinition = NULL;

    PWNODE_ALL_DATA WmiDiskInfo;
    DISK_PERFORMANCE            *pDiskPerformance;    //  Disk driver returns counters here

    PWCHAR  wszWmiInstanceName;
    WCHAR   wszInstanceName[MAX_PATH]; // the numbers shouldn't ever get this big
    DWORD   dwInstanceNameOffset;

    DWORD   dwNumPhysicalDisks = 0;

    PPDISK_COUNTER_DATA         pPCD;

    BOOL    bMoreEntries;

    LONGLONG    llTemp;
    DWORD       dwTemp;

    DWORD   dwReturn = ERROR_SUCCESS;
    WORD    wNameLength;

    BOOL    bSkip;

    DWORD       dwCurrentWmiObjCount = 0;
    DWORD       dwRemapCount = 10;

    DOUBLE      dReadTime, dWriteTime, dTransferTime;

    //
    //  Check for sufficient space for Physical Disk object
    //  type definition
    //

    do {
        dwNumPhysicalDisks = 0;
        // make sure the drive letter map is up-to-date
        if (bRemapDriveLetters) {
            dwStatus = MapDriveLetters();
            // MapDriveLetters clears the remap flag when successful
            if (dwStatus != ERROR_SUCCESS) {
                *lpcbTotalBytes = (DWORD) 0;
                *lpNumObjectTypes = (DWORD) 0;
                return dwStatus;
            }
        }

        pPhysicalDiskDataDefinition = (PDISK_DATA_DEFINITION *) *lppData;

        // clear the accumulator structure

        memset (&pcdTotal, 0, sizeof(pcdTotal));
        //
        //  Define Logical Disk data block
        //

        TotalLen = sizeof (PDISK_DATA_DEFINITION);

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

        memmove(pPhysicalDiskDataDefinition,
               &PhysicalDiskDataDefinition,
               sizeof(PDISK_DATA_DEFINITION));

        // read the data from the diskperf driver

        if (dwStatus == ERROR_SUCCESS) {

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

            WmiDiskInfo = (PWNODE_ALL_DATA)WmiBuffer;

            // make sure the structure is valid
            if (WmiDiskInfo->WnodeHeader.BufferSize < sizeof(WNODE_ALL_DATA)) {
                bMoreEntries = FALSE;
                // just to make sure someone notices on a checked build
                assert (WmiDiskInfo->WnodeHeader.BufferSize >= sizeof(WNODE_ALL_DATA));
            } else {
                // make sure there are some entries to return
                bMoreEntries =
                    (WmiDiskInfo->InstanceCount > 0) ? TRUE : FALSE;
            }

            while (bMoreEntries) {

                pDiskPerformance = (PDISK_PERFORMANCE)(
                            (PUCHAR)WmiDiskInfo +  WmiDiskInfo->DataBlockOffset);
                dwInstanceNameOffset = *((LPDWORD)(
                            (LPBYTE)WmiDiskInfo +  WmiDiskInfo->OffsetInstanceNameOffsets));
                wNameLength = *(WORD *)((LPBYTE)WmiDiskInfo + dwInstanceNameOffset);
                if (wNameLength > 0) {
                    wszWmiInstanceName = (LPWSTR)((LPBYTE)WmiDiskInfo + dwInstanceNameOffset + sizeof(WORD));

                    if (IsPhysicalDrive(pDiskPerformance)) {
#if _DBG_PRINT_INSTANCES
                        WCHAR szOutputBuffer[512];
#endif  
                        // then the format is correct AND this is a physical
                        // partition so set the name string pointer and
                        // length for use in creating the instance.
                        memset (wszInstanceName, 0, sizeof(wszInstanceName));
                        GetPhysicalDriveNameString (
                            pDiskPerformance->StorageDeviceNumber,
                            pPhysDiskList,
                            dwNumPhysDiskListEntries,
                            wszInstanceName);
#if _DBG_PRINT_INSTANCES
                        swprintf (szOutputBuffer, (LPCWSTR)L"\nPERFDISK: [%d] PhysDrive [%8.8s,%d] is mapped as: ",
                            dwNumPhysDiskListEntries,
                            pDiskPerformance->StorageManagerName,
                            pDiskPerformance->StorageDeviceNumber);
                        OutputDebugStringW (szOutputBuffer);
                        OutputDebugStringW (wszInstanceName);
#endif  
                        bSkip = FALSE;
                    } else {
                        bSkip = TRUE;
                    }           
                
                    if (!bSkip) {
                        // first see if there's room for this entry....

                        TotalLen =
                            // space already used
                            (DWORD)((PCHAR) pPerfInstanceDefinition -
                            (PCHAR) pPhysicalDiskDataDefinition)
                            // + estimate of this instance
                            +   sizeof(PERF_INSTANCE_DEFINITION)
                            +   (lstrlenW(wszInstanceName) + 1) * sizeof(WCHAR) ;
                        TotalLen = QWORD_MULTIPLE (TotalLen);
                        TotalLen += sizeof(PDISK_COUNTER_DATA);
                        TotalLen = QWORD_MULTIPLE (TotalLen);

                        if ( *lpcbTotalBytes < TotalLen ) {
                            *lpcbTotalBytes = (DWORD) 0;
                            *lpNumObjectTypes = (DWORD) 0;
                            dwReturn = ERROR_MORE_DATA;
                            break;
                        }

                        MonBuildInstanceDefinition(
                            pPerfInstanceDefinition,
                            (PVOID *) &pPCD,
                            0, 0,   // no parent
                            (DWORD)-1,// no unique ID
                            wszInstanceName);

                        // clear counter data block
                        memset (pPCD, 0, sizeof(PDISK_COUNTER_DATA));
                        pPCD->CounterBlock.ByteLength = sizeof(PDISK_COUNTER_DATA);

//                      KdPrint (("PERFDISK: (P)   Entry %8.8x for: %ws\n", (DWORD)pPCD, wszWmiInstanceName));

                        // insure quadword alignment of the data structure
                        assert (((DWORD)(pPCD) & 0x00000007) == 0);

                        //  Set up pointer for data collection

                        // the QueueDepth counter is only a byte so clear the unused bytes
                        pDiskPerformance->QueueDepth &= 0x000000FF;

                        //
                        //  Format and collect Physical data
                        //
                        pcdTotal.DiskCurrentQueueLength += pDiskPerformance->QueueDepth;
                        pPCD->DiskCurrentQueueLength = pDiskPerformance->QueueDepth;

                        llTemp = pDiskPerformance->ReadTime.QuadPart +
                                 pDiskPerformance->WriteTime.QuadPart;

                        // these values are read in 100 NS units but are expected
                        // to be in sys perf freq (tick) units for the Sec/op ctrs 
                        // so convert them here

                        dReadTime = (DOUBLE)(pDiskPerformance->ReadTime.QuadPart);
                        dWriteTime = (DOUBLE)(pDiskPerformance->WriteTime.QuadPart);
                        dTransferTime = (DOUBLE)(llTemp);

                        dReadTime *= dSysTickTo100Ns;
                        dWriteTime *= dSysTickTo100Ns;
                        dTransferTime *= dSysTickTo100Ns;

                        pPCD->DiskTime = llTemp;
                        pPCD->DiskAvgQueueLength = llTemp;
                        pcdTotal.DiskAvgQueueLength += llTemp;
                        pcdTotal.DiskTime += llTemp;

                        pPCD->DiskReadTime = pDiskPerformance->ReadTime.QuadPart;
                        pPCD->DiskReadQueueLength = pDiskPerformance->ReadTime.QuadPart;
                        pcdTotal.DiskReadTime +=  pDiskPerformance->ReadTime.QuadPart;
                        pcdTotal.DiskReadQueueLength += pDiskPerformance->ReadTime.QuadPart;

                        pPCD->DiskWriteTime = pDiskPerformance->WriteTime.QuadPart;
                        pPCD->DiskWriteQueueLength = pDiskPerformance->WriteTime.QuadPart;

                        pcdTotal.DiskWriteTime += pDiskPerformance->WriteTime.QuadPart;
                        pcdTotal.DiskWriteQueueLength += pDiskPerformance->WriteTime.QuadPart;

                        pPCD->DiskAvgTime = (LONGLONG)dTransferTime;
                        pcdTotal.DiskAvgTime += (LONGLONG)dTransferTime;

                        dwTemp = pDiskPerformance->ReadCount +
                                 pDiskPerformance->WriteCount;

                        pcdTotal.DiskTransfersBase1 += dwTemp;
                        pPCD->DiskTransfersBase1 = dwTemp;

                        pcdTotal.DiskAvgReadTime += (LONGLONG)dReadTime;
                        pPCD->DiskAvgReadTime = (LONGLONG)dReadTime;
                        pcdTotal.DiskReadsBase1 += pDiskPerformance->ReadCount;
                        pPCD->DiskReadsBase1 = pDiskPerformance->ReadCount;

                        pcdTotal.DiskAvgWriteTime += (LONGLONG)dWriteTime;
                        pPCD->DiskAvgWriteTime = (LONGLONG)dWriteTime;
                        pcdTotal.DiskWritesBase1 += pDiskPerformance->WriteCount;
                        pPCD->DiskWritesBase1 = pDiskPerformance->WriteCount;

                        pcdTotal.DiskTransfers += dwTemp;
                        pPCD->DiskTransfers = dwTemp;

                        pcdTotal.DiskReads += pDiskPerformance->ReadCount;
                        pPCD->DiskReads = pDiskPerformance->ReadCount;
                        pcdTotal.DiskWrites += pDiskPerformance->WriteCount;
                        pPCD->DiskWrites = pDiskPerformance->WriteCount;

                        llTemp = pDiskPerformance->BytesRead.QuadPart +
                                 pDiskPerformance->BytesWritten.QuadPart;
                        pcdTotal.DiskBytes += llTemp;
                        pPCD->DiskBytes = llTemp;

                        pcdTotal.DiskReadBytes += pDiskPerformance->BytesRead.QuadPart;
                        pPCD->DiskReadBytes = pDiskPerformance->BytesRead.QuadPart;
                        pcdTotal.DiskWriteBytes += pDiskPerformance->BytesWritten.QuadPart;
                        pPCD->DiskWriteBytes = pDiskPerformance->BytesWritten.QuadPart;

                        pcdTotal.DiskAvgBytes += llTemp;
                        pPCD->DiskAvgBytes = llTemp;
                        pcdTotal.DiskTransfersBase2 += dwTemp;
                        pPCD->DiskTransfersBase2 = dwTemp;

                        pcdTotal.DiskAvgReadBytes += pDiskPerformance->BytesRead.QuadPart;
                        pPCD->DiskAvgReadBytes = pDiskPerformance->BytesRead.QuadPart;
                        pcdTotal.DiskReadsBase2 += pDiskPerformance->ReadCount;
                        pPCD->DiskReadsBase2 = pDiskPerformance->ReadCount;

                        pcdTotal.DiskAvgWriteBytes += pDiskPerformance->BytesWritten.QuadPart;
                        pPCD->DiskAvgWriteBytes = pDiskPerformance->BytesWritten.QuadPart;
                        pcdTotal.DiskWritesBase2 += pDiskPerformance->WriteCount;
                        pPCD->DiskWritesBase2 = pDiskPerformance->WriteCount;

                        pPCD->IdleTime = pDiskPerformance->IdleTime.QuadPart;
                        pcdTotal.IdleTime += pDiskPerformance->IdleTime.QuadPart;
                        pPCD->SplitCount = pDiskPerformance->SplitCount;
                        pcdTotal.SplitCount += pDiskPerformance->SplitCount;

                        pPCD->DiskTimeTimeStamp = pDiskPerformance->QueryTime.QuadPart;
                        pcdTotal.DiskTimeTimeStamp += pDiskPerformance->QueryTime.QuadPart;

                        // move to the end of the buffer for the next instance
                        pPerfInstanceDefinition = (PPERF_INSTANCE_DEFINITION)&pPCD[1];
                        dwNumPhysicalDisks++;

                    } else {
//                      KdPrint (("PERFDISK: (P) Skipping Instance: %ws\n", wszWmiInstanceName));
                    }
                    // count the number of items returned by WMI
                    dwCurrentWmiObjCount++;
                } else {
                    // the name has 0 length so skip
                }
               
                // bump pointers inside WMI data block
                if (WmiDiskInfo->WnodeHeader.Linkage != 0) {
                    // continue
                    WmiDiskInfo = (PWNODE_ALL_DATA) (
                        (LPBYTE)WmiDiskInfo + WmiDiskInfo->WnodeHeader.Linkage);
                } else {
                    // this is the end of the line
                    bMoreEntries = FALSE;
                }
            } // end for each volume
            // see if number of WMI objects returned is different from
            // the last time the instance table was built, if so then 
            // remap the letters and redo the instances
            if (dwCurrentWmiObjCount != dwWmiDriveCount) {
                DebugPrint((1, "CollectPDisk: Remap Current %d Drive %d\n",
                    dwCurrentWmiObjCount, dwWmiDriveCount));
                bRemapDriveLetters = TRUE;
                dwRemapCount--;
            }
        } // end if mem init was successful
    } while (bRemapDriveLetters && dwRemapCount);


    if ((dwNumPhysicalDisks > 0) && (dwStatus == ERROR_SUCCESS)) {
        // see if there's room for this entry....

        TotalLen =
            // space already used
            (DWORD)((PCHAR) pPerfInstanceDefinition -
                (PCHAR) pPhysicalDiskDataDefinition)
            // + estimate of this instance
            +   sizeof(PERF_INSTANCE_DEFINITION)
            +   (lstrlenW(wszTotal) + 1) * sizeof(WCHAR) ;
        TotalLen = QWORD_MULTIPLE (TotalLen);
        TotalLen += sizeof(PDISK_COUNTER_DATA);
        TotalLen = QWORD_MULTIPLE (TotalLen);

        if ( *lpcbTotalBytes < TotalLen ) {
            *lpcbTotalBytes = (DWORD) 0;
            *lpNumObjectTypes = (DWORD) 0;
            dwReturn = ERROR_MORE_DATA;
        } else {
            // normalize the total times
            pcdTotal.DiskTime /= dwNumPhysicalDisks;
            pcdTotal.DiskReadTime /= dwNumPhysicalDisks;
            pcdTotal.DiskWriteTime /= dwNumPhysicalDisks;
            pcdTotal.IdleTime /= dwNumPhysicalDisks;
            pcdTotal.DiskTimeTimeStamp /= dwNumPhysicalDisks;

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

            // update the total counters

            // insure quadword alignment of the data structure
            assert (((DWORD)(pPCD) & 0x00000007) == 0);
            memcpy (pPCD, &pcdTotal, sizeof (pcdTotal));
            pPCD->CounterBlock.ByteLength = sizeof(PDISK_COUNTER_DATA);

            // and update the "next byte" pointer
            pPerfInstanceDefinition = (PPERF_INSTANCE_DEFINITION)&pPCD[1];

            // update pointer to next available buffer...
            pPhysicalDiskDataDefinition->DiskObjectType.NumInstances =
                dwNumPhysicalDisks + 1; // add 1 for "Total" disk
        }
    } else {
        //  If we are diskless, then return no instances
        pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)
                                    &pPhysicalDiskDataDefinition[1];
        pPhysicalDiskDataDefinition->DiskObjectType.NumInstances = 0;
    }

    if (dwReturn == ERROR_SUCCESS) {
        *lpcbTotalBytes =
            pPhysicalDiskDataDefinition->DiskObjectType.TotalByteLength =
                (DWORD)((PCHAR) pPerfInstanceDefinition -
                (PCHAR) pPhysicalDiskDataDefinition);

#if DBG
        // sanity check on buffer size estimates
        if (*lpcbTotalBytes > TotalLen ) {
            DbgPrint ("\nPERFDISK: Physical Disk Perf Ctr. Instance Size Underestimated:");
            DbgPrint ("\nPERFDISK:   Estimated size: %d, Actual Size: %d", TotalLen, *lpcbTotalBytes);
        }
#endif

        *lppData = (LPVOID) pPerfInstanceDefinition;

        *lpNumObjectTypes = 1;
    }

    return dwReturn;
}
