FunkyMunky
FunkyMunky

Reputation: 31

Getting GPU usage statistics with PDH

I am trying to get some stats about the GPU. Mainly I would like to get the total VRAM and the amount in use, as well as the GPU utilization (would prefer the 3d core but will settle for total usage).

After messing around for a while I found that I could display all PDH counters using the following program.

#include <windows.h>
#include <iostream>
#include <pdh.h>
#include <pdhmsg.h>
#pragma comment(lib, "pdh.lib")

void browseCounters() {
    PDH_BROWSE_DLG_CONFIG dlgConfig = {0};
    dlgConfig.bIncludeInstanceIndex = FALSE;
    dlgConfig.bSingleCounterPerAdd = TRUE;
    dlgConfig.bSingleCounterPerDialog = TRUE;
    dlgConfig.bLocalCountersOnly = FALSE;
    dlgConfig.bWildCardInstances = TRUE;
    dlgConfig.bHideDetailBox = TRUE;
    dlgConfig.bInitializePath = FALSE;
    dlgConfig.bDisableMachineSelection = FALSE;
    dlgConfig.bIncludeCostlyObjects = FALSE;
    dlgConfig.bShowObjectBrowser = FALSE;
    dlgConfig.hWndOwner = NULL;
    dlgConfig.szReturnPathBuffer = new char[PDH_MAX_COUNTER_PATH];
    dlgConfig.cchReturnPathLength = PDH_MAX_COUNTER_PATH;
    dlgConfig.pCallBack = NULL;
    dlgConfig.dwCallBackArg = 0;
    dlgConfig.CallBackStatus = ERROR_SUCCESS;
    dlgConfig.dwDefaultDetailLevel = PERF_DETAIL_WIZARD;

    // Create a non-const copy of the string literal
    char *caption = new char[17];
    strcpy(caption, "Select a counter");
    dlgConfig.szDialogBoxCaption = caption;

    PDH_STATUS status = PdhBrowseCounters(&dlgConfig);

    if (status == ERROR_SUCCESS) {
        std::wcout << L"Selected Counter: " << dlgConfig.szReturnPathBuffer << std::endl;
    } else {
        std::cout << "Error: " << status << std::endl;
    }

    delete[] dlgConfig.szReturnPathBuffer;
}

int main() {
    browseCounters();
    system("pause");
    return 0;
}

After looking through the available counters I see a few that are exactly what I want. I see there is a GPU Engine tab. After selecting that and hitting ok I get the counter "\GPU Engine(*)\Utilization Percentage". Using this counter always returns 0 for utilization percentage. I have tried using different counters and putting my GPU name in the counter path but I can't get this to work right.

Here's the function I'm having trouble with. Main calls this function and the while loop prints the GPU utilization forever.

void printGPU() {
    PDH_HQUERY gpuQuery;
    PDH_HCOUNTER gpuCounter;

    // Open a query
    PdhOpenQueryW(NULL, 0, &gpuQuery);

    // Replace "Your_GPU_Instance_Name" with the actual name of your GPU instance
    wchar_t gpuCounterPath[MAX_PATH];
    swprintf_s(gpuCounterPath, L"\\GPU Engine(*)\\Utilization Percentage");

    PDH_STATUS pdhStatus = PdhAddCounterW(gpuQuery, gpuCounterPath, 0, &gpuCounter);
    if (pdhStatus != ERROR_SUCCESS) {
        std::cerr << "Error adding GPU counter: " << pdhStatus << std::endl;
        return;
    }

    while (true) {  // Adjust the loop condition based on your needs
        // Collect data
        PdhCollectQueryData(gpuQuery);
        // Wait for a short period (e.g., 1 second)
        Sleep(1000);
        // Collect data again
        PdhCollectQueryData(gpuQuery);

        // Read and update GPU usage
        PDH_FMT_COUNTERVALUE counterValue;
        PdhGetFormattedCounterValue(gpuCounter, PDH_FMT_DOUBLE, NULL, &counterValue);

        double gpuUsage = counterValue.doubleValue;

        std::cout << "GPU Utilization: " << gpuUsage << "%" << std::endl;
    }

    // Close the query
    PdhCloseQuery(gpuQuery);
}

I am doing the same thing with the CPU and for that it works correctly. I simply update the list of cores with their percent usage. This makes me suspect that the path is incorrect.

void GetCpuUsage() { // Function to get CPU usage percentage for each core

    PDH_HQUERY coresQuery;
    PDH_HCOUNTER counters[numCores];  // numcores is gotten earier in the code and is set to be the number of system cores
    PDH_HCOUNTER totalCounter;  // New counter for total CPU usage

    // Open a query
    PdhOpenQuery(NULL, 0, &coresQuery);


    // Add counters for each CPU core
    for (int i = 0; i < numCores; ++i) {
        wchar_t counterPath[MAX_PATH];
        swprintf_s(counterPath, L"\\Processor(%d)\\%% Processor Time", i);

        // Convert wide string to narrow string
        char narrowCounterPath[MAX_PATH];
        WideCharToMultiByte(CP_ACP, 0, counterPath, -1, narrowCounterPath, MAX_PATH, NULL, NULL);

        PdhAddCounter(coresQuery, narrowCounterPath, 0, &counters[i]);
    }


    // Add counter for total CPU usage
    wchar_t totalCounterPath[MAX_PATH];
    swprintf_s(totalCounterPath, L"\\Processor(_Total)\\%% Processor Time");

    // Convert wide string to narrow string
    char narrowTotalCounterPath[MAX_PATH];
    WideCharToMultiByte(CP_ACP, 0, totalCounterPath, -1, narrowTotalCounterPath, MAX_PATH, NULL, NULL);

    PdhAddCounter(coresQuery, narrowTotalCounterPath, 0, &totalCounter);



    while(pollCPUusage) { // loop forever while main program is open

        // Collect data
        PdhCollectQueryData(coresQuery);
        // Wait for a short period (e.g., 1 second)
        Sleep(500);
        // Collect data again
        PdhCollectQueryData(coresQuery);

        // critical section
        {
            // lock mutex
            std::lock_guard<std::mutex> lock(coresMutex);

            // Read and update CPU usage for each core
            for (int i = 0; i < numCores; ++i) {
                PDH_FMT_COUNTERVALUE counterValue;
                PdhGetFormattedCounterValue(counters[i], PDH_FMT_DOUBLE, NULL, &counterValue);

                cores[i] = counterValue.doubleValue;

                //std::cout << "CPU Core " << i << " Usage: " << counterValue.doubleValue << "%" << std::endl;
            }


            // Read and update total CPU usage
            PDH_FMT_COUNTERVALUE totalCounterValue;
            PdhGetFormattedCounterValue(totalCounter, PDH_FMT_DOUBLE, NULL, &totalCounterValue);
            totalPctUsage = totalCounterValue.doubleValue;

        } // end of critical section mutex auto unlocks at this point

    }

    // Close the query
    PdhCloseQuery(coresQuery);
}

Any help is greatly appreciated.

Upvotes: 1

Views: 722

Answers (1)

SarthakShah
SarthakShah

Reputation: 61

You GPU counter has a wildcard (*) in the instance name. So you need to use the PdhGetFormattedCounterArray function instead of PdhGetFormattedCounterValue (https://learn.microsoft.com/en-us/windows/win32/api/pdh/nf-pdh-pdhgetformattedcounterarrayw). To calculate total GPU utilization, loop through all the entries in the array returned and sum them up.

Upvotes: 1

Related Questions