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