KCoreAddons

kmemoryinfo.cpp
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
62 : d(new KMemoryInfoPrivate)
63{
64 update();
65}
66
67KMemoryInfo::~KMemoryInfo()
68{
69}
70
72 : d(other.d)
73{
74}
75
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
104{
105 return d->m_totalPhysical == 0;
106}
107
109{
110 return d->m_totalPhysical;
111}
112
114{
115 return d->m_freePhysical;
116}
117
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
134{
135 return d->m_totalSwapFile;
136}
137
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(beginPattern, from);
205 if (beginIdx > -1) {
206 auto start = beginIdx + beginPattern.size();
207 auto endIdx = buffer.indexOf(endPattern, start);
208 if (endIdx > -1) {
209 from = endIdx + endPattern.size();
210 auto ok = false;
211 value = buffer.mid(start, endIdx - start).toULongLong(&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(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(totalPhys, meminfo, "MemTotal:", miFrom)) {
235 return false;
236 }
237 quint64 freePhys = 0;
238 if (!extractBytes(freePhys, meminfo, "MemFree:", miFrom)) {
239 return false;
240 }
241 quint64 availPhys = 0;
242 if (!extractBytes(availPhys, meminfo, "MemAvailable:", miFrom)) {
243 return false;
244 }
245 quint64 buffers = 0;
246 if (!extractBytes(buffers, meminfo, "Buffers:", miFrom)) {
247 return false;
248 }
249 quint64 cached = 0;
250 if (!extractBytes(cached, meminfo, "Cached:", miFrom)) {
251 return false;
252 }
253 quint64 swapTotal = 0;
254 if (!extractBytes(swapTotal, meminfo, "SwapTotal:", miFrom)) {
255 return false;
256 }
257 quint64 swapFree = 0;
258 if (!extractBytes(swapFree, meminfo, "SwapFree:", miFrom)) {
259 return false;
260 }
261 quint64 sharedMem = 0;
262 if (!extractBytes(sharedMem, meminfo, "Shmem:", miFrom)) {
263 return false;
264 }
265 quint64 sReclaimable = 0;
266 if (!extractBytes(sReclaimable, meminfo, "SReclaimable:", 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(availPhys, 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
The KMemoryInfo class provides an interface to get memory information (RAM/SWAP).
Definition kmemoryinfo.h:31
bool operator==(const KMemoryInfo &other) const
operator ==
quint64 freeSwapFile() const
freeSwapFile
quint64 freePhysical() const
freePhysical
quint64 cached() const
cached
KMemoryInfo & operator=(const KMemoryInfo &other)
operator = Makes a copy of the other memoryinfo and returns a reference to the copy.
quint64 totalSwapFile() const
totalSwapFile
quint64 buffers() const
buffers
bool isNull() const
isNull
bool operator!=(const KMemoryInfo &other) const
operator !=
quint64 availablePhysical() const
availablePhysical
KMemoryInfo()
KMemoryInfo Constructs a class with a snapshot of the state of the memory.
quint64 totalPhysical() const
totalPhysical
Q_SCRIPTABLE Q_NOREPLY void start()
KREPORT_EXPORT QPageSize::PageSizeId pageSize(const QString &key)
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
QByteArray mid(qsizetype pos, qsizetype len) const const
qulonglong toULongLong(bool *ok, int base) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jun 7 2024 12:01:00 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.