package memory import ( "bufio" "os" "strconv" "strings" "bugs.watch/agent/internal/aggregate" ) type Stats struct { TotalKB uint64 AvailableKB uint64 } // Read reads MemTotal and MemAvailable from /proc/meminfo. // Values are in KB. func Read() (Stats, error) { f, err := os.Open("/proc/meminfo") if err != nil { return Stats{}, err } defer f.Close() var total uint64 var available uint64 scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "MemTotal:") { fields := strings.Fields(line) if len(fields) >= 2 { if v, err := strconv.ParseUint(fields[1], 10, 64); err == nil { total = v } } continue } if strings.HasPrefix(line, "MemAvailable:") { fields := strings.Fields(line) if len(fields) >= 2 { if v, err := strconv.ParseUint(fields[1], 10, 64); err == nil { available = v } } continue } } if err := scanner.Err(); err != nil { return Stats{}, err } return Stats{ TotalKB: total, AvailableKB: available, }, nil } // UsedPercent returns used memory percentage based on MemTotal and MemAvailable. func UsedPercent(s Stats) float64 { if s.TotalKB == 0 { return 0 } used := s.TotalKB - s.AvailableKB return (float64(used) / float64(s.TotalKB)) * 100.0 } // Collector implements a stateless memory usage collector. type Collector struct{} func New() *Collector { return &Collector{} } func (c *Collector) Name() string { return "memory" } func (c *Collector) Collect(window *aggregate.Window) { stats, err := Read() if err != nil { return } window.AddMemory(UsedPercent(stats)) }