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#endif
32// clang-format on
33
34class KMemoryInfoPrivate : public QSharedData
35{
36public:
37 KMemoryInfoPrivate()
38 {
39 }
40
41 quint64 m_totalPhysical = 0;
42 quint64 m_availablePhysical = 0;
43 quint64 m_freePhysical = 0;
44 quint64 m_totalSwapFile = 0;
45 quint64 m_freeSwapFile = 0;
46 quint64 m_cached = 0;
47 quint64 m_buffers = 0;
48};
49
50KMemoryInfo::KMemoryInfo()
51 : d(new KMemoryInfoPrivate)
52{
53 update();
54}
55
56KMemoryInfo::~KMemoryInfo()
57{
58}
59
60KMemoryInfo::KMemoryInfo(const KMemoryInfo &other)
61 : d(other.d)
62{
63}
64
65KMemoryInfo &KMemoryInfo::operator=(const KMemoryInfo &other)
66{
67 d = other.d;
68 return *this;
69}
70
71bool KMemoryInfo::operator==(const KMemoryInfo &other) const
72{
73 if (this == &other) {
74 return true;
75 }
76 // clang-format off
77 return (d->m_availablePhysical == other.d->m_availablePhysical
78 && d->m_freePhysical == other.d->m_freePhysical
79 && d->m_freeSwapFile == other.d->m_freeSwapFile
80 && d->m_cached == other.d->m_cached
81 && d->m_buffers == other.d->m_buffers
82 && d->m_totalSwapFile == other.d->m_totalSwapFile
83 && d->m_totalPhysical == other.d->m_totalPhysical);
84 // clang-format on
85}
86
87bool KMemoryInfo::operator!=(const KMemoryInfo &other) const
88{
89 return !operator==(other);
90}
91
92bool KMemoryInfo::isNull() const
93{
94 return d->m_totalPhysical == 0;
95}
96
97quint64 KMemoryInfo::totalPhysical() const
98{
99 return d->m_totalPhysical;
100}
101
102quint64 KMemoryInfo::freePhysical() const
103{
104 return d->m_freePhysical;
105}
106
107quint64 KMemoryInfo::availablePhysical() const
108{
109 return d->m_availablePhysical;
110}
111
112quint64 KMemoryInfo::cached() const
113{
114 return d->m_cached;
115}
116
117quint64 KMemoryInfo::buffers() const
118{
119 return d->m_buffers;
120}
121
122quint64 KMemoryInfo::totalSwapFile() const
123{
124 return d->m_totalSwapFile;
125}
126
127quint64 KMemoryInfo::freeSwapFile() const
128{
129 return d->m_freeSwapFile;
130}
131
132#if defined(Q_OS_WINDOWS)
133/*****************************************************************************
134 * Windows
135 ****************************************************************************/
136
137struct SwapInfo {
138 quint64 totalPageFilePages = 0;
139 quint64 freePageFilePages = 0;
140};
141
142BOOL __stdcall pageInfo(LPVOID pContext, PENUM_PAGE_FILE_INFORMATION pPageFileInfo, LPCWSTR lpFilename)
143{
144 Q_UNUSED(lpFilename)
145 if (auto sw = static_cast<SwapInfo *>(pContext)) {
146 sw->totalPageFilePages += pPageFileInfo->TotalSize;
147 sw->freePageFilePages += (pPageFileInfo->TotalSize - pPageFileInfo->TotalInUse);
148 return true;
149 }
150 return false;
151}
152
153bool KMemoryInfo::update()
154{
155 MEMORYSTATUSEX statex;
156 statex.dwLength = sizeof(statex);
157 if (!GlobalMemoryStatusEx(&statex)) {
158 return false;
159 }
160
161 PERFORMANCE_INFORMATION pi;
162 DWORD pisz = sizeof(pi);
163 if (!GetPerformanceInfo(&pi, pisz)) {
164 return false;
165 }
166
167 SwapInfo si;
168 if (!EnumPageFiles(pageInfo, &si)) {
169 return false;
170 }
171
172 d->m_totalPhysical = statex.ullTotalPhys;
173 d->m_availablePhysical = statex.ullAvailPhys;
174 d->m_freePhysical = statex.ullAvailPhys;
175 d->m_totalSwapFile = si.totalPageFilePages * pi.PageSize;
176 d->m_freeSwapFile = si.freePageFilePages * pi.PageSize;
177 d->m_cached = pi.SystemCache * pi.PageSize;
178 d->m_buffers = 0;
179
180 return true;
181}
182
183#elif defined(Q_OS_LINUX) || defined(Q_OS_ANDROID)
184/*****************************************************************************
185 * GNU/Linux
186 ****************************************************************************/
187
188using ByteArrayView = QByteArrayView;
189
190bool extractBytes(quint64 &value, const QByteArray &buffer, const ByteArrayView &beginPattern, qsizetype &from)
191{
192 ByteArrayView endPattern("kB");
193 auto beginIdx = buffer.indexOf(beginPattern, from);
194 if (beginIdx > -1) {
195 auto start = beginIdx + beginPattern.size();
196 auto endIdx = buffer.indexOf(endPattern, start);
197 if (endIdx > -1) {
198 from = endIdx + endPattern.size();
199 auto ok = false;
200 value = buffer.mid(start, endIdx - start).toULongLong(&ok) * 1024;
201 return ok;
202 }
203 }
204 if (from) { // Wrong order? Restart from the beginning
205 qCWarning(LOG_KMEMORYINFO) << "KMemoryInfo: extractBytes: wrong order when extracting" << beginPattern;
206 from = 0;
207 return extractBytes(value, buffer, beginPattern, from);
208 }
209 return false;
210}
211
212bool KMemoryInfo::update()
213{
214 QFile file(QStringLiteral("/proc/meminfo"));
215 if (!file.open(QFile::ReadOnly)) {
216 return false;
217 }
218 auto meminfo = file.readAll();
219 file.close();
220
221 qsizetype miFrom = 0;
222 quint64 totalPhys = 0;
223 if (!extractBytes(totalPhys, meminfo, "MemTotal:", miFrom)) {
224 return false;
225 }
226 quint64 freePhys = 0;
227 if (!extractBytes(freePhys, meminfo, "MemFree:", miFrom)) {
228 return false;
229 }
230 quint64 availPhys = 0;
231 if (!extractBytes(availPhys, meminfo, "MemAvailable:", miFrom)) {
232 return false;
233 }
234 quint64 buffers = 0;
235 if (!extractBytes(buffers, meminfo, "Buffers:", miFrom)) {
236 return false;
237 }
238 quint64 cached = 0;
239 if (!extractBytes(cached, meminfo, "Cached:", miFrom)) {
240 return false;
241 }
242 quint64 swapTotal = 0;
243 if (!extractBytes(swapTotal, meminfo, "SwapTotal:", miFrom)) {
244 return false;
245 }
246 quint64 swapFree = 0;
247 if (!extractBytes(swapFree, meminfo, "SwapFree:", miFrom)) {
248 return false;
249 }
250 quint64 sharedMem = 0;
251 if (!extractBytes(sharedMem, meminfo, "Shmem:", miFrom)) {
252 return false;
253 }
254 quint64 sReclaimable = 0;
255 if (!extractBytes(sReclaimable, meminfo, "SReclaimable:", miFrom)) {
256 return false;
257 }
258
259 // Source HTOP: https://github.com/htop-dev/htop/blob/main/linux/LinuxProcessList.c
260 d->m_totalPhysical = totalPhys;
261 // NOTE: another viable solution: d->m_availablePhysical = std::min(availPhys, totalPhys - (committedAs - cached - (swapTotal - swapFree)))
262 d->m_availablePhysical = availPhys ? std::min(availPhys, totalPhys) : freePhys;
263 d->m_freePhysical = freePhys;
264 d->m_totalSwapFile = swapTotal;
265 d->m_freeSwapFile = swapFree;
266 d->m_cached = cached + sReclaimable - sharedMem;
267 d->m_buffers = buffers;
268
269 return true;
270}
271
272#elif defined(Q_OS_MACOS)
273/*****************************************************************************
274 * macOS
275 ****************************************************************************/
276
277template<class T>
278bool sysctlread(const char *name, T &var)
279{
280 auto sz = sizeof(var);
281 return (sysctlbyname(name, &var, &sz, NULL, 0) == 0);
282}
283
284bool KMemoryInfo::update()
285{
286 quint64 memSize = 0;
287 quint64 pageSize = 0;
288 xsw_usage swapUsage;
289
290 int mib[2];
291 size_t sz = 0;
292
293 mib[0] = CTL_HW;
294 mib[1] = HW_MEMSIZE;
295 sz = sizeof(memSize);
296 if (sysctl(mib, 2, &memSize, &sz, NULL, 0) != KERN_SUCCESS) {
297 return false;
298 }
299
300 mib[0] = CTL_HW;
301 mib[1] = HW_PAGESIZE;
302 sz = sizeof(pageSize);
303 if (sysctl(mib, 2, &pageSize, &sz, NULL, 0) != KERN_SUCCESS) {
304 return false;
305 }
306
307 mib[0] = CTL_VM;
308 mib[1] = VM_SWAPUSAGE;
309 sz = sizeof(swapUsage);
310 if (sysctl(mib, 2, &swapUsage, &sz, NULL, 0) != KERN_SUCCESS) {
311 return false;
312 }
313
314 quint64 zfs_arcstats_size = 0;
315 if (!sysctlread("kstat.zfs.misc.arcstats.size", zfs_arcstats_size)) {
316 zfs_arcstats_size = 0; // no ZFS used
317 }
318
319 mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
320 vm_statistics64_data_t vmstat;
321 if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vmstat, &count) != KERN_SUCCESS) {
322 return false;
323 }
324
325 d->m_totalPhysical = memSize;
326 d->m_availablePhysical = memSize - (vmstat.internal_page_count + vmstat.compressor_page_count + vmstat.wire_count) * pageSize;
327 d->m_freePhysical = vmstat.free_count * pageSize;
328 d->m_totalSwapFile = swapUsage.xsu_total;
329 d->m_freeSwapFile = swapUsage.xsu_avail;
330 d->m_cached = vmstat.external_page_count * pageSize + zfs_arcstats_size;
331 d->m_buffers = 0;
332
333 return true;
334}
335
336#elif defined(Q_OS_FREEBSD)
337/*****************************************************************************
338 * FreeBSD
339 ****************************************************************************/
340
341template<class T>
342bool sysctlread(const char *name, T &var)
343{
344 auto sz = sizeof(var);
345 return (sysctlbyname(name, &var, &sz, NULL, 0) == 0);
346}
347
348bool KMemoryInfo::update()
349{
350 quint64 memSize = 0;
351 quint64 pageSize = 0;
352
353 int mib[4];
354 size_t sz = 0;
355
356 mib[0] = CTL_HW;
357 mib[1] = HW_PHYSMEM;
358 sz = sizeof(memSize);
359 if (sysctl(mib, 2, &memSize, &sz, NULL, 0) != 0) {
360 return false;
361 }
362
363 mib[0] = CTL_HW;
364 mib[1] = HW_PAGESIZE;
365 sz = sizeof(pageSize);
366 if (sysctl(mib, 2, &pageSize, &sz, NULL, 0) != 0) {
367 return false;
368 }
369
370 quint32 v_pageSize = 0;
371 if (sysctlread("vm.stats.vm.v_page_size", v_pageSize)) {
372 pageSize = v_pageSize;
373 }
374 quint64 zfs_arcstats_size = 0;
375 if (!sysctlread("kstat.zfs.misc.arcstats.size", zfs_arcstats_size)) {
376 zfs_arcstats_size = 0; // no ZFS used
377 }
378 quint32 v_cache_count = 0;
379 if (!sysctlread("vm.stats.vm.v_cache_count", v_cache_count)) {
380 return false;
381 }
382 quint32 v_inactive_count = 0;
383 if (!sysctlread("vm.stats.vm.v_inactive_count", v_inactive_count)) {
384 return false;
385 }
386 quint32 v_free_count = 0;
387 if (!sysctlread("vm.stats.vm.v_free_count", v_free_count)) {
388 return false;
389 }
390 quint64 vfs_bufspace = 0;
391 if (!sysctlread("vfs.bufspace", vfs_bufspace)) {
392 return false;
393 }
394
395 quint64 swap_tot = 0;
396 quint64 swap_free = 0;
397 if (auto kd = kvm_open("/dev/null", "/dev/null", "/dev/null", O_RDONLY, "kvm_open")) {
398 struct kvm_swap swap;
399 // if you specify a maxswap value of 1, the function will typically return the
400 // value 0 and the single kvm_swap structure will be filled with the grand total over all swap devices.
401 auto nswap = kvm_getswapinfo(kd, &swap, 1, 0);
402 if (nswap == 0) {
403 swap_tot = swap.ksw_total;
404 swap_free = swap.ksw_used;
405 }
406 swap_free = (swap_tot - swap_free) * pageSize;
407 swap_tot *= pageSize;
408 }
409
410 // Source HTOP: https://github.com/htop-dev/htop/blob/main/freebsd/FreeBSDProcessList.c
411 d->m_totalPhysical = memSize;
412 d->m_availablePhysical = pageSize * (v_cache_count + v_free_count + v_inactive_count) + vfs_bufspace + zfs_arcstats_size;
413 d->m_freePhysical = pageSize * v_free_count;
414 d->m_totalSwapFile = swap_tot;
415 d->m_freeSwapFile = swap_free;
416 d->m_cached = pageSize * v_cache_count + zfs_arcstats_size;
417 d->m_buffers = vfs_bufspace;
418
419 return true;
420}
421
422#else
423/*****************************************************************************
424 * Unsupported platform
425 ****************************************************************************/
426
427bool KMemoryInfo::update()
428{
429 qCWarning(LOG_KMEMORYINFO) << "KMemoryInfo: unsupported platform!";
430 return false;
431}
432
433#endif
434

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