diff --git a/core/sys/info/platform_linux.odin b/core/sys/info/platform_linux.odin index 7886b6243..eef77afa0 100644 --- a/core/sys/info/platform_linux.odin +++ b/core/sys/info/platform_linux.odin @@ -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 } \ No newline at end of file