Слепая зона производительности: почему ваш процесс не знает, сколько он ест?
Представьте: вы написали сложное приложение на C++, оно работает, но начинает тормозить. Вы запускаете top, htop или диспетчер задач — и видите, что ваш процесс пожирает 90% CPU или гигабайты памяти. Но как заставить сам процесс осознать это и, например, динамически снизить нагрузку или сбросить кэш?
Это не просто академический интерес. В production-среде, на серверах без GUI, вы не можете полагаться на внешние утилиты. Нужно, чтобы программа сама следила за собой. Именно эту задачу решает популярный вопрос на Stack Overflow: "How to determine CPU and memory consumption from inside a process". Давайте разберем его на реальных примерах на C++.
Кроссплатформенная дилемма: Linux vs Windows
Операционные системы по-разному предоставляют доступ к статистике процессов. Под капотом Linux лежит виртуальная файловая система /proc, а Windows использует Win32 API. Мы напишем универсальный класс, который работает на обеих платформах.
Основные метрики, которые мы будем добывать:
- Использование CPU (в процентах или долях от ядра)
- Физическая память (RSS) — реально занятая RAM
- Виртуальная память (VSZ) — общий объем адресного пространства
Считываем CPU на Linux: магия /proc/self/stat
Для получения времени CPU процесса читаем файл /proc/self/stat. Он содержит десятки полей, нас интересуют поля 13-16 (utime, stime, cutime, cstime) — время в тиках (jiffies). Чтобы перевести в проценты, нужно знать частоту тиков (sysconf(_SC_CLK_TCK)) и общее время работы системы.
#ifdef __linux__
#include <unistd.h>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
double getCpuUsageLinux() {
static unsigned long long prevTotal = 0, prevIdle = 0;
static double prevProcessTicks = 0;
static bool first = true;
// Время процесса
std::ifstream procStat("/proc/self/stat");
std::string line;
std::getline(procStat, line);
std::istringstream iss(line);
std::vector<std::string> fields;
std::string field;
while (iss >> field) fields.push_back(field);
unsigned long utime = std::stoul(fields[13]);
unsigned long stime = std::stoul(fields[14]);
unsigned long cutime = std::stoul(fields[15]);
unsigned long cstime = std::stoul(fields[16]);
double processTicks = utime + stime + cutime + cstime;
// Системное время
std::ifstream statFile("/proc/stat");
std::getline(statFile, line);
statFile.close();
std::istringstream iss2(line);
std::string cpuLabel;
iss2 >> cpuLabel;
unsigned long long user, nice, system, idle, iowait, irq, softirq, steal;
iss2 >> user >> nice >> system >> idle >> iowait >> irq >> softirq >> steal;
unsigned long long total = user + nice + system + idle + iowait + irq + softirq + steal;
if (first) {
prevTotal = total;
prevIdle = idle;
prevProcessTicks = processTicks;
first = false;
return 0.0;
}
unsigned long long totalDelta = total - prevTotal;
double processDelta = processTicks - prevProcessTicks;
double cpuPercent = (processDelta / totalDelta) * 100.0;
prevTotal = total;
prevIdle = idle;
prevProcessTicks = processTicks;
return cpuPercent;
}
#endif
Этот код вычисляет процент CPU, занятый процессом с момента первого вызова. Обратите внимание: мы берем разницу между двумя замерами — это стандартный подход для точности.
Память на Linux: RSS и VSZ
Для памяти читаем /proc/self/status. Нас интересуют строки VmRSS и VmSize.
#ifdef __linux__
#include <fstream>
#include <string>
struct MemoryInfo {
long vmPeak; // пиковая виртуальная
long vmSize; // текущая виртуальная
long vmRSS; // физическая в RAM
long vmData; // данные (heap + mmap)
};
MemoryInfo getMemoryUsageLinux() {
MemoryInfo info = {0,0,0,0};
std::ifstream statusFile("/proc/self/status");
std::string line;
while (std::getline(statusFile, line)) {
if (line.find("VmPeak:") == 0) sscanf(line.c_str(), "VmPeak: %ld kB", &info.vmPeak);
else if (line.find("VmSize:") == 0) sscanf(line.c_str(), "VmSize: %ld kB", &info.vmSize);
else if (line.find("VmRSS:") == 0) sscanf(line.c_str(), "VmRSS: %ld kB", &info.vmRSS);
else if (line.find("VmData:") == 0) sscanf(line.c_str(), "VmData: %ld kB", &info.vmData);
}
return info;
}
#endif
Важно: Все значения в килобайтах. VmRSS — это реальное потребление RAM, а VmSize — виртуальное адресное пространство (может быть огромным из-за memory-mapped файлов).
Windows: Win32 API и структуры PROCESS_MEMORY_COUNTERS
На Windows подход другой — используем GetProcessMemoryInfo из psapi.h и GetSystemTimes для CPU.
#ifdef _WIN32
#include <windows.h>
#include <psapi.h>
double getCpuUsageWindows() {
static ULARGE_INTEGER prevKernel, prevUser, prevIdle;
static double prevProcessTime = 0;
static bool first = true;
FILETIME ftCreation, ftExit, ftKernel, ftUser;
GetProcessTimes(GetCurrentProcess(), &ftCreation, &ftExit, &ftKernel, &ftUser);
ULARGE_INTEGER kernel, user;
kernel.LowPart = ftKernel.dwLowDateTime;
kernel.HighPart = ftKernel.dwHighDateTime;
user.LowPart = ftUser.dwLowDateTime;
user.HighPart = ftUser.dwHighDateTime;
double processTime = (kernel.QuadPart + user.QuadPart) / 10000.0; // в миллисекундах
FILETIME ftIdle, ftKernelSys, ftUserSys;
GetSystemTimes(&ftIdle, &ftKernelSys, &ftUserSys);
ULARGE_INTEGER idle, kernelSys, userSys;
idle.LowPart = ftIdle.dwLowDateTime;
idle.HighPart = ftIdle.dwHighDateTime;
kernelSys.LowPart = ftKernelSys.dwLowDateTime;
kernelSys.HighPart = ftKernelSys.dwHighDateTime;
userSys.LowPart = ftUserSys.dwLowDateTime;
userSys.HighPart = ftUserSys.dwHighDateTime;
double totalSys = (