Моя цель — получить информацию об использовании памяти для произвольного процесса. Я делаю следующее из моего 32-битного процесса:
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);
}
Проблема в том, что если pid
процесс — это 64-битный процесс, он может выделить более 4 ГБ памяти, что приведет к переполнению pmx.WorkingSetSize
а также pmx.PrivateUsage
, которые являются 32-битными переменными в 32-битном процессе. Так что в этом случае вместо того, чтобы потерпеть неудачу, GetProcessMemoryInfo
успешно с обеими метриками, возвращенными как UINT_MAX
— что не так!
Поэтому мне было интересно, существует ли надежный API для извлечения использования памяти из произвольного процесса в 32-разрядном приложении?
Есть надежный API под названием «Помощники по производительности».
Сток окон perfmon
Утилита является классическим примером приложения Windows Performance Counter. Также Process Explorer использует его для сбора статистики процесса.
Его преимущество в том, что вам даже не нужно SeDebugPrivilege
получить PROCESS_VM_READ
доступ к другим процессам.
Обратите внимание, что доступ ограничен пользователями, входящими в группу «Пользователи мониторинга производительности».
Идея, лежащая в основе PDH:
Начать нужно немного больше, но в конце все еще легко. Что я делаю, так это настраиваю постоянный запрос PDH, чтобы я мог повторно использовать его в течение всего времени жизни моего приложения.
Есть один недостаток: по умолчанию операционная система создает пронумерованные записи для процессов с одинаковыми именами. Эти пронумерованные записи даже меняются, когда процессы завершаются или создаются новые. Таким образом, вы должны учитывать это и перепроверить идентификатор процесса (PID), фактически имея дескриптор, открытый для процесса (ов), для которого вы хотите получить использование памяти.
Ниже вы найдете простую альтернативу обертке PDH GetProcessMemoryInfo()
,
Конечно, есть много места, чтобы настроить следующий код или настроить его в соответствии с вашими потребностями. Я также видел людей, которые уже создали более общие обертки C ++.
#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_;
};
#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;
}
Почему бы вам не скомпилировать это приложение как 64-битное, и тогда вы сможете собрать данные об использовании памяти как для 32-битных, так и для 64-битных процессов.
Поставщик WMI Win32_Process имеет несколько 64-битных номеров памяти. Не уверен, что все, что вы ищете, есть или нет.