#include "mem_info.h"
#include <util/generic/strbuf.h>
#include <util/stream/file.h>
#include <util/string/cast.h>
#include <util/string/builder.h>
#include "error.h"
#include "info.h"
#if defined(_unix_)
#if defined(_freebsd_)
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/user.h>
#elif defined(_darwin_) && !defined(_arm_) && !defined(__IOS__)
#include <libproc.h>
#elif defined(__MACH__) && defined(__APPLE__)
#include <mach/mach.h>
#endif
#elif defined(_win_)
#include <Windows.h>
#include <util/generic/ptr.h>
using NTSTATUS = LONG;
#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004
#define STATUS_BUFFER_TOO_SMALL 0xC0000023
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _CLIENT_ID {
HANDLE UniqueProcess;
HANDLE UniqueThread;
} CLIENT_ID, *PCLIENT_ID;
using KWAIT_REASON = ULONG;
typedef struct _SYSTEM_THREAD_INFORMATION {
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER CreateTime;
ULONG WaitTime;
PVOID StartAddress;
CLIENT_ID ClientId;
LONG Priority;
LONG BasePriority;
ULONG ContextSwitches;
ULONG ThreadState;
KWAIT_REASON WaitReason;
} SYSTEM_THREAD_INFORMATION, *PSYSTEM_THREAD_INFORMATION;
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
LARGE_INTEGER SpareLi1;
LARGE_INTEGER SpareLi2;
LARGE_INTEGER SpareLi3;
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ImageName;
LONG BasePriority;
HANDLE UniqueProcessId;
HANDLE InheritedFromUniqueProcessId;
ULONG HandleCount;
ULONG SessionId;
ULONG_PTR PageDirectoryBase;
SIZE_T PeakVirtualSize;
SIZE_T VirtualSize;
DWORD PageFaultCount;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
SIZE_T QuotaPeakPagedPoolUsage;
SIZE_T QuotaPagedPoolUsage;
SIZE_T QuotaPeakNonPagedPoolUsage;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
SIZE_T PrivatePageCount;
LARGE_INTEGER ReadOperationCount;
LARGE_INTEGER WriteOperationCount;
LARGE_INTEGER OtherOperationCount;
LARGE_INTEGER ReadTransferCount;
LARGE_INTEGER WriteTransferCount;
LARGE_INTEGER OtherTransferCount;
SYSTEM_THREAD_INFORMATION Threads[1];
} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation = 0,
SystemProcessInformation = 5,
} SYSTEM_INFORMATION_CLASS;
#else
#endif
namespace NMemInfo {
TMemInfo GetMemInfo(pid_t pid) {
TMemInfo result;
#if defined(_unix_)
#if defined(_linux_) || defined(_freebsd_) || defined(_cygwin_)
const ui32 pagesize = NSystemInfo::GetPageSize();
#endif
#if defined(_linux_) || defined(_cygwin_)
TString path;
if (!pid) {
path = "/proc/self/statm";
} else {
path = TStringBuilder() << TStringBuf("/proc/") << pid << TStringBuf("/statm");
}
const TString stats = TUnbufferedFileInput(path).ReadAll();
TStringBuf statsiter(stats);
result.VMS = FromString<ui64>(statsiter.NextTok(' ')) * pagesize;
result.RSS = FromString<ui64>(statsiter.NextTok(' ')) * pagesize;
#if defined(_cygwin_)
//cygwin not very accurate
result.VMS = Max(result.VMS, result.RSS);
#endif
#elif defined(_freebsd_)
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
size_t size = sizeof(struct kinfo_proc);
struct kinfo_proc proc;
Zero(proc);
errno = 0;
if (sysctl((int*)mib, 4, &proc, &size, nullptr, 0) == -1) {
int err = errno;
TString errtxt = LastSystemErrorText(err);
ythrow yexception() << "sysctl({CTL_KERN,KERN_PROC,KERN_PROC_PID,pid},4,proc,&size,NULL,0) returned -1, errno: " << err << " (" << errtxt << ")" << Endl;
}
result.VMS = proc.ki_size;
result.RSS = proc.ki_rssize * pagesize;
#elif defined(_darwin_) && !defined(_arm_) && !defined(__IOS__)
if (!pid) {
pid = getpid();
}
struct proc_taskinfo taskInfo;
const int r = proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &taskInfo, sizeof(taskInfo));
if (r != sizeof(taskInfo)) {
int err = errno;
TString errtxt = LastSystemErrorText(err);
ythrow yexception() << "proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &taskInfo, sizeof(taskInfo)) returned " << r << ", errno: " << err << " (" << errtxt << ")" << Endl;
}
result.VMS = taskInfo.pti_virtual_size;
result.RSS = taskInfo.pti_resident_size;
#elif defined(__MACH__) && defined(__APPLE__)
Y_UNUSED(pid);
struct mach_task_basic_info taskInfo;
mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
const int r = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&taskInfo, &infoCount);
if (r != KERN_SUCCESS) {
int err = errno;
TString errtxt = LastSystemErrorText(err);
ythrow yexception() << "task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount) returned" << r << ", errno: " << err << " (" << errtxt << ")" << Endl;
}
result.VMS = taskInfo.virtual_size;
result.RSS = taskInfo.resident_size;
#elif defined(_arm_)
Y_UNUSED(pid);
ythrow yexception() << "arm is not supported";
#endif
#elif defined(_win_)
if (!pid) {
pid = GetCurrentProcessId();
}
NTSTATUS status;
TArrayHolder<char> buffer;
ULONG bufferSize;
// Query data for all processes and threads in the system.
// This is probably an overkill if the target process is normal not-privileged one,
// but allows to obtain information even about system processes that are not open-able directly.
typedef NTSTATUS(_stdcall * NTQSI_PROC)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG);
NTQSI_PROC NtQuerySystemInformation = (NTQSI_PROC)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "NtQuerySystemInformation");
bufferSize = 0x4000;
for (;;) {
buffer.Reset(new char[bufferSize]);
status = NtQuerySystemInformation(SystemProcessInformation, buffer.Get(), bufferSize, &bufferSize);
if (!status) {
break;
}
if (status != STATUS_BUFFER_TOO_SMALL && status != STATUS_INFO_LENGTH_MISMATCH) {
ythrow yexception() << "NtQuerySystemInformation failed with status code " << status;
}
}
SYSTEM_PROCESS_INFORMATION* process = (SYSTEM_PROCESS_INFORMATION*)buffer.Get();
while (process->UniqueProcessId != (HANDLE)(size_t)(pid)) {
if (!process->NextEntryOffset) {
ythrow yexception() << "GetMemInfo: invalid PID";
}
process = (SYSTEM_PROCESS_INFORMATION*)((char*)process + process->NextEntryOffset);
}
result.VMS = process->VirtualSize;
result.RSS = process->WorkingSetSize;
#endif
return result;
}
}