1 | /* |
2 | This file is part of the KDE Frameworks |
3 | |
4 | SPDX-FileCopyrightText: 2022 Mirco Miranda |
5 | |
6 | SPDX-License-Identifier: LGPL-2.0-or-later |
7 | */ |
8 | #include "kmemoryinfo.h" |
9 | |
10 | #include <QLoggingCategory> |
11 | #include <QSharedData> |
12 | |
13 | Q_DECLARE_LOGGING_CATEGORY(LOG_KMEMORYINFO) |
14 | Q_LOGGING_CATEGORY(LOG_KMEMORYINFO, "kf.coreaddons.kmemoryinfo" , QtWarningMsg) |
15 | |
16 | // clang-format off |
17 | #if defined(Q_OS_WINDOWS) |
18 | #include <windows.h> // Windows.h must stay above Pspapi.h |
19 | #include <psapi.h> |
20 | #elif defined(Q_OS_LINUX) || defined(Q_OS_ANDROID) |
21 | #include <QByteArray> |
22 | #include <QFile> |
23 | #include <QByteArrayView> |
24 | #elif defined(Q_OS_MACOS) |
25 | #include <mach/mach.h> |
26 | #include <sys/sysctl.h> |
27 | #elif defined(Q_OS_FREEBSD) |
28 | #include <fcntl.h> |
29 | #include <kvm.h> |
30 | #include <sys/sysctl.h> |
31 | #elif defined(Q_OS_OPENBSD) |
32 | #include <sys/mount.h> |
33 | #include <sys/param.h> /* DEV_BSIZE PZERO */ |
34 | #include <sys/swap.h> |
35 | #include <sys/syscall.h> |
36 | #include <sys/sysctl.h> |
37 | |
38 | #include <stdio.h> |
39 | #include <stdlib.h> |
40 | #include <strings.h> |
41 | #include <unistd.h> |
42 | #endif |
43 | // clang-format on |
44 | |
45 | class KMemoryInfoPrivate : public QSharedData |
46 | { |
47 | public: |
48 | KMemoryInfoPrivate() |
49 | { |
50 | } |
51 | |
52 | quint64 m_totalPhysical = 0; |
53 | quint64 m_availablePhysical = 0; |
54 | quint64 m_freePhysical = 0; |
55 | quint64 m_totalSwapFile = 0; |
56 | quint64 m_freeSwapFile = 0; |
57 | quint64 m_cached = 0; |
58 | quint64 m_buffers = 0; |
59 | }; |
60 | |
61 | KMemoryInfo::KMemoryInfo() |
62 | : d(new KMemoryInfoPrivate) |
63 | { |
64 | update(); |
65 | } |
66 | |
67 | KMemoryInfo::~KMemoryInfo() |
68 | { |
69 | } |
70 | |
71 | KMemoryInfo::KMemoryInfo(const KMemoryInfo &other) |
72 | : d(other.d) |
73 | { |
74 | } |
75 | |
76 | KMemoryInfo &KMemoryInfo::operator=(const KMemoryInfo &other) |
77 | { |
78 | d = other.d; |
79 | return *this; |
80 | } |
81 | |
82 | bool KMemoryInfo::operator==(const KMemoryInfo &other) const |
83 | { |
84 | if (this == &other) { |
85 | return true; |
86 | } |
87 | // clang-format off |
88 | return (d->m_availablePhysical == other.d->m_availablePhysical |
89 | && d->m_freePhysical == other.d->m_freePhysical |
90 | && d->m_freeSwapFile == other.d->m_freeSwapFile |
91 | && d->m_cached == other.d->m_cached |
92 | && d->m_buffers == other.d->m_buffers |
93 | && d->m_totalSwapFile == other.d->m_totalSwapFile |
94 | && d->m_totalPhysical == other.d->m_totalPhysical); |
95 | // clang-format on |
96 | } |
97 | |
98 | bool KMemoryInfo::operator!=(const KMemoryInfo &other) const |
99 | { |
100 | return !operator==(other); |
101 | } |
102 | |
103 | bool KMemoryInfo::isNull() const |
104 | { |
105 | return d->m_totalPhysical == 0; |
106 | } |
107 | |
108 | quint64 KMemoryInfo::totalPhysical() const |
109 | { |
110 | return d->m_totalPhysical; |
111 | } |
112 | |
113 | quint64 KMemoryInfo::freePhysical() const |
114 | { |
115 | return d->m_freePhysical; |
116 | } |
117 | |
118 | quint64 KMemoryInfo::availablePhysical() const |
119 | { |
120 | return d->m_availablePhysical; |
121 | } |
122 | |
123 | quint64 KMemoryInfo::cached() const |
124 | { |
125 | return d->m_cached; |
126 | } |
127 | |
128 | quint64 KMemoryInfo::buffers() const |
129 | { |
130 | return d->m_buffers; |
131 | } |
132 | |
133 | quint64 KMemoryInfo::totalSwapFile() const |
134 | { |
135 | return d->m_totalSwapFile; |
136 | } |
137 | |
138 | quint64 KMemoryInfo::freeSwapFile() const |
139 | { |
140 | return d->m_freeSwapFile; |
141 | } |
142 | |
143 | #if defined(Q_OS_WINDOWS) |
144 | /***************************************************************************** |
145 | * Windows |
146 | ****************************************************************************/ |
147 | |
148 | struct SwapInfo { |
149 | quint64 totalPageFilePages = 0; |
150 | quint64 freePageFilePages = 0; |
151 | }; |
152 | |
153 | BOOL __stdcall pageInfo(LPVOID pContext, PENUM_PAGE_FILE_INFORMATION pPageFileInfo, LPCWSTR lpFilename) |
154 | { |
155 | Q_UNUSED(lpFilename) |
156 | if (auto sw = static_cast<SwapInfo *>(pContext)) { |
157 | sw->totalPageFilePages += pPageFileInfo->TotalSize; |
158 | sw->freePageFilePages += (pPageFileInfo->TotalSize - pPageFileInfo->TotalInUse); |
159 | return true; |
160 | } |
161 | return false; |
162 | } |
163 | |
164 | bool KMemoryInfo::update() |
165 | { |
166 | MEMORYSTATUSEX statex; |
167 | statex.dwLength = sizeof(statex); |
168 | if (!GlobalMemoryStatusEx(&statex)) { |
169 | return false; |
170 | } |
171 | |
172 | PERFORMANCE_INFORMATION pi; |
173 | DWORD pisz = sizeof(pi); |
174 | if (!GetPerformanceInfo(&pi, pisz)) { |
175 | return false; |
176 | } |
177 | |
178 | SwapInfo si; |
179 | if (!EnumPageFiles(pageInfo, &si)) { |
180 | return false; |
181 | } |
182 | |
183 | d->m_totalPhysical = statex.ullTotalPhys; |
184 | d->m_availablePhysical = statex.ullAvailPhys; |
185 | d->m_freePhysical = statex.ullAvailPhys; |
186 | d->m_totalSwapFile = si.totalPageFilePages * pi.PageSize; |
187 | d->m_freeSwapFile = si.freePageFilePages * pi.PageSize; |
188 | d->m_cached = pi.SystemCache * pi.PageSize; |
189 | d->m_buffers = 0; |
190 | |
191 | return true; |
192 | } |
193 | |
194 | #elif defined(Q_OS_LINUX) || defined(Q_OS_ANDROID) |
195 | /***************************************************************************** |
196 | * GNU/Linux |
197 | ****************************************************************************/ |
198 | |
199 | using ByteArrayView = QByteArrayView; |
200 | |
201 | bool (quint64 &value, const QByteArray &buffer, const ByteArrayView &beginPattern, qsizetype &from) |
202 | { |
203 | ByteArrayView endPattern("kB" ); |
204 | auto beginIdx = buffer.indexOf(bv: beginPattern, from); |
205 | if (beginIdx > -1) { |
206 | auto start = beginIdx + beginPattern.size(); |
207 | auto endIdx = buffer.indexOf(bv: endPattern, from: start); |
208 | if (endIdx > -1) { |
209 | from = endIdx + endPattern.size(); |
210 | auto ok = false; |
211 | value = buffer.mid(index: start, len: endIdx - start).toULongLong(ok: &ok) * 1024; |
212 | return ok; |
213 | } |
214 | } |
215 | if (from) { // Wrong order? Restart from the beginning |
216 | qCWarning(LOG_KMEMORYINFO) << "KMemoryInfo: extractBytes: wrong order when extracting" << beginPattern; |
217 | from = 0; |
218 | return extractBytes(value, buffer, beginPattern, from); |
219 | } |
220 | return false; |
221 | } |
222 | |
223 | bool KMemoryInfo::update() |
224 | { |
225 | QFile file(QStringLiteral("/proc/meminfo" )); |
226 | if (!file.open(flags: QFile::ReadOnly)) { |
227 | return false; |
228 | } |
229 | auto meminfo = file.readAll(); |
230 | file.close(); |
231 | |
232 | qsizetype miFrom = 0; |
233 | quint64 totalPhys = 0; |
234 | if (!extractBytes(value&: totalPhys, buffer: meminfo, beginPattern: "MemTotal:" , from&: miFrom)) { |
235 | return false; |
236 | } |
237 | quint64 freePhys = 0; |
238 | if (!extractBytes(value&: freePhys, buffer: meminfo, beginPattern: "MemFree:" , from&: miFrom)) { |
239 | return false; |
240 | } |
241 | quint64 availPhys = 0; |
242 | if (!extractBytes(value&: availPhys, buffer: meminfo, beginPattern: "MemAvailable:" , from&: miFrom)) { |
243 | return false; |
244 | } |
245 | quint64 buffers = 0; |
246 | if (!extractBytes(value&: buffers, buffer: meminfo, beginPattern: "Buffers:" , from&: miFrom)) { |
247 | return false; |
248 | } |
249 | quint64 cached = 0; |
250 | if (!extractBytes(value&: cached, buffer: meminfo, beginPattern: "Cached:" , from&: miFrom)) { |
251 | return false; |
252 | } |
253 | quint64 swapTotal = 0; |
254 | if (!extractBytes(value&: swapTotal, buffer: meminfo, beginPattern: "SwapTotal:" , from&: miFrom)) { |
255 | return false; |
256 | } |
257 | quint64 swapFree = 0; |
258 | if (!extractBytes(value&: swapFree, buffer: meminfo, beginPattern: "SwapFree:" , from&: miFrom)) { |
259 | return false; |
260 | } |
261 | quint64 sharedMem = 0; |
262 | if (!extractBytes(value&: sharedMem, buffer: meminfo, beginPattern: "Shmem:" , from&: miFrom)) { |
263 | return false; |
264 | } |
265 | quint64 sReclaimable = 0; |
266 | if (!extractBytes(value&: sReclaimable, buffer: meminfo, beginPattern: "SReclaimable:" , from&: miFrom)) { |
267 | return false; |
268 | } |
269 | |
270 | // Source HTOP: https://github.com/htop-dev/htop/blob/main/linux/LinuxProcessList.c |
271 | d->m_totalPhysical = totalPhys; |
272 | // NOTE: another viable solution: d->m_availablePhysical = std::min(availPhys, totalPhys - (committedAs - cached - (swapTotal - swapFree))) |
273 | d->m_availablePhysical = availPhys ? std::min(a: availPhys, b: totalPhys) : freePhys; |
274 | d->m_freePhysical = freePhys; |
275 | d->m_totalSwapFile = swapTotal; |
276 | d->m_freeSwapFile = swapFree; |
277 | d->m_cached = cached + sReclaimable - sharedMem; |
278 | d->m_buffers = buffers; |
279 | |
280 | return true; |
281 | } |
282 | |
283 | #elif defined(Q_OS_MACOS) |
284 | /***************************************************************************** |
285 | * macOS |
286 | ****************************************************************************/ |
287 | |
288 | template<class T> |
289 | bool sysctlread(const char *name, T &var) |
290 | { |
291 | auto sz = sizeof(var); |
292 | return (sysctlbyname(name, &var, &sz, NULL, 0) == 0); |
293 | } |
294 | |
295 | bool KMemoryInfo::update() |
296 | { |
297 | quint64 memSize = 0; |
298 | quint64 pageSize = 0; |
299 | xsw_usage swapUsage; |
300 | |
301 | int mib[2]; |
302 | size_t sz = 0; |
303 | |
304 | mib[0] = CTL_HW; |
305 | mib[1] = HW_MEMSIZE; |
306 | sz = sizeof(memSize); |
307 | if (sysctl(mib, 2, &memSize, &sz, NULL, 0) != KERN_SUCCESS) { |
308 | return false; |
309 | } |
310 | |
311 | mib[0] = CTL_HW; |
312 | mib[1] = HW_PAGESIZE; |
313 | sz = sizeof(pageSize); |
314 | if (sysctl(mib, 2, &pageSize, &sz, NULL, 0) != KERN_SUCCESS) { |
315 | return false; |
316 | } |
317 | |
318 | mib[0] = CTL_VM; |
319 | mib[1] = VM_SWAPUSAGE; |
320 | sz = sizeof(swapUsage); |
321 | if (sysctl(mib, 2, &swapUsage, &sz, NULL, 0) != KERN_SUCCESS) { |
322 | return false; |
323 | } |
324 | |
325 | quint64 zfs_arcstats_size = 0; |
326 | if (!sysctlread("kstat.zfs.misc.arcstats.size" , zfs_arcstats_size)) { |
327 | zfs_arcstats_size = 0; // no ZFS used |
328 | } |
329 | |
330 | mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; |
331 | vm_statistics64_data_t vmstat; |
332 | if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vmstat, &count) != KERN_SUCCESS) { |
333 | return false; |
334 | } |
335 | |
336 | d->m_totalPhysical = memSize; |
337 | d->m_availablePhysical = memSize - (vmstat.internal_page_count + vmstat.compressor_page_count + vmstat.wire_count) * pageSize; |
338 | d->m_freePhysical = vmstat.free_count * pageSize; |
339 | d->m_totalSwapFile = swapUsage.xsu_total; |
340 | d->m_freeSwapFile = swapUsage.xsu_avail; |
341 | d->m_cached = vmstat.external_page_count * pageSize + zfs_arcstats_size; |
342 | d->m_buffers = 0; |
343 | |
344 | return true; |
345 | } |
346 | |
347 | #elif defined(Q_OS_FREEBSD) |
348 | /***************************************************************************** |
349 | * FreeBSD |
350 | ****************************************************************************/ |
351 | |
352 | template<class T> |
353 | bool sysctlread(const char *name, T &var) |
354 | { |
355 | auto sz = sizeof(var); |
356 | return (sysctlbyname(name, &var, &sz, NULL, 0) == 0); |
357 | } |
358 | |
359 | bool KMemoryInfo::update() |
360 | { |
361 | quint64 memSize = 0; |
362 | quint64 pageSize = 0; |
363 | |
364 | int mib[4]; |
365 | size_t sz = 0; |
366 | |
367 | mib[0] = CTL_HW; |
368 | mib[1] = HW_PHYSMEM; |
369 | sz = sizeof(memSize); |
370 | if (sysctl(mib, 2, &memSize, &sz, NULL, 0) != 0) { |
371 | return false; |
372 | } |
373 | |
374 | mib[0] = CTL_HW; |
375 | mib[1] = HW_PAGESIZE; |
376 | sz = sizeof(pageSize); |
377 | if (sysctl(mib, 2, &pageSize, &sz, NULL, 0) != 0) { |
378 | return false; |
379 | } |
380 | |
381 | quint32 v_pageSize = 0; |
382 | if (sysctlread("vm.stats.vm.v_page_size" , v_pageSize)) { |
383 | pageSize = v_pageSize; |
384 | } |
385 | quint64 zfs_arcstats_size = 0; |
386 | if (!sysctlread("kstat.zfs.misc.arcstats.size" , zfs_arcstats_size)) { |
387 | zfs_arcstats_size = 0; // no ZFS used |
388 | } |
389 | quint32 v_cache_count = 0; |
390 | if (!sysctlread("vm.stats.vm.v_cache_count" , v_cache_count)) { |
391 | return false; |
392 | } |
393 | quint32 v_inactive_count = 0; |
394 | if (!sysctlread("vm.stats.vm.v_inactive_count" , v_inactive_count)) { |
395 | return false; |
396 | } |
397 | quint32 v_free_count = 0; |
398 | if (!sysctlread("vm.stats.vm.v_free_count" , v_free_count)) { |
399 | return false; |
400 | } |
401 | quint64 vfs_bufspace = 0; |
402 | if (!sysctlread("vfs.bufspace" , vfs_bufspace)) { |
403 | return false; |
404 | } |
405 | |
406 | quint64 swap_tot = 0; |
407 | quint64 swap_free = 0; |
408 | if (auto kd = kvm_open("/dev/null" , "/dev/null" , "/dev/null" , O_RDONLY, "kvm_open" )) { |
409 | struct kvm_swap swap; |
410 | // if you specify a maxswap value of 1, the function will typically return the |
411 | // value 0 and the single kvm_swap structure will be filled with the grand total over all swap devices. |
412 | auto nswap = kvm_getswapinfo(kd, &swap, 1, 0); |
413 | if (nswap == 0) { |
414 | swap_tot = swap.ksw_total; |
415 | swap_free = swap.ksw_used; |
416 | } |
417 | swap_free = (swap_tot - swap_free) * pageSize; |
418 | swap_tot *= pageSize; |
419 | } |
420 | |
421 | // Source HTOP: https://github.com/htop-dev/htop/blob/main/freebsd/FreeBSDProcessList.c |
422 | d->m_totalPhysical = memSize; |
423 | d->m_availablePhysical = pageSize * (v_cache_count + v_free_count + v_inactive_count) + vfs_bufspace + zfs_arcstats_size; |
424 | d->m_freePhysical = pageSize * v_free_count; |
425 | d->m_totalSwapFile = swap_tot; |
426 | d->m_freeSwapFile = swap_free; |
427 | d->m_cached = pageSize * v_cache_count + zfs_arcstats_size; |
428 | d->m_buffers = vfs_bufspace; |
429 | |
430 | return true; |
431 | } |
432 | |
433 | #elif defined(Q_OS_OPENBSD) |
434 | /***************************************************************************** |
435 | * OpenBSD |
436 | ****************************************************************************/ |
437 | // From src/usr.bin/top/machine.c |
438 | static int swap_usage(int *used, int *total) |
439 | { |
440 | struct swapent *swdev; |
441 | int nswap, rnswap, i; |
442 | |
443 | nswap = swapctl(SWAP_NSWAP, nullptr, 0); |
444 | if (nswap == 0) |
445 | return 0; |
446 | |
447 | swdev = static_cast<struct swapent *>(calloc(nswap, sizeof(*swdev))); |
448 | if (swdev == NULL) |
449 | return 0; |
450 | |
451 | rnswap = swapctl(SWAP_STATS, swdev, nswap); |
452 | if (rnswap == -1) { |
453 | free(swdev); |
454 | return 0; |
455 | } |
456 | /* Total things up */ |
457 | *total = *used = 0; |
458 | for (i = 0; i < nswap; i++) { |
459 | if (swdev[i].se_flags & SWF_ENABLE) { |
460 | *used += (swdev[i].se_inuse / (1024 / DEV_BSIZE)); |
461 | *total += (swdev[i].se_nblks / (1024 / DEV_BSIZE)); |
462 | } |
463 | } |
464 | free(swdev); |
465 | return 1; |
466 | } |
467 | |
468 | bool KMemoryInfo::update() |
469 | { |
470 | // TODO: compute m_availablePhysical on OpenBSD |
471 | |
472 | // tota phsycial memory |
473 | const long phys_pages = sysconf(_SC_PHYS_PAGES); |
474 | const long pagesize = sysconf(_SC_PAGESIZE); |
475 | if (phys_pages != -1 && pagesize != -1) |
476 | d->m_totalPhysical = ((uint64_t)phys_pages * (uint64_t)pagesize / 1024); |
477 | |
478 | int swap_free = 0; |
479 | int swap_tot = 0; |
480 | if (swap_usage(&swap_free, &swap_tot)) { |
481 | d->m_totalSwapFile = swap_tot; |
482 | d->m_freeSwapFile = swap_free; |
483 | } |
484 | |
485 | int uvmexp_mib[] = {CTL_VM, VM_UVMEXP}; |
486 | struct uvmexp uvmexp; |
487 | size_t size = sizeof(uvmexp); |
488 | if (sysctl(uvmexp_mib, 2, &uvmexp, &size, NULL, 0) == -1) { |
489 | bzero(&uvmexp, sizeof(uvmexp)); |
490 | return false; |
491 | } |
492 | d->m_freePhysical = uvmexp.free * pagesize / 1024; |
493 | |
494 | int bcstats_mib[] = {CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT}; |
495 | struct bcachestats bcstats; |
496 | size = sizeof(bcstats); |
497 | if (sysctl(bcstats_mib, 3, &bcstats, &size, NULL, 0) == -1) { |
498 | bzero(&bcstats, sizeof(bcstats)); |
499 | return false; |
500 | } |
501 | d->m_cached = bcstats.numbufpages * pagesize / 1024; |
502 | |
503 | return true; |
504 | } |
505 | #else |
506 | /***************************************************************************** |
507 | * Unsupported platform |
508 | ****************************************************************************/ |
509 | |
510 | bool KMemoryInfo::update() |
511 | { |
512 | qCWarning(LOG_KMEMORYINFO) << "KMemoryInfo: unsupported platform!" ; |
513 | return false; |
514 | } |
515 | |
516 | #endif |
517 | |