|
14 | 14 | #include <sys/sysctl.h> |
15 | 15 | #elif __linux__ |
16 | 16 | #include <fstream> |
| 17 | +#include <sys/sysinfo.h> |
| 18 | +#include <unistd.h> |
17 | 19 | #elif _WIN32 |
| 20 | +#include <psapi.h> |
18 | 21 | #include <windows.h> |
19 | 22 | #endif |
20 | 23 |
|
21 | 24 | struct AddonSystemMemoryInfo { |
22 | 25 | std::optional<uint64_t> total = std::nullopt; |
23 | | - std::optional<uint64_t> useful = std::nullopt; |
| 26 | + std::optional<uint64_t> wired = std::nullopt; |
24 | 27 | std::optional<uint64_t> free = std::nullopt; |
25 | 28 | }; |
26 | 29 |
|
| 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 | + |
27 | 42 | #ifdef __APPLE__ |
28 | 43 | struct AddonHostPortScopeExit { |
29 | 44 | mach_port_t hostPort; |
@@ -172,103 +187,216 @@ static std::optional<uint64_t> parseLinuxMeminfoValueBytes(const std::string& li |
172 | 187 |
|
173 | 188 | return static_cast<uint64_t>(roundedBytesValue); |
174 | 189 | } |
| 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 | +} |
175 | 249 | #endif |
176 | 250 |
|
177 | 251 | static AddonSystemMemoryInfo retrieveSystemMemoryInfo() { |
178 | 252 | AddonSystemMemoryInfo systemMemoryInfo; |
179 | 253 |
|
180 | 254 | #ifdef __APPLE__ |
181 | 255 | { |
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(); |
186 | 268 | } else { |
187 | 269 | addonLog(GGML_LOG_LEVEL_ERROR, "Failed to get total system memory"); |
188 | 270 | } |
189 | 271 | } |
190 | 272 |
|
191 | 273 | mach_port_t hostPort = mach_host_self(); |
192 | 274 | AddonHostPortScopeExit hostPortGuard(hostPort); |
193 | | - vm_size_t pageSize = 0; |
194 | | - if (host_page_size(hostPort, &pageSize) == KERN_SUCCESS) { |
| 275 | + if (vm_page_size != 0) { |
195 | 276 | vm_statistics64_data_t vmStats; |
196 | 277 | mach_msg_type_number_t infoCount = HOST_VM_INFO64_COUNT; |
197 | 278 | 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; |
203 | 281 |
|
204 | 282 | systemMemoryInfo.free = freeBytes; |
205 | | - systemMemoryInfo.useful = freeBytes + purgeableBytes + reclaimableFileCacheBytes; |
| 283 | + systemMemoryInfo.wired = wiredBytes; |
206 | 284 | } 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"); |
208 | 286 | } |
209 | 287 | } else { |
210 | 288 | addonLog(GGML_LOG_LEVEL_ERROR, "Failed to get system page size"); |
211 | 289 | } |
212 | 290 | #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 | + } |
230 | 301 | } |
| 302 | + } |
231 | 303 |
|
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"); |
256 | 377 | } |
257 | 378 | #elif _WIN32 |
258 | 379 | MEMORYSTATUSEX memoryStatus; |
259 | 380 | memoryStatus.dwLength = sizeof(MEMORYSTATUSEX); |
260 | 381 |
|
261 | 382 | if (GlobalMemoryStatusEx(&memoryStatus)) { |
262 | 383 | systemMemoryInfo.total = memoryStatus.ullTotalPhys; |
263 | | - systemMemoryInfo.useful = memoryStatus.ullAvailPhys; |
264 | 384 | systemMemoryInfo.free = memoryStatus.ullAvailPhys; |
265 | 385 | } 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"); |
267 | 395 | } |
268 | 396 | #endif |
269 | 397 |
|
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()); |
272 | 400 |
|
273 | 401 | if (systemMemoryInfo.total.has_value() && systemMemoryInfo.free.has_value()) |
274 | 402 | systemMemoryInfo.free = std::min(systemMemoryInfo.free.value(), systemMemoryInfo.total.value()); |
@@ -310,9 +438,9 @@ class AddonGetSystemMemoryInfoWorker : public Napi::AsyncWorker { |
310 | 438 | : Env().Null() |
311 | 439 | ); |
312 | 440 | 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()) |
316 | 444 | : Env().Null() |
317 | 445 | ); |
318 | 446 | result.Set( |
|
0 commit comments