master
c 305 lines 11.4 KB
Raw
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