package hostcollect import ( "fmt" "strings" "github.com/itworx/pulse/internal/host" ) // parseMeminfo converts /proc/meminfo into bytes. // // meminfo is kB based ("MemTotal: 16316420 kB"), where kB means KiB, and a handful of // counters carry no unit at all. Treating either as bytes is the classic off-by-1024 // bug, so the unit is read per line instead of assumed. func parseMeminfo(data []byte) (host.RawMemory, error) { values := make(map[string]uint64, 8) for _, line := range lines(data) { key, rest, found := strings.Cut(line, ":") if !found { continue } fields := strings.Fields(rest) if len(fields) == 0 { continue } amount, ok := parseUint(fields[0]) if !ok { continue } if len(fields) > 1 && strings.EqualFold(fields[1], "kB") { const kib = 1024 if amount > (1<<64-1)/kib { continue } amount *= kib } values[strings.TrimSpace(key)] = amount } total := values["MemTotal"] if total == 0 { return host.RawMemory{}, errNoMemTotal } available, ok := values["MemAvailable"] if !ok { // Kernels before 3.14 have no MemAvailable. The traditional approximation is // free plus the reclaimable page cache; it is an estimate, never above total. available = values["MemFree"] + values["Buffers"] + values["Cached"] + values["SReclaimable"] } if available > total { available = total } memory := host.RawMemory{TotalBytes: total, AvailableBytes: available} swapTotal := values["SwapTotal"] if swapTotal > 0 { swapFree := values["SwapFree"] if swapFree > swapTotal { swapFree = swapTotal } memory.SwapTotalBytes = swapTotal memory.SwapUsedBytes = swapTotal - swapFree } return memory, nil } func (c *Collector) memory() (host.RawMemory, error) { data, err := readLimited(c.procPath("meminfo"), maxFileBytes) if err != nil { return host.RawMemory{}, fmt.Errorf("hostcollect: read meminfo: %w", err) } return parseMeminfo(data) }