c00000fd
c00000fd

Reputation: 22255

How to get a reliable memory usage information for a 64-bit process from a 32-bit process?

My goal is to get memory usage information for an arbitrary process. I do the following from my 32-bit process:

HANDLE hProc = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, 0, pid);
if(hProc)
{
    PROCESS_MEMORY_COUNTERS_EX pmx = {0};
    if(::GetProcessMemoryInfo(hProc, (PROCESS_MEMORY_COUNTERS*)&pmx, sizeof(pmx)))
    {
        wprintf(L"Working set: %.02f MB\n", pmx.WorkingSetSize / (1024.0 * 1024.0));
        wprintf(L"Private bytes: %.02f MB\n", pmx.PrivateUsage / (1024.0 * 1024.0));
    }

    ::CloseHandle(hProc);
}

The issue is that if the pid process is a 64-bit process, it may have allocated more than 4GB of memory, which will overflow both pmx.WorkingSetSize and pmx.PrivateUsage, which are both 32-bit variables in a 32-bit process. So in that case instead of failing, GetProcessMemoryInfo suceeds with both metrics returned as UINT_MAX -- which is wrong!

So I was wondering, if there was a reliable API to retrieve memory usage from an arbitrary process in a 32-bit application?

Upvotes: 0

Views: 1923

Answers (3)

klaus triendl
klaus triendl

Reputation: 1359

There's a reliable API called the "Performance Data Helpers".

Windows' stock perfmon utility is the classical example of a Windows Performance Counter application. Also Process Explorer is using it for collecting process statistics.

Its advantage is that you don't even need the SeDebugPrivilege to gain PROCESS_VM_READ access to other processes.
Note though that access is limited to users being part of the Performance Monitoring Users group.

The idea behind PDH is:

  • A Query object
    • One or multiple counters
  • Create samples upon request or periodically
  • Fetch the data you have asked for

It's a bit more work to get you started, but still easy in the end. What I do is setting up a permanent PDH query, such that I can reuse it throughout the lifetime of my application.

There's one drawback: By default, the Operating System creates numbered entries for processes having the same name. Those numbered entries even change while processes terminate or new ones get created. So you have to account for this and cross-check the process ID (PID), actually while having a handle open to the process(es) you want to obtain memory usage for.

Below you find a simple PDH wrapper alternative to GetProcessMemoryInfo(). Of course there's plenty of room to tweak the following code or adjust it to your needs. I have also seen people who created more generic C++ wrappers already.

Declaration

#include <tuple>
#include <array>
#include <vector>
#include <stdint.h>
#include <Pdh.h>

#pragma comment(lib, "Pdh.lib")


class process_memory_info
{
private:
    using pd_t = std::tuple<DWORD, ULONGLONG, ULONGLONG>; // performance data type
    static constexpr size_t pidIdx = 0;
    static constexpr size_t wsIdx = 1;
    static constexpr size_t pbIdx = 2;
    struct less_pd
    {
        bool operator ()(const pd_t& left, const pd_t& right) const
        {
            return std::get<pidIdx>(left) < std::get<pidIdx>(right);
        }
    };

public:
    ~process_memory_info();

    bool setup_query();
    bool take_sample();
    std::pair<uintmax_t, uintmax_t> get_memory_info(DWORD pid) const;

private:
    PDH_HQUERY pdhQuery_ = nullptr;
    std::array<PDH_HCOUNTER, std::tuple_size_v<pd_t>> pdhCounters_ = {};
    std::vector<pd_t> perfData_;
};

Implementation

#include <memory>
#include <execution>
#include <algorithm>
#include <stdlib.h>

using std::unique_ptr;
using std::pair;
using std::array;
using std::make_unique;
using std::get;


process_memory_info::~process_memory_info()
{
    PdhCloseQuery(pdhQuery_);
}

