/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    logidisk.c

Abstract:

    This file implements a Performance Object that presents
    Logical 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"
#include "diskmsg.h"
#include "datalogi.h"

DWORD APIENTRY
CollectLDiskObjectData(
    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

--*/
{
    PLDISK_DATA_DEFINITION      pLogicalDiskDataDefinition;
    DWORD  TotalLen;            //  Length of the total return block
    LDISK_COUNTER_DATA          lcdTotal;

    DWORD   dwStatus    = ERROR_SUCCESS;
    PPERF_INSTANCE_DEFINITION   pPerfInstanceDefinition;

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

    PWCHAR  wszInstanceName;
    DWORD   dwInstanceNameOffset;

    DWORD   dwNumLogicalDisks;

    WCHAR   wszTempName[MAX_PATH];
    WORD    wNameLength;
    WCHAR   wszDriveName[MAX_PATH];
    DWORD   dwDriveNameSize;

    PLDISK_COUNTER_DATA         pLCD;

    BOOL    bMoreEntries;

    DWORD   dwReturn = ERROR_SUCCESS;

    LONGLONG    llTemp;
    DWORD       dwTemp;
    PDRIVE_VOLUME_ENTRY pVolume;

    LONGLONG    TotalBytes;
    LONGLONG    FreeBytes;

    DWORD       dwCurrentWmiObjCount = 0;
    DWORD       dwRemapCount = 10;

    DOUBLE      dReadTime, dWriteTime, dTransferTime;

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

    do {
        dwNumLogicalDisks = 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;
            }
        }

        pLogicalDiskDataDefinition = (LDISK_DATA_DEFINITION *) *lppData;

        // clear the accumulator structure

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

        TotalLen = sizeof (LDISK_DATA_DEFINITION);

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

        memmove(pLogicalDiskDataDefinition,
               &LogicalDiskDataDefinition,
               sizeof(LDISK_DATA_DEFINITION));


        pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)
                                    &pLogicalDiskDataDefinition[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) {
            ULONG64 StorageManagerName[2];

            pDiskPerformance = (PDISK_PERFORMANCE)(
                        (PUCHAR)WmiDiskInfo +  WmiDiskInfo->DataBlockOffset);
            dwInstanceNameOffset = (sizeof(DISK_PERFORMANCE)+1) & ~1;
            wNameLength = *(WORD *)((LPBYTE)pDiskPerformance +
                                            dwInstanceNameOffset);
            if (wNameLength > 0) {
                wszInstanceName = (LPWSTR)((LPBYTE)pDiskPerformance +
                                           dwInstanceNameOffset + sizeof(WORD));

                // copy to local buffer for processing
                if (wNameLength >= MAX_PATH)
                    wNameLength = MAX_PATH-1; // truncate if necessary
                // copy text
                memcpy (wszTempName, wszInstanceName, wNameLength);
                // then null terminate
                wNameLength /= 2;
                wszTempName[wNameLength] = 0;
                memcpy(&StorageManagerName[0],
                    &pDiskPerformance->StorageManagerName[0],
                    2*sizeof(ULONG64));

                DebugPrint((4,
                    "PERFDISK: Logical Disk Instance: %ws\n", wszTempName));
                // see if this is a Physical Drive
                if (!IsPhysicalDrive(pDiskPerformance)) {
                    // it's not so get the name of it for this instance
                    dwDriveNameSize = sizeof (wszDriveName)
                                        / sizeof(wszDriveName[0]);
                    dwStatus = GetDriveNameString (
                        wszTempName, 
                        (DWORD)wNameLength,
                        pVolumeList,
                        dwNumVolumeListEntries,
                        wszDriveName,
                        &dwDriveNameSize,
                        (LPCWSTR) &StorageManagerName[0],
                        pDiskPerformance->StorageDeviceNumber,
                        &pVolume);
                    if (dwStatus != ERROR_SUCCESS) {
                        // just so we have a name
                        lstrcpyW (wszDriveName, wszTempName);
                        dwDriveNameSize = lstrlenW(wszDriveName);
                    }
                    DebugPrint((4, "\t loaded as %ws\n", wszDriveName));

                    TotalLen =
                        // space already used
                        (DWORD)((PCHAR) pPerfInstanceDefinition -
                            (PCHAR) pLogicalDiskDataDefinition)
                        // + estimate of this instance
                        +   sizeof(PERF_INSTANCE_DEFINITION)
                        +   (dwDriveNameSize + 1) * sizeof(WCHAR) ;
                    TotalLen = QWORD_MULTIPLE (TotalLen);
                    TotalLen += sizeof(LDISK_COUNTER_DATA);
                    TotalLen = QWORD_MULTIPLE (TotalLen);

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

                    MonBuildInstanceDefinition(
                        pPerfInstanceDefinition,
                        (PVOID *) &pLCD,
                        0, 0,   // no parent
                        (DWORD)-1,// no unique ID
                        &wszDriveName[0]);

                    // insure quadword alignment of the data structure
                    assert (((DWORD)(pLCD) & 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
                    //
                    lcdTotal.DiskCurrentQueueLength += pDiskPerformance->QueueDepth;
                    pLCD->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;

                    pLCD->DiskTime = llTemp;
                    pLCD->DiskAvgQueueLength = llTemp;
                    lcdTotal.DiskAvgQueueLength += llTemp;
                    lcdTotal.DiskTime += llTemp;

                    pLCD->DiskReadTime = pDiskPerformance->ReadTime.QuadPart;
                    pLCD->DiskReadQueueLength = pDiskPerformance->ReadTime.QuadPart;
                    lcdTotal.DiskReadTime +=  pDiskPerformance->ReadTime.QuadPart;
                    lcdTotal.DiskReadQueueLength += pDiskPerformance->ReadTime.QuadPart;

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

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

                    pLCD->DiskAvgTime = (LONGLONG)dTransferTime;
                    lcdTotal.DiskAvgTime += (LONGLONG)dTransferTime;

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

                    lcdTotal.DiskTransfersBase1 += dwTemp;
                    pLCD->DiskTransfersBase1 = dwTemp;

                    lcdTotal.DiskAvgReadTime += (LONGLONG)dReadTime;
                    pLCD->DiskAvgReadTime = (LONGLONG)dReadTime;
                    lcdTotal.DiskReadsBase1 += pDiskPerformance->ReadCount;
                    pLCD->DiskReadsBase1 = pDiskPerformance->ReadCount;

                    lcdTotal.DiskAvgWriteTime += (LONGLONG)dWriteTime;
                    pLCD->DiskAvgWriteTime = (LONGLONG)dWriteTime;
                    lcdTotal.DiskWritesBase1 += pDiskPerformance->WriteCount;
                    pLCD->DiskWritesBase1 = pDiskPerformance->WriteCount;

                    lcdTotal.DiskTransfers += dwTemp;
                    pLCD->DiskTransfers = dwTemp;

                    lcdTotal.DiskReads += pDiskPerformance->ReadCount;
                    pLCD->DiskReads = pDiskPerformance->ReadCount;
                    lcdTotal.DiskWrites += pDiskPerformance->WriteCount;
                    pLCD->DiskWrites = pDiskPerformance->WriteCount;

                    llTemp = pDiskPerformance->BytesRead.QuadPart +
                             pDiskPerformance->BytesWritten.QuadPart;
                    lcdTotal.DiskBytes += llTemp;
                    pLCD->DiskBytes = llTemp;

                    lcdTotal.DiskReadBytes += pDiskPerformance->BytesRead.QuadPart;
                    pLCD->DiskReadBytes = pDiskPerformance->BytesRead.QuadPart;
                    lcdTotal.DiskWriteBytes += pDiskPerformance->BytesWritten.QuadPart;
                    pLCD->DiskWriteBytes = pDiskPerformance->BytesWritten.QuadPart;

                    lcdTotal.DiskAvgBytes += llTemp;
                    pLCD->DiskAvgBytes = llTemp;
                    lcdTotal.DiskTransfersBase2 += dwTemp;
                    pLCD->DiskTransfersBase2 = dwTemp;

                    lcdTotal.DiskAvgReadBytes += pDiskPerformance->BytesRead.QuadPart;
                    pLCD->DiskAvgReadBytes = pDiskPerformance->BytesRead.QuadPart;
                    lcdTotal.DiskReadsBase2 += pDiskPerformance->ReadCount;
                    pLCD->DiskReadsBase2 = pDiskPerformance->ReadCount;

                    lcdTotal.DiskAvgWriteBytes += pDiskPerformance->BytesWritten.QuadPart;
                    pLCD->DiskAvgWriteBytes = pDiskPerformance->BytesWritten.QuadPart;
                    lcdTotal.DiskWritesBase2 += pDiskPerformance->WriteCount;
                    pLCD->DiskWritesBase2 = pDiskPerformance->WriteCount;

                    pLCD->IdleTime = pDiskPerformance->IdleTime.QuadPart;
                    lcdTotal.IdleTime += pDiskPerformance->IdleTime.QuadPart;
                    pLCD->SplitCount = pDiskPerformance->SplitCount;
                    lcdTotal.SplitCount += pDiskPerformance->SplitCount;

                    pLCD->DiskTimeTimestamp = pDiskPerformance->QueryTime.QuadPart;
                    lcdTotal.DiskTimeTimestamp += pDiskPerformance->QueryTime.QuadPart;

                    if (pVolume != NULL) {
                        TotalBytes = pVolume->TotalBytes;
                        FreeBytes = pVolume->FreeBytes;

                        //  First two yield percentage of free space;
                        //  last is for raw count of free space in megabytes

                        lcdTotal.DiskFreeMbytes1 +=
                            pLCD->DiskFreeMbytes1 = (DWORD)FreeBytes;

                        lcdTotal.DiskTotalMbytes +=
                            pLCD->DiskTotalMbytes = (DWORD)TotalBytes;
                        lcdTotal.DiskFreeMbytes2 +=
                            pLCD->DiskFreeMbytes2 = (DWORD)FreeBytes;
                    } else {
                        if (!NT_SUCCESS (dwStatus)) {
                            if (!bShownDiskVolumeMessage) {
                                bShownDiskVolumeMessage = ReportEvent (hEventLog,
                                    EVENTLOG_WARNING_TYPE,
                                    0,
                                    PERFDISK_UNABLE_QUERY_VOLUME_INFO,
                                    NULL,
                                    0,
                                    sizeof(DWORD),
                                    NULL,
                                    (LPVOID)&dwStatus);
                            }
                        }
                        // Cannot get space information
                        pLCD->DiskFreeMbytes1 = 0;
                        pLCD->DiskTotalMbytes = 0;
                        pLCD->DiskFreeMbytes2 = 0;
                    }

                    // bump pointers in Perf Data Block
                    dwNumLogicalDisks++;
                    pLCD->CounterBlock.ByteLength = sizeof (LDISK_COUNTER_DATA);
                    pPerfInstanceDefinition = (PPERF_INSTANCE_DEFINITION)&pLCD[1];

                } else {
                    // this is a physical drive entry so skip it
#if _DBG_PRINT_INSTANCES
                    OutputDebugStringW ((LPCWSTR)L" (skipped)");
#endif
                }
                // count the number of items returned
                dwCurrentWmiObjCount++;
            } else {
                // 0 length name string 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, "CollectLDisk: Remap Current %d Drive %d\n",
                dwCurrentWmiObjCount, dwWmiDriveCount));
            bRemapDriveLetters = TRUE;
            dwRemapCount--;
        }
    } while (bRemapDriveLetters && dwRemapCount);

    if (dwNumLogicalDisks > 0) {
        // see if there's room for the TOTAL entry....

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

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

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

            // update the total counters

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

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

            // update pointer to next available buffer...
            pLogicalDiskDataDefinition->DiskObjectType.NumInstances =
                dwNumLogicalDisks + 1; // add 1 for "Total" disk
        }
    } else {
        // there are  no instances so adjust the pointer for the 
        // rest of the code 
        pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)
                                    &pLogicalDiskDataDefinition[1];
    }

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

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

        *lppData = (LPVOID) pPerfInstanceDefinition;

        *lpNumObjectTypes = 1;

    }

    return dwReturn;
}

