Skip to content

Commit 547c692

Browse files
committed
fix: correct wired memory calculation
1 parent 79543d9 commit 547c692

7 files changed

Lines changed: 221 additions & 97 deletions

File tree

llama/addon/globals/getSystemMemoryInfo.cpp

Lines changed: 190 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,31 @@
1414
#include <sys/sysctl.h>
1515
#elif __linux__
1616
#include <fstream>
17+
#include <sys/sysinfo.h>
18+
#include <unistd.h>
1719
#elif _WIN32
20+
#include <psapi.h>
1821
#include <windows.h>
1922
#endif
2023

2124
struct AddonSystemMemoryInfo {
2225
std::optional<uint64_t> total = std::nullopt;
23-
std::optional<uint64_t> useful = std::nullopt;
26+
std::optional<uint64_t> wired = std::nullopt;
2427
std::optional<uint64_t> free = std::nullopt;
2528
};
2629

30+
static std::optional<uint64_t> multiplyUint64(uint64_t left, uint64_t right) {
31+
if (left == 0 || right == 0) {
32+
return uint64_t(0);
33+
}
34+
35+
if (left > (std::numeric_limits<uint64_t>::max() / right)) {
36+
return std::nullopt;
37+
}
38+
39+
return left * right;
40+
}
41+
2742
#ifdef __APPLE__
2843
struct AddonHostPortScopeExit {
2944
mach_port_t hostPort;
@@ -172,103 +187,216 @@ static std::optional<uint64_t> parseLinuxMeminfoValueBytes(const std::string& li
172187

173188
return static_cast<uint64_t>(roundedBytesValue);
174189
}
190+
191+
static std::optional<uint64_t> parseLinuxVmstatValuePages(const std::string& line, std::string& key) {
192+
const size_t separatorIndex = line.find_first_of(" \t");
193+
if (separatorIndex == std::string::npos || separatorIndex == 0) {
194+
return std::nullopt;
195+
}
196+
197+
key = line.substr(0, separatorIndex);
198+
199+
size_t index = separatorIndex;
200+
while (index < line.size() && isLinuxMeminfoWhitespace(line[index])) {
201+
index++;
202+
}
203+
204+
if (index == line.size()) {
205+
return std::nullopt;
206+
}
207+
208+
uint64_t pageCount = 0;
209+
bool sawDigit = false;
210+
while (index < line.size() && std::isdigit(static_cast<unsigned char>(line[index]))) {
211+
sawDigit = true;
212+
213+
const uint64_t digit = uint64_t(line[index] - '0');
214+
if (pageCount > ((std::numeric_limits<uint64_t>::max() - digit) / 10)) {
215+
return std::nullopt;
216+
}
217+
218+
pageCount = (pageCount * 10) + digit;
219+
index++;
220+
}
221+
222+
if (!sawDigit) {
223+
return std::nullopt;
224+
}
225+
226+
while (index < line.size() && isLinuxMeminfoWhitespace(line[index])) {
227+
index++;
228+
}
229+
230+
if (index != line.size()) {
231+
return std::nullopt;
232+
}
233+
234+
return pageCount;
235+
}
236+
237+
static std::optional<uint64_t> getLinuxPageSize() {
238+
static const std::optional<uint64_t> cachedLinuxPageSize = []() -> std::optional<uint64_t> {
239+
const long pageSize = sysconf(_SC_PAGESIZE);
240+
if (pageSize <= 0) {
241+
return std::nullopt;
242+
}
243+
244+
return uint64_t(pageSize);
245+
}();
246+
247+
return cachedLinuxPageSize;
248+
}
175249
#endif
176250

177251
static AddonSystemMemoryInfo retrieveSystemMemoryInfo() {
178252
AddonSystemMemoryInfo systemMemoryInfo;
179253

180254
#ifdef __APPLE__
181255
{
182-
uint64_t physicalMemory = 0;
183-
size_t physicalMemorySize = sizeof(physicalMemory);
184-
if (sysctlbyname("hw.memsize", &physicalMemory, &physicalMemorySize, NULL, 0) == 0) {
185-
systemMemoryInfo.total = physicalMemory;
256+
static const std::optional<uint64_t> cachedPhysicalMemory = []() -> std::optional<uint64_t> {
257+
uint64_t physicalMemory = 0;
258+
size_t physicalMemorySize = sizeof(physicalMemory);
259+
if (sysctlbyname("hw.memsize", &physicalMemory, &physicalMemorySize, NULL, 0) == 0) {
260+
return physicalMemory;
261+
}
262+
263+
return std::nullopt;
264+
}();
265+
266+
if (cachedPhysicalMemory.has_value()) {
267+
systemMemoryInfo.total = cachedPhysicalMemory.value();
186268
} else {
187269
addonLog(GGML_LOG_LEVEL_ERROR, "Failed to get total system memory");
188270
}
189271
}
190272

191273
mach_port_t hostPort = mach_host_self();
192274
AddonHostPortScopeExit hostPortGuard(hostPort);
193-
vm_size_t pageSize = 0;
194-
if (host_page_size(hostPort, &pageSize) == KERN_SUCCESS) {
275+
if (vm_page_size != 0) {
195276
vm_statistics64_data_t vmStats;
196277
mach_msg_type_number_t infoCount = HOST_VM_INFO64_COUNT;
197278
if (host_statistics64(hostPort, HOST_VM_INFO64, (host_info64_t)&vmStats, &infoCount) == KERN_SUCCESS) {
198-
const uint64_t freeBytes = uint64_t(vmStats.free_count) * pageSize;
199-
const uint64_t purgeableBytes = uint64_t(vmStats.purgeable_count) * pageSize;
200-
201-
const uint64_t reclaimableFileCachePages = std::min<uint64_t>(vmStats.inactive_count, vmStats.external_page_count);
202-
const uint64_t reclaimableFileCacheBytes = reclaimableFileCachePages * pageSize;
279+
const uint64_t freeBytes = (uint64_t(vmStats.free_count) * vm_page_size);
280+
const uint64_t wiredBytes = uint64_t(vmStats.wire_count) * vm_page_size;
203281

204282
systemMemoryInfo.free = freeBytes;
205-
systemMemoryInfo.useful = freeBytes + purgeableBytes + reclaimableFileCacheBytes;
283+
systemMemoryInfo.wired = wiredBytes;
206284
} else {
207-
addonLog(GGML_LOG_LEVEL_ERROR, "Failed to get useful system memory");
285+
addonLog(GGML_LOG_LEVEL_ERROR, "Failed to get wired system memory");
208286
}
209287
} else {
210288
addonLog(GGML_LOG_LEVEL_ERROR, "Failed to get system page size");
211289
}
212290
#elif __linux__
213-
std::ifstream procMeminfo("/proc/meminfo");
214-
if (!procMeminfo.is_open()) {
215-
addonLog(GGML_LOG_LEVEL_ERROR, "Failed to open /proc/meminfo");
216-
return systemMemoryInfo;
217-
}
218-
219-
std::string line;
220-
uint64_t buffersBytes = 0;
221-
uint64_t cachedBytes = 0;
222-
uint64_t sReclaimableBytes = 0;
223-
uint64_t shmemBytes = 0;
224-
225-
while (std::getline(procMeminfo, line)) {
226-
std::string key;
227-
const std::optional<uint64_t> valueBytes = parseLinuxMeminfoValueBytes(line, key);
228-
if (!valueBytes.has_value()) {
229-
continue;
291+
{
292+
struct sysinfo linuxSystemInfo;
293+
if (sysinfo(&linuxSystemInfo) == 0) {
294+
if (systemMemoryInfo.total.has_value()) {
295+
systemMemoryInfo.total = multiplyUint64(uint64_t(linuxSystemInfo.totalram), uint64_t(linuxSystemInfo.mem_unit));
296+
}
297+
298+
if (!systemMemoryInfo.free.has_value()) {
299+
systemMemoryInfo.free = multiplyUint64(uint64_t(linuxSystemInfo.freeram), uint64_t(linuxSystemInfo.mem_unit));
300+
}
230301
}
302+
}
231303

232-
if (key == "MemTotal") {
233-
systemMemoryInfo.total = valueBytes.value();
234-
} else if (key == "MemAvailable") {
235-
systemMemoryInfo.useful = valueBytes.value();
236-
} else if (key == "MemFree") {
237-
systemMemoryInfo.free = valueBytes.value();
238-
} else if (key == "Buffers")
239-
buffersBytes = valueBytes.value();
240-
else if (key == "Cached")
241-
cachedBytes = valueBytes.value();
242-
else if (key == "SReclaimable")
243-
sReclaimableBytes = valueBytes.value();
244-
else if (key == "Shmem")
245-
shmemBytes = valueBytes.value();
246-
}
247-
248-
if (!systemMemoryInfo.useful.has_value() && systemMemoryInfo.free.has_value()) {
249-
// fallback approximation on older kernels when `MemAvailable` is absent
250-
systemMemoryInfo.useful = (
251-
systemMemoryInfo.free.value() + buffersBytes + cachedBytes + sReclaimableBytes -
252-
std::min<uint64_t>(cachedBytes + sReclaimableBytes, shmemBytes)
253-
);
254-
} else {
255-
addonLog(GGML_LOG_LEVEL_ERROR, "Failed to get useful system memory");
304+
const std::optional<uint64_t> pageSize = getLinuxPageSize();
305+
std::optional<uint64_t> nrMlockPages = std::nullopt;
306+
if (pageSize.has_value()) {
307+
std::ifstream procVmstat("/proc/vmstat");
308+
if (procVmstat.is_open()) {
309+
std::string line;
310+
311+
while (std::getline(procVmstat, line)) {
312+
std::string key;
313+
const std::optional<uint64_t> valuePages = parseLinuxVmstatValuePages(line, key);
314+
if (!valuePages.has_value()) {
315+
continue;
316+
}
317+
318+
if (key == "nr_unevictable" && !systemMemoryInfo.wired.has_value()) {
319+
systemMemoryInfo.wired = multiplyUint64(valuePages.value(), pageSize.value());
320+
} else if (key == "nr_mlock") {
321+
nrMlockPages = valuePages.value();
322+
} else if (key == "nr_free_pages" && !systemMemoryInfo.free.has_value()) {
323+
systemMemoryInfo.free = multiplyUint64(valuePages.value(), pageSize.value());
324+
}
325+
326+
if (systemMemoryInfo.wired.has_value() && systemMemoryInfo.free.has_value()) {
327+
break;
328+
}
329+
}
330+
}
331+
}
332+
333+
if (!systemMemoryInfo.total.has_value() || !systemMemoryInfo.free.has_value() || !systemMemoryInfo.wired.has_value()) {
334+
std::ifstream procMeminfo("/proc/meminfo");
335+
if (procMeminfo.is_open()) {
336+
std::string line;
337+
std::optional<uint64_t> mlockedBytes = std::nullopt;
338+
339+
while (std::getline(procMeminfo, line)) {
340+
std::string key;
341+
const std::optional<uint64_t> valueBytes = parseLinuxMeminfoValueBytes(line, key);
342+
if (!valueBytes.has_value()) {
343+
continue;
344+
}
345+
346+
if (key == "MemTotal" && !systemMemoryInfo.total.has_value()) {
347+
systemMemoryInfo.total = valueBytes.value();
348+
} else if (key == "Unevictable" && !systemMemoryInfo.wired.has_value()) {
349+
systemMemoryInfo.wired = valueBytes.value();
350+
} else if (key == "MemFree" && !systemMemoryInfo.free.has_value()) {
351+
systemMemoryInfo.free = valueBytes.value();
352+
} else if (key == "Mlocked") {
353+
mlockedBytes = valueBytes.value();
354+
}
355+
}
356+
357+
if (!systemMemoryInfo.wired.has_value() && mlockedBytes.has_value()) {
358+
systemMemoryInfo.wired = mlockedBytes.value();
359+
}
360+
}
361+
}
362+
363+
if (!systemMemoryInfo.wired.has_value() && pageSize.has_value() && nrMlockPages.has_value()) {
364+
systemMemoryInfo.wired = multiplyUint64(nrMlockPages.value(), pageSize.value());
365+
}
366+
367+
if (!systemMemoryInfo.total.has_value()) {
368+
addonLog(GGML_LOG_LEVEL_ERROR, "Failed to get total system memory");
369+
}
370+
371+
if (!systemMemoryInfo.free.has_value()) {
372+
addonLog(GGML_LOG_LEVEL_ERROR, "Failed to get free system memory");
373+
}
374+
375+
if (!systemMemoryInfo.wired.has_value()) {
376+
addonLog(GGML_LOG_LEVEL_ERROR, "Failed to get wired system memory");
256377
}
257378
#elif _WIN32
258379
MEMORYSTATUSEX memoryStatus;
259380
memoryStatus.dwLength = sizeof(MEMORYSTATUSEX);
260381

261382
if (GlobalMemoryStatusEx(&memoryStatus)) {
262383
systemMemoryInfo.total = memoryStatus.ullTotalPhys;
263-
systemMemoryInfo.useful = memoryStatus.ullAvailPhys;
264384
systemMemoryInfo.free = memoryStatus.ullAvailPhys;
265385
} else {
266-
addonLog(GGML_LOG_LEVEL_ERROR, "Failed to get useful system memory");
386+
addonLog(GGML_LOG_LEVEL_ERROR, "Failed to get free system memory");
387+
}
388+
389+
PERFORMANCE_INFORMATION perfInfo;
390+
perfInfo.cb = sizeof(PERFORMANCE_INFORMATION);
391+
if (GetPerformanceInfo(&perfInfo, sizeof(perfInfo))) {
392+
systemMemoryInfo.wired = uint64_t(perfInfo.KernelNonpaged) * uint64_t(perfInfo.PageSize);
393+
} else {
394+
addonLog(GGML_LOG_LEVEL_ERROR, "Failed to get wired system memory");
267395
}
268396
#endif
269397

270-
if (systemMemoryInfo.total.has_value() && systemMemoryInfo.useful.has_value())
271-
systemMemoryInfo.useful = std::min(systemMemoryInfo.useful.value(), systemMemoryInfo.total.value());
398+
if (systemMemoryInfo.total.has_value() && systemMemoryInfo.wired.has_value())
399+
systemMemoryInfo.wired = std::min(systemMemoryInfo.wired.value(), systemMemoryInfo.total.value());
272400

273401
if (systemMemoryInfo.total.has_value() && systemMemoryInfo.free.has_value())
274402
systemMemoryInfo.free = std::min(systemMemoryInfo.free.value(), systemMemoryInfo.total.value());
@@ -310,9 +438,9 @@ class AddonGetSystemMemoryInfoWorker : public Napi::AsyncWorker {
310438
: Env().Null()
311439
);
312440
result.Set(
313-
"useful",
314-
systemMemoryInfo.useful.has_value()
315-
? Napi::Number::New(Env(), systemMemoryInfo.useful.value())
441+
"wired",
442+
systemMemoryInfo.wired.has_value()
443+
? Napi::Number::New(Env(), systemMemoryInfo.wired.value())
316444
: Env().Null()
317445
);
318446
result.Set(

src/bindings/AddonTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export type BindingModule = {
9999
},
100100
getSystemMemoryInfo(): Promise<{
101101
total: number | null,
102-
useful: number | null,
102+
wired: number | null,
103103
free: number | null
104104
}>,
105105
getProcessMemoryInfo(): {

0 commit comments

Comments
 (0)