Как узнать RAM Usage (использование оперативки) какого-нибудь процесса в .NET?
Запускается процесс с помощью класса ProcessStartInfo и Process. Есть ли в си шарпе какие-то функции чтобы узнать это? Или АПИ? Если знаете подскажите как.
Благодарю.
Вот члены класса Process и смотрите. Автокомплит вам в помощь. ;)
Там этих мемори юзаджей у Процесс так много. Может кто-то точно сказать как узнать сколько памяти использует экзешник?
ReturnValues[2] = (p.PeakVirtualMemorySize64 + p.PeakWorkingSet64).ToString();
Пробую так, результат всегда разный. И не только так пробовал...
Там этих мемори юзаджей у Процесс так много. Может кто-то точно сказать как узнать сколько памяти использует экзешник?
ReturnValues[2] = (p.PeakVirtualMemorySize64 + p.PeakWorkingSet64).ToString();
Пробую так, результат всегда разный. И не только так пробовал...
Проситите, но вы документацию-то читали?
PeakXXX возвращает пиковое значение параметра XXX, которое когда либо было достигнуто процессом.
Любой процесс занимает некоторое количество виртуальной памяти, она включает в себя выделенную физическую, еще не выделенную ОС вообще, а также область в файле подкачки (выделенная память, но сброшенная туда ввиду нехватки ОЗУ).
Так вот, физическая память, выделенная процессу называется WorkingSet, а вообще вся память процесса - виртуальная память VirtualMemorySize.
Размер WorkingSet можно ограничить снизу (MinWorkingSet) и сверху (MaxWorkingSet), если процесс затребует больше физической памяти, чем его MaxWorkingSet, то часть ворк-сета будет сброшена в файл подкачки.
Кстати мне и надо получить максимальное использование оперативки, беда лишь в том, что я пытаюсь вычислить ее одинаково, для одинакового процесса, а получаю каждый раз значения, превосходящие порой на 300% предыдущее значение.
М... на сколько я понял (может быть и не верно) мне надо юзать PeakVirtualMemorySize64.
Может дело правда в том, что я провожу измерения памяти каждые 50 мс. И тут как-о сказывается время когда измеряю. Но как иначе получить дейстьвительное максимальное значение памяти, если когда процесс завершил работу, попытки получить значение этих полей заканчивается ошибкой, что процесс уже завершился?
гугл не знает что такое tool help, tool help c#, tool help sdk
я тоже. где и как узнать что это тоже не знаю
гугл не знает что такое tool help, tool help c#, tool help sdk
я тоже. где и как узнать что это тоже не знаю
Класс Process фактически обертка над ними. Искать надо в МСДН.
CreateToolhelp32Snapshot
смотри в MSDN и заголовок tlhelp32.h
алгоритм для подсчета будет приблизительно такой:
1) делаем снимок
2) прыгаем на первую кучу из списка
3) прыгаем на первый блок из кучи и берем его размер
4) прыгаем на следующий блок и сумируем его размер с предыдущим
5) повторяем шаг 4 пока не закончятся блоки
6) прыгаем на следующую кучу
7) повторяем шаг 3, 4, 5 для нее)
....
думаю дальше понятно
будет время заморочусь и сделаю пример;)
как в С# не знаю...
Это очень круто, но как это поможет мне определить именно максимальное использование памяти? Ведь если проверять интервалами, то я могу упустить максимальное использование памяти, когда наступает пик и процесс завершается до следующей проверки
проверяю уже и так раз в миллисекунду.
Делать цикл без задержки это вообще ужас, ибо одновременно прийдется проверять от одного процесса до n процессов. А использование CPU будет 100%
Как бы получить максимальное использование памяти прямо перед завершением работы процесса.
вот тебе примерчик сделал С++:)
думаю с остальными полями структуры разберешься:)
#include <psapi.h>
#pragma comment (lib, "psapi.lib")
#include <iostream>
int main (void)
{
// берем хэндл на процесс
HANDLE hProcess = GetCurrentProcess();
// инициализируем структуру
PROCESS_MEMORY_COUNTERS pmc;
ZeroMemory(&pmc, sizeof(PROCESS_MEMORY_COUNTERS));
// заполняем структуру инфой о нужном нам процессе
GetProcessMemoryInfo(hProcess, &pmc, sizeof(PROCESS_MEMORY_COUNTERS));
std::cout << "Peak usage: " << pmc.PeakWorkingSetSize / 1024 << "kb" << std::endl;
std::cout << "Current usage: " << pmc.WorkingSetSize / 1024 << "kb" << std::endl;
return 0;
}
И мне по прежнему не ясно, как он мне поможет определить макс РАМ прямо перед завершением процесса. Не своего конечно же, а чужого, который я сам запускаю и сам мониторю. Может есть какие-то события что процесс собирается завершиться, но пока не завершился можно узнать его РАМ юзаж, или можно прервать завершение работы процесса (допустим в ilde его перевести, или ожидание ввода или еще чего-то), измерить оперативку и завершить его работу или может какая-то статистика в ОС есть, откуда можно взять инфу о уже завершившемся процесе (Я не знаю возможно ли это все...)
твой пример конечно определяет именно максимальное использование РАМ и именно в конце работы процесса, но своего, а мне надо чужого...
Смысл его в том что мы внедряем в нужный нам процесс библиотеку (шпионский модуль). Перед завершением процесса система всегда вызывает из спроецированных в адресное пространство процесса библиотек функцию DllMain с параметром DLL_PROCESS_DETACH, вот в этом месте мы и собираем нужную нам статистику, а в это время наша программа которая внедрила шпионский модуль ожидает завершения процесса, как только процесс завершился, она открывает файл в который шпионский модуль записал нужную нам инфу и читает.
остальные объяснения в коментарияx к коду
прога внедряющая модуль:
#include <string.h>
// подключаем toolhelp32 и PSAPI
#include <tlhelp32.h>
#include <psapi.h>
#pragma comment (lib, "psapi.lib")
// не бум заморачиваться с окнами, в данном контексте нам это не нужно
#include <iostream>
// используя toolhelp32 напишем функцию которая будет нам возвращать идентификатор
// процесса по имени, если функция завершилась удачно, возвращаем идентификатор процесса
// в противном случае возвращаем -1 и устанавливаем коды ошибок
#define A_SUCCES 0x0 // все нормально
#define A_PROCESS_NOT_FOUND 0x1 // процесс с заданным именем не существует
#define A_FUNCTION_INTERNAL_ERROR 0x2 // внутренняя ошибка функции(невозможно сделать снимок и т.д.)
DWORD GetProcessByName(const char* proc_name)
{
// делаем снимок процессов
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
// проверяем что все нормально
if (hSnapshot == INVALID_HANDLE_VALUE) {
// если не удалось создать снимок, устанавливаем код ошибки (внутренняя ошибка)
SetLastError(A_FUNCTION_INTERNAL_ERROR);
// и выходим
return -1;
}
// со снимком все нормально идем дальше
// инициализируем структуру PROCESSENTRY32
PROCESSENTRY32 pe32;
ZeroMemory(&pe32, sizeof(PROCESSENTRY32));
pe32.dwSize = sizeof(PROCESSENTRY32);
// прыгаем на первый процесс
BOOL bError;
bError = Process32First(hSnapshot, &pe32);
if (bError == FALSE) {
SetLastError(A_FUNCTION_INTERNAL_ERROR);
return -1;
}
// сравниваем
if ((::strcmp(pe32.szExeFile, proc_name))== 0) {
CloseHandle(hSnapshot);
SetLastError(A_SUCCES);
return pe32.th32ProcessID;
}
// перебираем процессы дальше
while (bError!= FALSE) {
bError = Process32Next(hSnapshot, &pe32);
if ((::strcmp(pe32.szExeFile, proc_name)) == 0) {
CloseHandle(hSnapshot);
SetLastError(A_SUCCES);
return pe32.th32ProcessID;
}
}
// процесс с заданным именем не найден
SetLastError(A_PROCESS_NOT_FOUND);
return -1;
}
int main (void)
{
char *proc_n = "calc.exe";
char *spy_mod = "D:\\Development\\spymod.dll";
char *spy_info_file = "D:\\spyinfo.spy";
int length_spy_mod = ::strlen(spy_mod) + 1;
// ищем нужный процесс и обрабатываем ошибки
DWORD pid = GetProcessByName(proc_n);
if (pid == -1) {
if (GetLastError() == A_PROCESS_NOT_FOUND) {
std::cout << "Process " << proc_n << " not found" << std::endl;
return 0;
} else {
std::cout << "Error in function GetProcessByName. Need to Debug" << std::endl;
return 0;
}
}
// нужно же что-то сообщить
std::cout << "Process " << proc_n << " found with ID = " << pid << std::endl;
// подготавливаемся к внедрению нашего шпиона в адресное пространство изучаемого процесса
// оттолкнемся от того, что скорее всего kernel32.dll расположен в адресном пространство изучаемого
// процеса по тому же базовому адресу что и в нашем
PVOID pfLoadLibraryA = GetProcAddress(GetModuleHandle("kernel32"), "LoadLibraryA");
// пытаемся открыть нужный нам процес с правами на создание потоков и блоков памяти,
// а также записи в память и синхронизации
HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | SYNCHRONIZE,
FALSE, pid);
// здесь стоит проверить, получили ли мы дескриптор, потому что для некоторых
// процессов система нам может отказать в открытии с такими правами
// пока забьем на это, ибо не думаю что калькулятор представляет особую ценность для безопасности системы
// теперь воспользуясь тем что прототип функции процесса и прототип LoadLibraryA выглядят очень и очень схоже
// заставим исследуемый процесс загрузить нашего шпиона в свое адресное пространство, для этого:
// 1) создадим в его адресном пространстве блок памяти и запишем туда строку с именем нашего шпиона
char* remote_buf =(char*) VirtualAllocEx(hProcess, NULL, length_spy_mod, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, remote_buf, spy_mod, length_spy_mod, NULL);
// 2) создадим в нем поток, подсунув адрес LoadLibraryA, вместо функции потока
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,(LPTHREAD_START_ROUTINE) pfLoadLibraryA, remote_buf, 0, NULL);
//3) ожидаем завершения работы потока
WaitForSingleObject(hThread, INFINITE);
//4) освобождаем выделенную память
VirtualFreeEx(hProcess, remote_buf, 0, MEM_RELEASE);
//5) все наш шпион успешно внедрен, сообщим об этом
std::cout << "spymod.dll successfuly injected in " << proc_n << std::endl;
std::cout << "Waiting process" << std::endl;
// теперь нам осталось дождаться завершения процесса и прочесть данные собранные шпионом
WaitForSingleObject(hProcess, INFINITE);
std::cout << proc_n << " terminated" << std::endl;
// прочтем данные собранные нашим шпионом
PROCESS_MEMORY_COUNTERS pmc;
ZeroMemory(&pmc, sizeof(PROCESS_MEMORY_COUNTERS));
DWORD nb;
HANDLE hSpyFile = CreateFile(spy_info_file, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
ReadFile(hSpyFile, &pmc, sizeof(pmc), &nb, NULL);
CloseHandle(hSpyFile);
// и выведем нужные нам
std::cout << "Max process memory usage: " << pmc.PeakWorkingSetSize / 1024 << std::endl;
return 0;
}
библиотека шпион:
// подключаем PSAPI
#include <psapi.h>
#pragma comment (lib, "psapi.lib")
BOOL WINAPI
DllMain (HINSTANCE hInstDll, DWORD fdwReason, LPVOID lpvReserved)
{
char *spy_info_file = "D:\\spyinfo.spy";
HANDLE hSpyInfo;
// при завершении процесса система вызовет DllMain с причиной DLL_PROCESS_DETACH
if (fdwReason == DLL_PROCESS_DETACH)
{
// проинициализируем структуру PROCESS_MEMORY_COUNTERS
PROCESS_MEMORY_COUNTERS pmc;
ZeroMemory(&pmc, sizeof(PROCESS_MEMORY_COUNTERS));
// заполним ее данными о текущем процессе
GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(PROCESS_MEMORY_COUNTERS));
// запишем данные о процессе в наш файлик
DWORD nb;
hSpyInfo = CreateFile(spy_info_file, FILE_ALL_ACCESS, 0, NULL, CREATE_ALWAYS, NULL, NULL);
WriteFile(hSpyInfo, &pmc, sizeof(pmc), &nb, NULL);
CloseHandle(hSpyInfo);
}
return TRUE;
}
в примере шпион внедряется в обычный виндовский калькулятор.
Компилировать без юникода, ну и пути поменять на свои.
Полноценная статья по внедрению длл :)
Все очень подробно и как надо :) Премного благодарен!!!
Единстенный вопрос что у меня возникает, длл шпион можно внедрить в любое приложение? Т.е. дос / вин32 / .нет? На сколько я знаю есть определенные сложности в том, чтобы заставить .нет приложение использовать натив длл, да и наоборот наверное, если натив экзе заставить загрузить .нет сборку. Это так для развития :P
Хотя... Ведь сам процесс это уже натив код, может и не будет проблем с внедрением :)
Осталось это переписать на c# и будет мне счастье :D
Легко:
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
namespace ConsoleApplication11 {
class Program {
static void Main(string[] args) {
List<Process> processes = new List<Process>(Process.GetProcesses());
foreach (Process process in processes) {
process.Exited += delegate(object sender, EventArgs e) {
Process exited_process = (Process)sender;
Console.WriteLine("Exited: {0}", exited_process);
Console.WriteLine("Peak virtual memory usage: {0}", exited_process.PeakVirtualMemorySize64);
Console.WriteLine("Peak working set size: {0}", exited_process.PeakWorkingSet64);
};
Console.WriteLine(process);
try {
process.EnableRaisingEvents = true;
} catch {
Console.WriteLine("Failed to subscribe 'Exited' event: {0}", process);
}
}
Console.ReadLine();
}
}
}
Единственно что мне не ясно, почему когда я пытался обратиться к полям p.PeakVirtualMemorySize64 сразу же после завершения работы процесса, отписывало ошибку что процесс уже завершен и значение не доступно. Однако тут все отлично работает %) Событие Exited генерируется когда процесс еще не завершился или как?
The Exited event indicates that the associated process exited. This occurrence means either that the process terminated (aborted) or successfully closed. This event can occur only if the value of the EnableRaisingEvents property is true. В мсдн написано что когда завершился... м...
P.S. Код с внедрением длл сгодится на множество других целей :D Интересно поиграться
Сразу после завершения процесса делается финальный снимок параметров, который используется в контексте исполнения обработчика - вполне ожидаемое поведение. Думаю, что вне обработчика Exited, обращение к этим свойствам приведет к непредсказуемому результату.
Это обычная техника (известна как dll injection), да только антивирусы будут немного озадачены таким поведением программы, могут ругаться.
З.Ы. Решение на .NET ограничено в возможностях - оно не позволяет обращаться к процессам-сервисам. Но так как вы запускаете процесс из собственного кода, ограничение вас не касается.
Не могу поверить, но этот способ перестал для меня работать, опять ошибка "Process has exited, so the requested information is not available.", только в этот раз уже при исполнении делегата.
Раньше это работало, как только я прочитал ваш пост, через несколкьо дней решил оптимизировать, хоть по сути ничего и не поменял.
Вы можете мне объяснить почему в вашем случае при выполнении делегата нет ошибки, а в моем теперь есть? %) В упор не понимаю, логика-то одна
код таков:
{
string[] ReturnValues = new string[3];
bool delegate_executed = false;
System.Diagnostics.ProcessStartInfo si = new System.Diagnostics.ProcessStartInfo();
si.FileName = System.IO.Path.GetFullPath(cnfgtn.WorkFolder + "\\" + login + "\\testFile.exe");
si.WorkingDirectory = cnfgtn.WorkFolder + "\\" + login;
si.CreateNoWindow = true;
si.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(si);
p.Exited += delegate(object sender, System.EventArgs e)
{
ReturnValues[2] = ((System.Diagnostics.Process)sender).PeakWorkingSet64.ToString(); //здесь вылетает исключение
delegate_executed = true;
};
p.EnableRaisingEvents = true;
p.WaitForExit(max_exec_time_limit * 1000);
if (!p.HasExited)
{
ReturnValues[0] = "Test file has overused time limit (" + max_exec_time_limit + " seconds) and was terminated";
if (!p.HasExited)
p.Kill();
}
while (!delegate_executed) //это уже от безысходности, чтоб исключить случай когда следующий код выполнится до завершения работы делегата
continue;
if (System.Convert.ToInt32(ReturnValues[2]) > max_ram_usage_limit)
ReturnValues[0] = "Test file has overused RAM limit (" + max_ram_usage_limit + " bytes) and was terminated";
ReturnValues[1] = (p.ExitTime - p.StartTime).TotalSeconds.ToString();
p.Close();
return ReturnValues;
}