mirror of
https://github.com/mudler/LocalAI.git
synced 2026-06-14 11:49:33 -04:00
fix(xsysinfo): make reported system RAM total cgroup/lxcfs-aware (#8059) GetSystemRAMInfo derived Total from memory.TotalMemory(), which on Linux uses syscall.Sysinfo().Totalram - the HOST kernel total. lxcfs/LXD does NOT virtualize that value, while MemAvailable (used for Free/Available) IS virtualized. Inside an LXD/container with a 128Gi host but a ~10Gi container view this produced Total=128Gi, Available=10Gi => Used=118Gi, reporting ~92% RAM usage on an idle container. Derive Total instead from the minimum of all non-zero, non-unlimited candidates: cgroup v2 memory.max, cgroup v1 memory.limit_in_bytes (the kernel unlimited sentinel is ignored), /proc/meminfo MemTotal (which lxcfs virtualizes), and the syscall.Sysinfo total as the bare-metal fallback. On bare metal every candidate is unlimited or equals the host total, so behavior is unchanged. The selection/parsing lives in a pure function chooseTotalMemory(...) taking file CONTENTS, unit-tested without a real LXD host; OS file reads stay in a thin wrapper. Assisted-by: claude:claude-opus-4-8 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
78 lines
2.2 KiB
Go
78 lines
2.2 KiB
Go
package xsysinfo
|
|
|
|
import (
|
|
"os"
|
|
|
|
"github.com/mudler/memory"
|
|
)
|
|
|
|
// cgroup/proc paths used to make the reported RAM total container-aware.
|
|
// They are variables (not consts) so tests could override them if needed.
|
|
var (
|
|
cgroupV2MaxPath = "/sys/fs/cgroup/memory.max"
|
|
cgroupV1LimitPath = "/sys/fs/cgroup/memory/memory.limit_in_bytes"
|
|
procMemInfoPath = "/proc/meminfo"
|
|
)
|
|
|
|
// SystemRAMInfo contains system RAM usage information
|
|
type SystemRAMInfo struct {
|
|
Total uint64 `json:"total"`
|
|
Used uint64 `json:"used"`
|
|
Free uint64 `json:"free"`
|
|
Available uint64 `json:"available"`
|
|
UsagePercent float64 `json:"usage_percent"`
|
|
}
|
|
|
|
// readFileBestEffort reads a file and returns its contents, or "" on any error.
|
|
// Missing cgroup/proc files (e.g. on non-Linux hosts) are expected and benign.
|
|
func readFileBestEffort(path string) string {
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
// systemTotalMemory returns the container-aware total system RAM in bytes.
|
|
//
|
|
// memory.TotalMemory() reports the HOST kernel total (syscall.Sysinfo on
|
|
// Linux), which lxcfs/LXD does NOT virtualize. Inside a container that
|
|
// over-reports physical RAM and, combined with the virtualized MemAvailable,
|
|
// inflates the reported usage (see issue #8059). We instead derive the total
|
|
// from the minimum of all available container-aware candidates.
|
|
func systemTotalMemory() uint64 {
|
|
return chooseTotalMemory(
|
|
readFileBestEffort(cgroupV2MaxPath),
|
|
readFileBestEffort(cgroupV1LimitPath),
|
|
readFileBestEffort(procMemInfoPath),
|
|
memory.TotalMemory(),
|
|
)
|
|
}
|
|
|
|
// GetSystemRAMInfo returns real-time system RAM usage
|
|
func GetSystemRAMInfo() (*SystemRAMInfo, error) {
|
|
total := systemTotalMemory()
|
|
available := memory.AvailableMemory()
|
|
|
|
// AvailableMemory (MemAvailable) is virtualized by lxcfs, so in edge
|
|
// cases it can exceed our corrected total; clamp to avoid an unsigned
|
|
// underflow when computing Used.
|
|
if available > total {
|
|
available = total
|
|
}
|
|
|
|
used := total - available
|
|
|
|
usagePercent := 0.0
|
|
if total > 0 {
|
|
usagePercent = float64(used) / float64(total) * 100
|
|
}
|
|
return &SystemRAMInfo{
|
|
Total: total,
|
|
Used: used,
|
|
Free: available,
|
|
Available: available,
|
|
UsagePercent: usagePercent,
|
|
}, nil
|
|
}
|