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
13Q_DECLARE_LOGGING_CATEGORY(LOG_KMEMORYINFO)
14Q_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
45class KMemoryInfoPrivate : public QSharedData
46{
47public:
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
61KMemoryInfo::KMemoryInfo()
62 : d(new KMemoryInfoPrivate)
63{
64 update();
65}
66
67KMemoryInfo::~KMemoryInfo()
68{
69}
70
71KMemoryInfo::KMemoryInfo(const KMemoryInfo &other)
72 : d(other.d)
73{
74}
75
76KMemoryInfo &KMemoryInfo::operator=(const KMemoryInfo &other)
77{
78 d = other.d;
79 return *this;
80}
81
82bool 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
98bool KMemoryInfo::operator!=(const KMemoryInfo &other) const
99{
100 return !operator==(other);
101}
102
103bool KMemoryInfo::isNull() const
104{
105 return d->m_totalPhysical == 0;
106}
107
108quint64 KMemoryInfo::totalPhysical() const
109{
110 return d->m_totalPhysical;
111}
112
113quint64 KMemoryInfo::freePhysical() const
114{
115 return d->m_freePhysical;
116}
117
118quint64 KMemoryInfo::availablePhysical() const
119{
120 return d->m_availablePhysical;
121}
122
123quint64 KMemoryInfo::cached() const
124{
125 return d->m_cached;
126}
127
128quint64 KMemoryInfo::buffers() const
129{
130 return d->m_buffers;
131}
132
133quint64 KMemoryInfo::totalSwapFile() const
134{
135 return d->m_totalSwapFile;
136}
137
138quint64 KMemoryInfo::freeSwapFile() const
139{
140 return d->m_freeSwapFile;
141}
142
143#if defined(Q_OS_WINDOWS)
144/*****************************************************************************
145 * Windows
146 ****************************************************************************/
147
148struct SwapInfo {
149 quint64 totalPageFilePages = 0;
150 quint64 freePageFilePages = 0;
151};
152
153BOOL __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
164bool 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
199using ByteArrayView = QByteArrayView;
200
201bool extractBytes(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
223bool 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
288template<class T>
289bool sysctlread(const char *name, T &var)
290{
291 auto sz = sizeof(var);
292 return (sysctlbyname(name, &var, &sz, NULL, 0) == 0);
293}
294
295bool 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
352template<class T>
353bool sysctlread(const char *name, T &var)
354{
355 auto sz = sizeof(var);
356 return (sysctlbyname(name, &var, &sz, NULL, 0) == 0);
357}
358
359bool 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
438static 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
468bool 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
510bool KMemoryInfo::update()
511{
512 qCWarning(LOG_KMEMORYINFO) << "KMemoryInfo: unsupported platform!";
513 return false;
514}
515
516#endif
517

source code of kcoreaddons/src/lib/util/kmemoryinfo.cpp