a73x

internal/agent/hostinfo/hostinfo_linux.go

Ref:   Size: 2.8 KiB   History

//go:build linux

package hostinfo

import (
	"context"
	"os"
	"strings"
	"syscall"

	agentexec "github.com/a73x/eitri/internal/agent/exec"
	"github.com/a73x/eitri/internal/pb"
)

// Seams — syscall-typed, so unlike the plain path vars in hostinfo.go these
// can only live where the types they close over exist.
var (
	sysinfoFn = syscall.Sysinfo
	statfsFn  = syscall.Statfs
)

func readBootID() string {
	b, err := os.ReadFile(bootIDPath)
	if err != nil {
		return ""
	}
	return strings.TrimSpace(string(b))
}

func totalMemMB() int64 {
	var si syscall.Sysinfo_t
	if err := sysinfoFn(&si); err != nil {
		return 0
	}
	return int64(si.Totalram) * int64(si.Unit) / mb
}

func totalDiskGB(stateDir string) int64 {
	var fs syscall.Statfs_t
	if err := statfsFn(stateDir, &fs); err != nil {
		return 0
	}
	// Bsize is int64 on Linux and uint32 on Darwin — convert, never multiply raw.
	return int64(fs.Blocks) * int64(fs.Bsize) / gb
}

// osIdentity is Facts' Linux source for the OS fields: /etc/os-release.
func osIdentity() (id, pretty, version string) {
	return parseOSRelease(readFile(osReleasePath))
}

// virtSource is Facts' Linux source for Virt: systemd-detect-virt, through the
// injected runner.
func virtSource(ctx context.Context, run agentexec.Runner) string {
	return detectVirt(ctx, run)
}

// kernelVersion is Facts' Linux source for Kernel: the running release string
// from /proc/sys/kernel/osrelease.
func kernelVersion() string { return strings.TrimSpace(readFile(kernelPath)) }

// cpuModel is Facts' Linux source for CpuModel: the first "model name" line
// in /proc/cpuinfo.
func cpuModel() string { return parseCPUModel(readFile(cpuInfoPath)) }

// readMetrics is Metrics' Linux implementation.
func readMetrics(stateDir string) *pb.HostMetrics {
	m := &pb.HostMetrics{}

	var si syscall.Sysinfo_t
	if err := sysinfoFn(&si); err == nil {
		m.UptimeS = si.Uptime
		m.Load1 = float64(si.Loads[0]) / loadScale
		m.Load5 = float64(si.Loads[1]) / loadScale
		m.Load15 = float64(si.Loads[2]) / loadScale
		totalMB := int64(si.Totalram) * int64(si.Unit) / mb
		availMB := memAvailableMB(&si)
		m.MemAvailableMb = availMB
		if used := totalMB - availMB; used > 0 {
			m.MemUsedMb = used
		}
	}

	var fs syscall.Statfs_t
	if err := statfsFn(stateDir, &fs); err == nil {
		bsize := int64(fs.Bsize)
		m.DiskFreeGb = int64(fs.Bavail) * bsize / gb
		m.DiskUsedGb = (int64(fs.Blocks) - int64(fs.Bfree)) * bsize / gb
	}
	return m
}

// memAvailableMB prefers /proc/meminfo MemAvailable (accounts for reclaimable
// cache — the honest headroom number); falls back to Sysinfo Free+Buffer, which
// undercounts cache, when meminfo is unreadable.
func memAvailableMB(si *syscall.Sysinfo_t) int64 {
	if kb, ok := parseMemAvailableKB(readFile(memInfoPath)); ok {
		return kb / 1024
	}
	return int64(si.Freeram+si.Bufferram) * int64(si.Unit) / mb
}