File indexing completed on 2024-05-19 03:56:24
0001 /* 0002 This file is part of the KDE Frameworks 0003 0004 SPDX-FileCopyrightText: 2022 Mirco Miranda 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 #include "kmemoryinfo.h" 0009 0010 #include <QLoggingCategory> 0011 #include <QSharedData> 0012 0013 Q_DECLARE_LOGGING_CATEGORY(LOG_KMEMORYINFO) 0014 Q_LOGGING_CATEGORY(LOG_KMEMORYINFO, "kf.coreaddons.kmemoryinfo", QtWarningMsg) 0015 0016 // clang-format off 0017 #if defined(Q_OS_WINDOWS) 0018 #include <windows.h> // Windows.h must stay above Pspapi.h 0019 #include <psapi.h> 0020 #elif defined(Q_OS_LINUX) || defined(Q_OS_ANDROID) 0021 #include <QByteArray> 0022 #include <QFile> 0023 #include <QByteArrayView> 0024 #elif defined(Q_OS_MACOS) 0025 #include <mach/mach.h> 0026 #include <sys/sysctl.h> 0027 #elif defined(Q_OS_FREEBSD) 0028 #include <fcntl.h> 0029 #include <kvm.h> 0030 #include <sys/sysctl.h> 0031 #elif defined(Q_OS_OPENBSD) 0032 #include <sys/mount.h> 0033 #include <sys/param.h> /* DEV_BSIZE PZERO */ 0034 #include <sys/swap.h> 0035 #include <sys/syscall.h> 0036 #include <sys/sysctl.h> 0037 0038 #include <stdio.h> 0039 #include <stdlib.h> 0040 #include <strings.h> 0041 #include <unistd.h> 0042 #endif 0043 // clang-format on 0044 0045 class KMemoryInfoPrivate : public QSharedData 0046 { 0047 public: 0048 KMemoryInfoPrivate() 0049 { 0050 } 0051 0052 quint64 m_totalPhysical = 0; 0053 quint64 m_availablePhysical = 0; 0054 quint64 m_freePhysical = 0; 0055 quint64 m_totalSwapFile = 0; 0056 quint64 m_freeSwapFile = 0; 0057 quint64 m_cached = 0; 0058 quint64 m_buffers = 0; 0059 }; 0060 0061 KMemoryInfo::KMemoryInfo() 0062 : d(new KMemoryInfoPrivate) 0063 { 0064 update(); 0065 } 0066 0067 KMemoryInfo::~KMemoryInfo() 0068 { 0069 } 0070 0071 KMemoryInfo::KMemoryInfo(const KMemoryInfo &other) 0072 : d(other.d) 0073 { 0074 } 0075 0076 KMemoryInfo &KMemoryInfo::operator=(const KMemoryInfo &other) 0077 { 0078 d = other.d; 0079 return *this; 0080 } 0081 0082 bool KMemoryInfo::operator==(const KMemoryInfo &other) const 0083 { 0084 if (this == &other) { 0085 return true; 0086 } 0087 // clang-format off 0088 return (d->m_availablePhysical == other.d->m_availablePhysical 0089 && d->m_freePhysical == other.d->m_freePhysical 0090 && d->m_freeSwapFile == other.d->m_freeSwapFile 0091 && d->m_cached == other.d->m_cached 0092 && d->m_buffers == other.d->m_buffers 0093 && d->m_totalSwapFile == other.d->m_totalSwapFile 0094 && d->m_totalPhysical == other.d->m_totalPhysical); 0095 // clang-format on 0096 } 0097 0098 bool KMemoryInfo::operator!=(const KMemoryInfo &other) const 0099 { 0100 return !operator==(other); 0101 } 0102 0103 bool KMemoryInfo::isNull() const 0104 { 0105 return d->m_totalPhysical == 0; 0106 } 0107 0108 quint64 KMemoryInfo::totalPhysical() const 0109 { 0110 return d->m_totalPhysical; 0111 } 0112 0113 quint64 KMemoryInfo::freePhysical() const 0114 { 0115 return d->m_freePhysical; 0116 } 0117 0118 quint64 KMemoryInfo::availablePhysical() const 0119 { 0120 return d->m_availablePhysical; 0121 } 0122 0123 quint64 KMemoryInfo::cached() const 0124 { 0125 return d->m_cached; 0126 } 0127 0128 quint64 KMemoryInfo::buffers() const 0129 { 0130 return d->m_buffers; 0131 } 0132 0133 quint64 KMemoryInfo::totalSwapFile() const 0134 { 0135 return d->m_totalSwapFile; 0136 } 0137 0138 quint64 KMemoryInfo::freeSwapFile() const 0139 { 0140 return d->m_freeSwapFile; 0141 } 0142 0143 #if defined(Q_OS_WINDOWS) 0144 /***************************************************************************** 0145 * Windows 0146 ****************************************************************************/ 0147 0148 struct SwapInfo { 0149 quint64 totalPageFilePages = 0; 0150 quint64 freePageFilePages = 0; 0151 }; 0152 0153 BOOL __stdcall pageInfo(LPVOID pContext, PENUM_PAGE_FILE_INFORMATION pPageFileInfo, LPCWSTR lpFilename) 0154 { 0155 Q_UNUSED(lpFilename) 0156 if (auto sw = static_cast<SwapInfo *>(pContext)) { 0157 sw->totalPageFilePages += pPageFileInfo->TotalSize; 0158 sw->freePageFilePages += (pPageFileInfo->TotalSize - pPageFileInfo->TotalInUse); 0159 return true; 0160 } 0161 return false; 0162 } 0163 0164 bool KMemoryInfo::update() 0165 { 0166 MEMORYSTATUSEX statex; 0167 statex.dwLength = sizeof(statex); 0168 if (!GlobalMemoryStatusEx(&statex)) { 0169 return false; 0170 } 0171 0172 PERFORMANCE_INFORMATION pi; 0173 DWORD pisz = sizeof(pi); 0174 if (!GetPerformanceInfo(&pi, pisz)) { 0175 return false; 0176 } 0177 0178 SwapInfo si; 0179 if (!EnumPageFiles(pageInfo, &si)) { 0180 return false; 0181 } 0182 0183 d->m_totalPhysical = statex.ullTotalPhys; 0184 d->m_availablePhysical = statex.ullAvailPhys; 0185 d->m_freePhysical = statex.ullAvailPhys; 0186 d->m_totalSwapFile = si.totalPageFilePages * pi.PageSize; 0187 d->m_freeSwapFile = si.freePageFilePages * pi.PageSize; 0188 d->m_cached = pi.SystemCache * pi.PageSize; 0189 d->m_buffers = 0; 0190 0191 return true; 0192 } 0193 0194 #elif defined(Q_OS_LINUX) || defined(Q_OS_ANDROID) 0195 /***************************************************************************** 0196 * GNU/Linux 0197 ****************************************************************************/ 0198 0199 using ByteArrayView = QByteArrayView; 0200 0201 bool extractBytes(quint64 &value, const QByteArray &buffer, const ByteArrayView &beginPattern, qsizetype &from) 0202 { 0203 ByteArrayView endPattern("kB"); 0204 auto beginIdx = buffer.indexOf(beginPattern, from); 0205 if (beginIdx > -1) { 0206 auto start = beginIdx + beginPattern.size(); 0207 auto endIdx = buffer.indexOf(endPattern, start); 0208 if (endIdx > -1) { 0209 from = endIdx + endPattern.size(); 0210 auto ok = false; 0211 value = buffer.mid(start, endIdx - start).toULongLong(&ok) * 1024; 0212 return ok; 0213 } 0214 } 0215 if (from) { // Wrong order? Restart from the beginning 0216 qCWarning(LOG_KMEMORYINFO) << "KMemoryInfo: extractBytes: wrong order when extracting" << beginPattern; 0217 from = 0; 0218 return extractBytes(value, buffer, beginPattern, from); 0219 } 0220 return false; 0221 } 0222 0223 bool KMemoryInfo::update() 0224 { 0225 QFile file(QStringLiteral("/proc/meminfo")); 0226 if (!file.open(QFile::ReadOnly)) { 0227 return false; 0228 } 0229 auto meminfo = file.readAll(); 0230 file.close(); 0231 0232 qsizetype miFrom = 0; 0233 quint64 totalPhys = 0; 0234 if (!extractBytes(totalPhys, meminfo, "MemTotal:", miFrom)) { 0235 return false; 0236 } 0237 quint64 freePhys = 0; 0238 if (!extractBytes(freePhys, meminfo, "MemFree:", miFrom)) { 0239 return false; 0240 } 0241 quint64 availPhys = 0; 0242 if (!extractBytes(availPhys, meminfo, "MemAvailable:", miFrom)) { 0243 return false; 0244 } 0245 quint64 buffers = 0; 0246 if (!extractBytes(buffers, meminfo, "Buffers:", miFrom)) { 0247 return false; 0248 } 0249 quint64 cached = 0; 0250 if (!extractBytes(cached, meminfo, "Cached:", miFrom)) { 0251 return false; 0252 } 0253 quint64 swapTotal = 0; 0254 if (!extractBytes(swapTotal, meminfo, "SwapTotal:", miFrom)) { 0255 return false; 0256 } 0257 quint64 swapFree = 0; 0258 if (!extractBytes(swapFree, meminfo, "SwapFree:", miFrom)) { 0259 return false; 0260 } 0261 quint64 sharedMem = 0; 0262 if (!extractBytes(sharedMem, meminfo, "Shmem:", miFrom)) { 0263 return false; 0264 } 0265 quint64 sReclaimable = 0; 0266 if (!extractBytes(sReclaimable, meminfo, "SReclaimable:", miFrom)) { 0267 return false; 0268 } 0269 0270 // Source HTOP: https://github.com/htop-dev/htop/blob/main/linux/LinuxProcessList.c 0271 d->m_totalPhysical = totalPhys; 0272 // NOTE: another viable solution: d->m_availablePhysical = std::min(availPhys, totalPhys - (committedAs - cached - (swapTotal - swapFree))) 0273 d->m_availablePhysical = availPhys ? std::min(availPhys, totalPhys) : freePhys; 0274 d->m_freePhysical = freePhys; 0275 d->m_totalSwapFile = swapTotal; 0276 d->m_freeSwapFile = swapFree; 0277 d->m_cached = cached + sReclaimable - sharedMem; 0278 d->m_buffers = buffers; 0279 0280 return true; 0281 } 0282 0283 #elif defined(Q_OS_MACOS) 0284 /***************************************************************************** 0285 * macOS 0286 ****************************************************************************/ 0287 0288 template<class T> 0289 bool sysctlread(const char *name, T &var) 0290 { 0291 auto sz = sizeof(var); 0292 return (sysctlbyname(name, &var, &sz, NULL, 0) == 0); 0293 } 0294 0295 bool KMemoryInfo::update() 0296 { 0297 quint64 memSize = 0; 0298 quint64 pageSize = 0; 0299 xsw_usage swapUsage; 0300 0301 int mib[2]; 0302 size_t sz = 0; 0303 0304 mib[0] = CTL_HW; 0305 mib[1] = HW_MEMSIZE; 0306 sz = sizeof(memSize); 0307 if (sysctl(mib, 2, &memSize, &sz, NULL, 0) != KERN_SUCCESS) { 0308 return false; 0309 } 0310 0311 mib[0] = CTL_HW; 0312 mib[1] = HW_PAGESIZE; 0313 sz = sizeof(pageSize); 0314 if (sysctl(mib, 2, &pageSize, &sz, NULL, 0) != KERN_SUCCESS) { 0315 return false; 0316 } 0317 0318 mib[0] = CTL_VM; 0319 mib[1] = VM_SWAPUSAGE; 0320 sz = sizeof(swapUsage); 0321 if (sysctl(mib, 2, &swapUsage, &sz, NULL, 0) != KERN_SUCCESS) { 0322 return false; 0323 } 0324 0325 quint64 zfs_arcstats_size = 0; 0326 if (!sysctlread("kstat.zfs.misc.arcstats.size", zfs_arcstats_size)) { 0327 zfs_arcstats_size = 0; // no ZFS used 0328 } 0329 0330 mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; 0331 vm_statistics64_data_t vmstat; 0332 if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vmstat, &count) != KERN_SUCCESS) { 0333 return false; 0334 } 0335 0336 d->m_totalPhysical = memSize; 0337 d->m_availablePhysical = memSize - (vmstat.internal_page_count + vmstat.compressor_page_count + vmstat.wire_count) * pageSize; 0338 d->m_freePhysical = vmstat.free_count * pageSize; 0339 d->m_totalSwapFile = swapUsage.xsu_total; 0340 d->m_freeSwapFile = swapUsage.xsu_avail; 0341 d->m_cached = vmstat.external_page_count * pageSize + zfs_arcstats_size; 0342 d->m_buffers = 0; 0343 0344 return true; 0345 } 0346 0347 #elif defined(Q_OS_FREEBSD) 0348 /***************************************************************************** 0349 * FreeBSD 0350 ****************************************************************************/ 0351 0352 template<class T> 0353 bool sysctlread(const char *name, T &var) 0354 { 0355 auto sz = sizeof(var); 0356 return (sysctlbyname(name, &var, &sz, NULL, 0) == 0); 0357 } 0358 0359 bool KMemoryInfo::update() 0360 { 0361 quint64 memSize = 0; 0362 quint64 pageSize = 0; 0363 0364 int mib[4]; 0365 size_t sz = 0; 0366 0367 mib[0] = CTL_HW; 0368 mib[1] = HW_PHYSMEM; 0369 sz = sizeof(memSize); 0370 if (sysctl(mib, 2, &memSize, &sz, NULL, 0) != 0) { 0371 return false; 0372 } 0373 0374 mib[0] = CTL_HW; 0375 mib[1] = HW_PAGESIZE; 0376 sz = sizeof(pageSize); 0377 if (sysctl(mib, 2, &pageSize, &sz, NULL, 0) != 0) { 0378 return false; 0379 } 0380 0381 quint32 v_pageSize = 0; 0382 if (sysctlread("vm.stats.vm.v_page_size", v_pageSize)) { 0383 pageSize = v_pageSize; 0384 } 0385 quint64 zfs_arcstats_size = 0; 0386 if (!sysctlread("kstat.zfs.misc.arcstats.size", zfs_arcstats_size)) { 0387 zfs_arcstats_size = 0; // no ZFS used 0388 } 0389 quint32 v_cache_count = 0; 0390 if (!sysctlread("vm.stats.vm.v_cache_count", v_cache_count)) { 0391 return false; 0392 } 0393 quint32 v_inactive_count = 0; 0394 if (!sysctlread("vm.stats.vm.v_inactive_count", v_inactive_count)) { 0395 return false; 0396 } 0397 quint32 v_free_count = 0; 0398 if (!sysctlread("vm.stats.vm.v_free_count", v_free_count)) { 0399 return false; 0400 } 0401 quint64 vfs_bufspace = 0; 0402 if (!sysctlread("vfs.bufspace", vfs_bufspace)) { 0403 return false; 0404 } 0405 0406 quint64 swap_tot = 0; 0407 quint64 swap_free = 0; 0408 if (auto kd = kvm_open("/dev/null", "/dev/null", "/dev/null", O_RDONLY, "kvm_open")) { 0409 struct kvm_swap swap; 0410 // if you specify a maxswap value of 1, the function will typically return the 0411 // value 0 and the single kvm_swap structure will be filled with the grand total over all swap devices. 0412 auto nswap = kvm_getswapinfo(kd, &swap, 1, 0); 0413 if (nswap == 0) { 0414 swap_tot = swap.ksw_total; 0415 swap_free = swap.ksw_used; 0416 } 0417 swap_free = (swap_tot - swap_free) * pageSize; 0418 swap_tot *= pageSize; 0419 } 0420 0421 // Source HTOP: https://github.com/htop-dev/htop/blob/main/freebsd/FreeBSDProcessList.c 0422 d->m_totalPhysical = memSize; 0423 d->m_availablePhysical = pageSize * (v_cache_count + v_free_count + v_inactive_count) + vfs_bufspace + zfs_arcstats_size; 0424 d->m_freePhysical = pageSize * v_free_count; 0425 d->m_totalSwapFile = swap_tot; 0426 d->m_freeSwapFile = swap_free; 0427 d->m_cached = pageSize * v_cache_count + zfs_arcstats_size; 0428 d->m_buffers = vfs_bufspace; 0429 0430 return true; 0431 } 0432 0433 #elif defined(Q_OS_OPENBSD) 0434 /***************************************************************************** 0435 * OpenBSD 0436 ****************************************************************************/ 0437 // From src/usr.bin/top/machine.c 0438 static int swap_usage(int *used, int *total) 0439 { 0440 struct swapent *swdev; 0441 int nswap, rnswap, i; 0442 0443 nswap = swapctl(SWAP_NSWAP, nullptr, 0); 0444 if (nswap == 0) 0445 return 0; 0446 0447 swdev = static_cast<struct swapent *>(calloc(nswap, sizeof(*swdev))); 0448 if (swdev == NULL) 0449 return 0; 0450 0451 rnswap = swapctl(SWAP_STATS, swdev, nswap); 0452 if (rnswap == -1) { 0453 free(swdev); 0454 return 0; 0455 } 0456 /* Total things up */ 0457 *total = *used = 0; 0458 for (i = 0; i < nswap; i++) { 0459 if (swdev[i].se_flags & SWF_ENABLE) { 0460 *used += (swdev[i].se_inuse / (1024 / DEV_BSIZE)); 0461 *total += (swdev[i].se_nblks / (1024 / DEV_BSIZE)); 0462 } 0463 } 0464 free(swdev); 0465 return 1; 0466 } 0467 0468 bool KMemoryInfo::update() 0469 { 0470 // TODO: compute m_availablePhysical on OpenBSD 0471 0472 // tota phsycial memory 0473 const long phys_pages = sysconf(_SC_PHYS_PAGES); 0474 const long pagesize = sysconf(_SC_PAGESIZE); 0475 if (phys_pages != -1 && pagesize != -1) 0476 d->m_totalPhysical = ((uint64_t)phys_pages * (uint64_t)pagesize / 1024); 0477 0478 int swap_free = 0; 0479 int swap_tot = 0; 0480 if (swap_usage(&swap_free, &swap_tot)) { 0481 d->m_totalSwapFile = swap_tot; 0482 d->m_freeSwapFile = swap_free; 0483 } 0484 0485 int uvmexp_mib[] = {CTL_VM, VM_UVMEXP}; 0486 struct uvmexp uvmexp; 0487 size_t size = sizeof(uvmexp); 0488 if (sysctl(uvmexp_mib, 2, &uvmexp, &size, NULL, 0) == -1) { 0489 bzero(&uvmexp, sizeof(uvmexp)); 0490 return false; 0491 } 0492 d->m_freePhysical = uvmexp.free * pagesize / 1024; 0493 0494 int bcstats_mib[] = {CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT}; 0495 struct bcachestats bcstats; 0496 size = sizeof(bcstats); 0497 if (sysctl(bcstats_mib, 3, &bcstats, &size, NULL, 0) == -1) { 0498 bzero(&bcstats, sizeof(bcstats)); 0499 return false; 0500 } 0501 d->m_cached = bcstats.numbufpages * pagesize / 1024; 0502 0503 return true; 0504 } 0505 #else 0506 /***************************************************************************** 0507 * Unsupported platform 0508 ****************************************************************************/ 0509 0510 bool KMemoryInfo::update() 0511 { 0512 qCWarning(LOG_KMEMORYINFO) << "KMemoryInfo: unsupported platform!"; 0513 return false; 0514 } 0515 0516 #endif