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 | |
13 | Q_DECLARE_LOGGING_CATEGORY(LOG_KMEMORYINFO) |
14 | Q_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 | |
34 | class KMemoryInfoPrivate : public QSharedData |
35 | { |
36 | public: |
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 | |
50 | KMemoryInfo::KMemoryInfo() |
51 | : d(new KMemoryInfoPrivate) |
52 | { |
53 | update(); |
54 | } |
55 | |
56 | KMemoryInfo::~KMemoryInfo() |
57 | { |
58 | } |
59 | |
60 | KMemoryInfo::KMemoryInfo(const KMemoryInfo &other) |
61 | : d(other.d) |
62 | { |
63 | } |
64 | |
65 | KMemoryInfo &KMemoryInfo::operator=(const KMemoryInfo &other) |
66 | { |
67 | d = other.d; |
68 | return *this; |
69 | } |
70 | |
71 | bool 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 | |
87 | bool KMemoryInfo::operator!=(const KMemoryInfo &other) const |
88 | { |
89 | return !operator==(other); |
90 | } |
91 | |
92 | bool KMemoryInfo::isNull() const |
93 | { |
94 | return d->m_totalPhysical == 0; |
95 | } |
96 | |
97 | quint64 KMemoryInfo::totalPhysical() const |
98 | { |
99 | return d->m_totalPhysical; |
100 | } |
101 | |
102 | quint64 KMemoryInfo::freePhysical() const |
103 | { |
104 | return d->m_freePhysical; |
105 | } |
106 | |
107 | quint64 KMemoryInfo::availablePhysical() const |
108 | { |
109 | return d->m_availablePhysical; |
110 | } |
111 | |
112 | quint64 KMemoryInfo::cached() const |
113 | { |
114 | return d->m_cached; |
115 | } |
116 | |
117 | quint64 KMemoryInfo::buffers() const |
118 | { |
119 | return d->m_buffers; |
120 | } |
121 | |
122 | quint64 KMemoryInfo::totalSwapFile() const |
123 | { |
124 | return d->m_totalSwapFile; |
125 | } |
126 | |
127 | quint64 KMemoryInfo::freeSwapFile() const |
128 | { |
129 | return d->m_freeSwapFile; |
130 | } |
131 | |
132 | #if defined(Q_OS_WINDOWS) |
133 | /***************************************************************************** |
134 | * Windows |
135 | ****************************************************************************/ |
136 | |
137 | struct SwapInfo { |
138 | quint64 totalPageFilePages = 0; |
139 | quint64 freePageFilePages = 0; |
140 | }; |
141 | |
142 | BOOL __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 | |
153 | bool 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 | |
188 | using ByteArrayView = QByteArrayView; |
189 | |
190 | bool 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 | |
212 | bool 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 | |
277 | template<class T> |
278 | bool sysctlread(const char *name, T &var) |
279 | { |
280 | auto sz = sizeof(var); |
281 | return (sysctlbyname(name, &var, &sz, NULL, 0) == 0); |
282 | } |
283 | |
284 | bool 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 | |
341 | template<class T> |
342 | bool sysctlread(const char *name, T &var) |
343 | { |
344 | auto sz = sizeof(var); |
345 | return (sysctlbyname(name, &var, &sz, NULL, 0) == 0); |
346 | } |
347 | |
348 | bool 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 | |
427 | bool KMemoryInfo::update() |
428 | { |
429 | qCWarning(LOG_KMEMORYINFO) << "KMemoryInfo: unsupported platform!" ; |
430 | return false; |
431 | } |
432 | |
433 | #endif |
434 | |