diff --git a/agent/internal/inventory/collect_linux.go b/agent/internal/inventory/collect_linux.go new file mode 100644 index 0000000..39d0daf --- /dev/null +++ b/agent/internal/inventory/collect_linux.go @@ -0,0 +1,157 @@ +//go:build linux + +package inventory + +import ( + "bufio" + "os" + "strconv" + "strings" + "syscall" + "time" + + "github.com/mrhid6/vantage/agent/internal/grpc/pb" +) + +func collect(r *pb.InventoryReport, includeStatic bool) { + r.CPU.UsagePct = cpuUsage() + r.CPU.Load1 = load1() + memTotal, memAvail, swapTotal, swapFree := meminfo() + if memTotal > memAvail { + r.Memory.UsedBytes = memTotal - memAvail + } + if swapTotal > swapFree { + r.SwapUsed = swapTotal - swapFree + } + if includeStatic { + r.Memory.TotalBytes = memTotal + r.SwapTotal = swapTotal + r.CPU.Model, r.CPU.Cores = cpuStatic() + r.Kernel = kernel() + r.Partitions = partitions() + } +} + +func readProc(path string) string { b, _ := os.ReadFile(path); return string(b) } + +func cpuSample() (idle, total uint64) { + f, err := os.Open("/proc/stat") + if err != nil { + return + } + defer f.Close() + sc := bufio.NewScanner(f) + if sc.Scan() { + fields := strings.Fields(sc.Text()) // cpu user nice system idle iowait ... + for i, v := range fields[1:] { + n, _ := strconv.ParseUint(v, 10, 64) + total += n + if i == 3 { // idle + idle = n + } + } + } + return +} + +func cpuUsage() float64 { + i1, t1 := cpuSample() + time.Sleep(100 * time.Millisecond) + i2, t2 := cpuSample() + dt := float64(t2 - t1) + if dt <= 0 { + return 0 + } + return (1 - float64(i2-i1)/dt) * 100 +} + +func load1() float64 { + fields := strings.Fields(readProc("/proc/loadavg")) + if len(fields) > 0 { + v, _ := strconv.ParseFloat(fields[0], 64) + return v + } + return 0 +} + +func meminfo() (total, avail, swapTotal, swapFree uint64) { + f, err := os.Open("/proc/meminfo") + if err != nil { + return + } + defer f.Close() + sc := bufio.NewScanner(f) + for sc.Scan() { + fields := strings.Fields(sc.Text()) + if len(fields) < 2 { + continue + } + kb, _ := strconv.ParseUint(fields[1], 10, 64) + b := kb * 1024 + switch strings.TrimSuffix(fields[0], ":") { + case "MemTotal": + total = b + case "MemAvailable": + avail = b + case "SwapTotal": + swapTotal = b + case "SwapFree": + swapFree = b + } + } + return +} + +func cpuStatic() (model string, cores int) { + f, err := os.Open("/proc/cpuinfo") + if err != nil { + return + } + defer f.Close() + sc := bufio.NewScanner(f) + for sc.Scan() { + line := sc.Text() + if strings.HasPrefix(line, "processor") { + cores++ + } else if strings.HasPrefix(line, "model name") && model == "" { + if i := strings.Index(line, ":"); i >= 0 { + model = strings.TrimSpace(line[i+1:]) + } + } + } + return +} + +func kernel() string { + return strings.TrimSpace(readProc("/proc/sys/kernel/osrelease")) +} + +func partitions() []pb.PartitionReport { + allowed := map[string]bool{"ext4": true, "xfs": true, "btrfs": true, "zfs": true, "vfat": true, "ntfs": true, "ext3": true} + f, err := os.Open("/proc/mounts") + if err != nil { + return nil + } + defer f.Close() + var out []pb.PartitionReport + seen := map[string]bool{} + sc := bufio.NewScanner(f) + for sc.Scan() { + fields := strings.Fields(sc.Text()) + if len(fields) < 3 || !allowed[fields[2]] || seen[fields[1]] { + continue + } + seen[fields[1]] = true + var st syscall.Statfs_t + if syscall.Statfs(fields[1], &st) != nil { + continue + } + total := st.Blocks * uint64(st.Bsize) + free := st.Bavail * uint64(st.Bsize) + out = append(out, pb.PartitionReport{ + Device: fields[0], Mountpoint: fields[1], Fstype: fields[2], + TotalBytes: total, UsedBytes: total - free, + }) + } + return out +} diff --git a/agent/internal/inventory/collect_other.go b/agent/internal/inventory/collect_other.go new file mode 100644 index 0000000..a293e32 --- /dev/null +++ b/agent/internal/inventory/collect_other.go @@ -0,0 +1,8 @@ +//go:build !linux + +package inventory + +import "github.com/mrhid6/vantage/agent/internal/grpc/pb" + +// collect is a no-op best-effort stub on non-Linux platforms. +func collect(r *pb.InventoryReport, includeStatic bool) {} diff --git a/agent/internal/inventory/inventory.go b/agent/internal/inventory/inventory.go new file mode 100644 index 0000000..238c29d --- /dev/null +++ b/agent/internal/inventory/inventory.go @@ -0,0 +1,11 @@ +package inventory + +import "github.com/mrhid6/vantage/agent/internal/grpc/pb" + +// Collect gathers metrics always and static hardware info when includeStatic. +// Platform specifics are provided by collect_linux.go / collect_other.go. +func Collect(includeStatic bool) *pb.InventoryReport { + r := &pb.InventoryReport{IncludeStatic: includeStatic, CPU: &pb.CPUReport{}, Memory: &pb.MemReport{}} + collect(r, includeStatic) + return r +}