Merge pull request #6765 from MightyChubz/fix/linux-inaccurate-meminfo

fix: Get meminfo from `/proc/meminfo` over `sysinfo()`
This commit is contained in:
Jeroen van Rijn
2026-06-05 09:39:30 +02:00
committed by GitHub

View File

@@ -2,6 +2,7 @@ package sysinfo
import "base:intrinsics"
import "base:runtime"
import "core:strconv"
import "core:strings"
import "core:sys/linux"
@@ -80,16 +81,98 @@ _os_version :: proc (allocator: runtime.Allocator, loc := #caller_location) -> (
@(private)
_ram_stats :: proc "contextless" () -> (total_ram, free_ram, total_swap, free_swap: i64, ok: bool) {
// Retrieve RAM info using `sysinfo`
sys_info: linux.Sys_Info
errno := linux.sysinfo(&sys_info)
assert_contextless(errno == .NONE, "Good luck to whoever's debugging this, something's seriously cucked up!")
// This is here for some of the strings procedures
context = runtime.default_context()
total_ram = i64(sys_info.totalram) * i64(sys_info.mem_unit)
free_ram = i64(sys_info.freeram) * i64(sys_info.mem_unit)
total_swap = i64(sys_info.totalswap) * i64(sys_info.mem_unit)
free_swap = i64(sys_info.freeswap) * i64(sys_info.mem_unit)
ok = true
// The approach is to read /proc/meminfo for the memory information. We do this over
// reading sysinfo() since sysinfo() only returns MemFree, which is based on the amount
// of free pages. The value we actually want is MemAvailable inside meminfo since it is
// estimated around being about to evict things out of the page cache.
fd, errno := linux.open("/proc/meminfo", {})
if errno != .NONE {
// This should never happen since something would be wrong with the system
// if /proc/meminfo wasn't able to be opened for any reason. But, in the
// event that this _does_ happen, let's just try to recover through the
// syscall
sys_info: linux.Sys_Info
sysinfo_errno := linux.sysinfo(&sys_info)
assert_contextless(sysinfo_errno == .NONE, "If this has failed, there is no recovery from this")
total_ram = i64(sys_info.totalram) * i64(sys_info.mem_unit)
free_ram = i64(sys_info.freeram) * i64(sys_info.mem_unit)
total_swap = i64(sys_info.totalswap) * i64(sys_info.mem_unit)
free_swap = i64(sys_info.freeswap) * i64(sys_info.mem_unit)
ok = true
return
}
defer linux.close(fd)
// We need a relatively large size to store all the info
meminfo_buf: [4096]u8
n, read_errno := linux.read(fd, meminfo_buf[:])
if read_errno != .NONE {
sys_info: linux.Sys_Info
sysinfo_errno := linux.sysinfo(&sys_info)
assert_contextless(sysinfo_errno == .NONE, "If this has failed, there is no recovery from this")
total_ram = i64(sys_info.totalram) * i64(sys_info.mem_unit)
free_ram = i64(sys_info.freeram) * i64(sys_info.mem_unit)
total_swap = i64(sys_info.totalswap) * i64(sys_info.mem_unit)
free_swap = i64(sys_info.freeswap) * i64(sys_info.mem_unit)
ok = true
return
}
meminfo := string(meminfo_buf[:n])
// Fallback in the event MemAvailable is not found or is invalid in its value
mem_free: i64
mem_unit :: 1024
for line in strings.split_lines_iterator(&meminfo) {
if len(line) == 0 {
continue
}
colon_idx := strings.index(line, ":")
if colon_idx < 0 {
continue
}
key := strings.trim_space(line[:colon_idx])
value_str := strings.trim_space(strings.trim_suffix(line[colon_idx + 1:], "kB"))
value, conv_ok := strconv.parse_i64(value_str, 10)
if !conv_ok {
continue
}
switch key {
case "MemTotal":
total_ram = value * mem_unit
case "MemFree":
mem_free = value * mem_unit
case "MemAvailable":
free_ram = value * mem_unit
case "SwapTotal":
total_swap = value * mem_unit
case "SwapFree":
free_swap = value * mem_unit
}
}
if free_ram == 0 || free_ram > total_ram {
// We opt to return MemFree here if MemAvailable is not found or is broken to some degree.
// This will act as a predictable fallback, but shouldn't ever really occur unless the user
// is on Linux < 3.14
free_ram = mem_free
}
ok = true
return
}