bool process_memory_info::setup_query()
{
    if (pdhQuery_)
        return true;
    if (PdhOpenQuery(nullptr, 0, &pdhQuery_))
        return false;

    size_t i = 0;
    for (auto& counterPath : array<PDH_COUNTER_PATH_ELEMENTS, std::tuple_size_v<pd_t>>{ {
        { nullptr, L"Process", L"*", nullptr, 0, L"ID Process" },
        { nullptr, L"Process", L"*", nullptr, 0, L"Working Set" },
        { nullptr, L"Process", L"*", nullptr, 0, L"Private Bytes" }
        }})
    {
        wchar_t pathStr[PDH_MAX_COUNTER_PATH] = {};

        DWORD size;
        PdhMakeCounterPath(&counterPath, pathStr, &(size = _countof(pathStr)), 0);
        PdhAddEnglishCounter(pdhQuery_, pathStr, 0, &pdhCounters_[i++]);
    }

    return true;
}

bool process_memory_info::take_sample()
{
    if (PdhCollectQueryData(pdhQuery_))
        return false;

    DWORD nItems = 0;
    DWORD size;
    PdhGetFormattedCounterArray(pdhCounters_[0], PDH_FMT_LONG, &(size = 0), &nItems, nullptr);
    auto valuesBuf = make_unique<BYTE[]>(size);
    PdhGetFormattedCounterArray(pdhCounters_[0], PDH_FMT_LONG, &size, &nItems, PPDH_FMT_COUNTERVALUE_ITEM(valuesBuf.get()));
    unique_ptr<PDH_FMT_COUNTERVALUE_ITEM[]> pidValues{ PPDH_FMT_COUNTERVALUE_ITEM(valuesBuf.release()) };

    valuesBuf = make_unique<BYTE[]>(size);
    PdhGetFormattedCounterArray(pdhCounters_[1], PDH_FMT_LARGE, &size, &nItems, PPDH_FMT_COUNTERVALUE_ITEM(valuesBuf.get()));
    unique_ptr<PDH_FMT_COUNTERVALUE_ITEM[]> wsValues{ PPDH_FMT_COUNTERVALUE_ITEM(valuesBuf.release()) };

    valuesBuf = make_unique<BYTE[]>(size);
    PdhGetFormattedCounterArray(pdhCounters_[2], PDH_FMT_LARGE, &size, &nItems, PPDH_FMT_COUNTERVALUE_ITEM(valuesBuf.get()));
    unique_ptr<PDH_FMT_COUNTERVALUE_ITEM[]> pbValues{ PPDH_FMT_COUNTERVALUE_ITEM(valuesBuf.release()) };

    perfData_.clear();
    perfData_.reserve(nItems);
    for (size_t i = 0, n = nItems; i < n; ++i)
    {
        perfData_.emplace_back(pidValues[i].FmtValue.longValue, wsValues[i].FmtValue.largeValue, pbValues[i].FmtValue.largeValue);
    }
    std::sort(std::execution::par_unseq, perfData_.begin(), perfData_.end(), less_pd{});

    return true;
}

pair<uintmax_t, uintmax_t> process_memory_info::get_memory_info(DWORD pid) const
{
    auto it = std::lower_bound(perfData_.cbegin(), perfData_.cend(), pd_t{ pid, 0, 0 }, less_pd{});

    if (it != perfData_.cend() && get<pidIdx>(*it) == pid)
        return { get<wsIdx>(*it), get<pbIdx>(*it) };
    else
        return {};
}


int main()
{
    process_memory_info pmi;
    pmi.setup_query();

    DWORD pid = 4;

    pmi.take_sample();
    auto[workingSet, privateBytes] = pmi.get_memory_info(pid);

    return 0;
}

Upvotes: 2

SoronelHaetir
SoronelHaetir

Reputation: 15164

The WMI Win32_Process provider has quite a few 64-bit memory numbers. Not sure if everything you are after is there or not.

Upvotes: 0

Amit Rastogi
Amit Rastogi

Reputation: 958

Why don't you compile this application as 64-bit and then you should be able to collect the memory usage for both 32-bit and 64-bit processes.

Upvotes: 0

Related Questions