| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "process_memory.h" |
| 4 | #include "libnetdata/libnetdata.h" |
| 5 | |
| 6 | static OS_PROCESS_MEMORY last_process_memory_info = OS_PROCESS_MEMORY_EMPTY; |
| 7 | |
| 8 | #if defined(OS_LINUX) |
| 9 | /** |
| 10 | * Get process memory information for Linux |
| 11 | * |
| 12 | * Uses /proc/<pid>/statm and /proc/<pid>/status to collect memory information. |
| 13 | */ |
| 14 | OS_PROCESS_MEMORY os_process_memory(pid_t pid) { |
| 15 | OS_PROCESS_MEMORY proc_mem = OS_PROCESS_MEMORY_EMPTY; |
| 16 | char filename[FILENAME_MAX + 1]; |
| 17 | char buffer[4096 + 1]; |
| 18 | long page_size = sysconf(_SC_PAGESIZE); |
| 19 | |
| 20 | if (pid == 0) |
| 21 | pid = getpid(); |
| 22 | |
| 23 | // Get statm information (for RSS, total size, shared, text, data) |
| 24 | size_t len = 0; |
| 25 | len = strcatz(filename, len, "/proc/", sizeof(filename)); |
| 26 | len += print_uint64(&filename[len], pid); |
| 27 | len = strcatz(filename, len, "/statm", sizeof(filename)); |
| 28 | if (read_txt_file(filename, buffer, sizeof(buffer)) == 0) { |
| 29 | unsigned long size = 0, resident = 0, shared = 0, text = 0, lib = 0, data = 0; |
| 30 | char *pos = buffer; |
| 31 | size = str2ull(pos, &pos); |
| 32 | resident = str2ull(pos, &pos); |
| 33 | shared = str2ull(pos, &pos); |
| 34 | text = str2ull(pos, &pos); |
| 35 | lib = str2ull(pos, &pos); |
| 36 | data = str2ull(pos, &pos); |
| 37 | (void)lib; |
| 38 | (void)data; |
| 39 | |
| 40 | proc_mem.virtual_size = size * page_size; |
| 41 | proc_mem.rss = resident * page_size; |
| 42 | proc_mem.shared = shared * page_size; |
| 43 | proc_mem.text = text * page_size; |
| 44 | proc_mem.data = data * page_size; |
| 45 | } |
| 46 | |
| 47 | // Get status information (for peak resident set size) |
| 48 | len = 0; |
| 49 | len = strcatz(filename, len, "/proc/", sizeof(filename)); |
| 50 | len += print_uint64(&filename[len], pid); |
| 51 | len = strcatz(filename, len, "/status", sizeof(filename)); |
| 52 | if (read_txt_file(filename, buffer, sizeof(buffer)) == 0) { |
| 53 | char *s = strstr(buffer, "VmHWM:"); |
| 54 | if (s) { |
| 55 | s += 6; // Skip "VmHWM:" |
| 56 | while (isspace((uint8_t)*s)) s++; // Skip spaces |
| 57 | proc_mem.max_rss = str2ull(s, NULL) * 1024; // VmHWM is in kB, convert to bytes |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | if (OS_PROCESS_MEMORY_OK(proc_mem)) |
| 62 | last_process_memory_info = proc_mem; |
| 63 | |
| 64 | return proc_mem; |
| 65 | } |
| 66 | #endif |
| 67 | |
| 68 | #if defined(OS_FREEBSD) |
| 69 | #include <sys/param.h> |
| 70 | #include <sys/user.h> |
| 71 | #include <sys/sysctl.h> |
| 72 | |
| 73 | /** |
| 74 | * Get process memory information for FreeBSD |
| 75 | * |
| 76 | * Uses the sysctl API to collect memory information. |
| 77 | */ |
| 78 | OS_PROCESS_MEMORY os_process_memory(pid_t pid) { |
| 79 | OS_PROCESS_MEMORY proc_mem = OS_PROCESS_MEMORY_EMPTY; |
| 80 | int mib[4]; |
| 81 | struct kinfo_proc proc; |
| 82 | size_t len = sizeof(proc); |
| 83 | |
| 84 | if (pid == 0) |
| 85 | pid = getpid(); |
| 86 | |
| 87 | // Use direct sysctl call instead of higher-level functions |
| 88 | mib[0] = CTL_KERN; |
| 89 | mib[1] = KERN_PROC; |
| 90 | mib[2] = KERN_PROC_PID; |
| 91 | mib[3] = pid; |
| 92 | |
| 93 | if (sysctl(mib, 4, &proc, &len, NULL, 0) == 0) { |
| 94 | int page_size = getpagesize(); |
| 95 | |
| 96 | proc_mem.rss = proc.ki_rssize * page_size; |
| 97 | proc_mem.virtual_size = proc.ki_size; |
| 98 | |
| 99 | // Get maximum RSS without using getrusage |
| 100 | unsigned long maxrss = 0; |
| 101 | size_t maxrss_len = sizeof(maxrss); |
| 102 | char maxrss_name[128]; |
| 103 | size_t maxrss_name_len = 0; |
| 104 | maxrss_name[0] = '\0'; |
| 105 | maxrss_name_len = strcatz(maxrss_name, maxrss_name_len, "kern.proc.", sizeof(maxrss_name)); |
| 106 | maxrss_name_len += print_uint64(&maxrss_name[maxrss_name_len], KERN_PROC_PID); |
| 107 | maxrss_name_len = strcatz(maxrss_name, maxrss_name_len, ".rusage.", sizeof(maxrss_name)); |
| 108 | maxrss_name_len += print_uint64(&maxrss_name[maxrss_name_len], pid); |
| 109 | maxrss_name_len = strcatz(maxrss_name, maxrss_name_len, ".maxrss", sizeof(maxrss_name)); |
| 110 | if (sysctlbyname(maxrss_name, &maxrss, &maxrss_len, NULL, 0) == 0) |
| 111 | proc_mem.max_rss = maxrss * 1024; // maxrss is in KB |
| 112 | else |
| 113 | proc_mem.max_rss = proc_mem.rss; // Fall back to current RSS if peak not available |
| 114 | |
| 115 | // Get memory map information for shared memory approximation |
| 116 | // This approach still uses syscall directly, which is already low-level |
| 117 | size_t shared_pages_len = 0; |
| 118 | int mib_shared[] = {CTL_KERN, KERN_PROC, KERN_PROC_VMMAP, pid}; |
| 119 | |
| 120 | // First call to get size |
| 121 | if (sysctl(mib_shared, 4, NULL, &shared_pages_len, NULL, 0) == 0 && shared_pages_len > 0) { |
| 122 | // Allocate memory for the vmmap data |
| 123 | void *shared_info = mallocz(shared_pages_len); |
| 124 | if (sysctl(mib_shared, 4, shared_info, &shared_pages_len, NULL, 0) == 0) { |
| 125 | // For now, use a simple approximation |
| 126 | // A more accurate calculation would involve parsing the vm_map_entries |
| 127 | proc_mem.shared = proc_mem.rss / 4; // rough estimate |
| 128 | } |
| 129 | freez(shared_info); |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | if (OS_PROCESS_MEMORY_OK(proc_mem)) |
| 134 | last_process_memory_info = proc_mem; |
| 135 | |
| 136 | return proc_mem; |
| 137 | } |
| 138 | #endif |
| 139 | |
| 140 | #if defined(OS_MACOS) |
| 141 | #include <mach/mach.h> |
| 142 | #include <mach/task.h> |
| 143 | #include <mach/mach_init.h> |
| 144 | |
| 145 | /** |
| 146 | * Get process memory information for macOS |
| 147 | * |
| 148 | * Uses the Mach task API to collect memory information. |
| 149 | * Mach API is already a low-level API for accessing process information on macOS. |
| 150 | */ |
| 151 | OS_PROCESS_MEMORY os_process_memory(pid_t pid) { |
| 152 | OS_PROCESS_MEMORY proc_mem = OS_PROCESS_MEMORY_EMPTY; |
| 153 | task_t task; |
| 154 | kern_return_t kr; |
| 155 | |
| 156 | if (pid == 0) |
| 157 | pid = getpid(); |
| 158 | |
| 159 | // Get the Mach task for the process - this is a low-level system call |
| 160 | kr = task_for_pid(mach_task_self(), pid, &task); |
| 161 | if (kr != KERN_SUCCESS) { |
| 162 | if (pid == getpid()) { |
| 163 | // Always works for current process |
| 164 | task = mach_task_self(); |
| 165 | } else { |
| 166 | // Can't get info for other processes without privileges |
| 167 | return proc_mem; |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | // Get basic task info (RSS and virtual size) - direct Mach API call |
| 172 | struct task_basic_info_64 task_basic_info; |
| 173 | mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT; |
| 174 | kr = task_info(task, TASK_BASIC_INFO_64, (task_info_t)&task_basic_info, &count); |
| 175 | |
| 176 | if (kr == KERN_SUCCESS) { |
| 177 | proc_mem.rss = task_basic_info.resident_size; |
| 178 | proc_mem.virtual_size = task_basic_info.virtual_size; |
| 179 | |
| 180 | // Get page-in information to estimate max RSS - direct Mach API call |
| 181 | // This avoids using getrusage().ru_maxrss |
| 182 | task_events_info_data_t events_info; |
| 183 | mach_msg_type_number_t events_info_count = TASK_EVENTS_INFO_COUNT; |
| 184 | kr = task_info(task, TASK_EVENTS_INFO, (task_info_t)&events_info, &events_info_count); |
| 185 | |
| 186 | if (kr == KERN_SUCCESS && events_info.pageins > 0) { |
| 187 | // If we have page-ins, we can use a more accurate calculation |
| 188 | vm_size_t page_size = 0; // Changed from mach_vm_size_t to vm_size_t |
| 189 | host_page_size(mach_host_self(), &page_size); |
| 190 | proc_mem.max_rss = task_basic_info.resident_size + (events_info.pageins * page_size); |
| 191 | } else { |
| 192 | // Otherwise, just use current RSS as a fallback |
| 193 | proc_mem.max_rss = task_basic_info.resident_size; |
| 194 | } |
| 195 | |
| 196 | // On macOS, use a simpler approach without vm_region |
| 197 | // Just estimate shared, text, and data based on total memory |
| 198 | if (task_basic_info.resident_size > 0) { |
| 199 | // Simple heuristic estimates based on typical process memory layout |
| 200 | proc_mem.shared = task_basic_info.resident_size / 5; // ~20% shared libraries |
| 201 | proc_mem.text = task_basic_info.resident_size / 5; // ~20% code |
| 202 | proc_mem.data = task_basic_info.resident_size - proc_mem.shared - proc_mem.text; // remaining is data |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | // Release the task port if we obtained it - low-level resource management |
| 207 | if (task != mach_task_self()) { |
| 208 | mach_port_deallocate(mach_task_self(), task); |
| 209 | } |
| 210 | |
| 211 | if (OS_PROCESS_MEMORY_OK(proc_mem)) |
| 212 | last_process_memory_info = proc_mem; |
| 213 | |
| 214 | return proc_mem; |
| 215 | } |
| 216 | #endif |
| 217 | |
| 218 | #if defined(OS_WINDOWS) |
| 219 | #include <windows.h> |
| 220 | #include <psapi.h> |
| 221 | #include <tlhelp32.h> |
| 222 | |
| 223 | /** |
| 224 | * Get process memory information for Windows |
| 225 | * |
| 226 | * Uses Windows APIs to collect memory information. |
| 227 | * Windows API is inherently lower-level compared to standard C library functions. |
| 228 | */ |
| 229 | OS_PROCESS_MEMORY os_process_memory(pid_t pid) { |
| 230 | OS_PROCESS_MEMORY proc_mem = OS_PROCESS_MEMORY_EMPTY; |
| 231 | HANDLE hProcess; |
| 232 | DWORD process_id = pid; |
| 233 | |
| 234 | if (process_id == 0) |
| 235 | process_id = GetCurrentProcessId(); |
| 236 | |
| 237 | // OpenProcess is a direct Windows API call - low level access to process |
| 238 | hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, process_id); |
| 239 | if (hProcess) { |
| 240 | // GetProcessMemoryInfo is a direct Windows API call to get memory info |
| 241 | PROCESS_MEMORY_COUNTERS_EX pmc; |
| 242 | ZeroMemory(&pmc, sizeof(pmc)); |
| 243 | pmc.cb = sizeof(pmc); |
| 244 | |
| 245 | if (GetProcessMemoryInfo(hProcess, (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) { |
| 246 | proc_mem.rss = pmc.WorkingSetSize; |
| 247 | proc_mem.max_rss = pmc.PeakWorkingSetSize; |
| 248 | proc_mem.virtual_size = pmc.PagefileUsage + pmc.WorkingSetSize; |
| 249 | |
| 250 | // Get module information to determine text and shared memory |
| 251 | // CreateToolhelp32Snapshot is a low-level Windows API call |
| 252 | HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, process_id); |
| 253 | if (hSnapshot != INVALID_HANDLE_VALUE) { |
| 254 | MODULEENTRY32 me; |
| 255 | ZeroMemory(&me, sizeof(me)); |
| 256 | me.dwSize = sizeof(MODULEENTRY32); |
| 257 | |
| 258 | // Module32First/Next are direct API calls to examine loaded modules |
| 259 | if (Module32First(hSnapshot, &me)) { |
| 260 | // The first module is the executable itself |
| 261 | proc_mem.text = me.modBaseSize; |
| 262 | |
| 263 | // Sum all other modules as shared code |
| 264 | while (Module32Next(hSnapshot, &me)) { |
| 265 | proc_mem.shared += me.modBaseSize; |
| 266 | } |
| 267 | } |
| 268 | CloseHandle(hSnapshot); |
| 269 | } |
| 270 | |
| 271 | // Get virtual memory information for more detailed breakdown |
| 272 | MEMORY_BASIC_INFORMATION mbi; |
| 273 | ZeroMemory(&mbi, sizeof(mbi)); |
| 274 | SIZE_T address = 0; |
| 275 | |
| 276 | // VirtualQueryEx is a low-level API to get memory region info |
| 277 | while (VirtualQueryEx(hProcess, (LPCVOID)address, &mbi, sizeof(mbi)) == sizeof(mbi)) { |
| 278 | if (mbi.State == MEM_COMMIT) { |
| 279 | if (mbi.Type == MEM_PRIVATE && !(mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY))) { |
| 280 | // Private data memory |
| 281 | proc_mem.data += mbi.RegionSize; |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | // Move to the next region |
| 286 | address = (SIZE_T)mbi.BaseAddress + mbi.RegionSize; |
| 287 | |
| 288 | // Avoid potential infinite loop on 64-bit systems |
| 289 | if (address < (SIZE_T)mbi.BaseAddress) |
| 290 | break; |
| 291 | } |
| 292 | |
| 293 | // If data counting failed, fall back to estimation |
| 294 | if (proc_mem.data == 0) |
| 295 | proc_mem.data = pmc.PrivateUsage - proc_mem.text; |
| 296 | } |
| 297 | CloseHandle(hProcess); |
| 298 | } |
| 299 | |
| 300 | if (OS_PROCESS_MEMORY_OK(proc_mem)) |
| 301 | last_process_memory_info = proc_mem; |
| 302 | |
| 303 | return proc_mem; |
| 304 | } |
| 305 | #endif |