internal/agent/hostinfo/hostinfo_darwin.go
Ref: Size: 4.3 KiB History
//go:build darwin
package hostinfo
import (
"context"
"fmt"
"time"
"golang.org/x/sys/unix"
agentexec "github.com/a73x/eitri/internal/agent/exec"
"github.com/a73x/eitri/internal/pb"
)
// systemVersionPath is macOS's identity file, osIdentity's source. Unlike the
// Linux path seams in hostinfo.go it lives with its platform: nothing untagged
// reads it, so on Linux it would be an unused variable.
var systemVersionPath = "/System/Library/CoreServices/SystemVersion.plist"
// readBootID derives an opaque per-boot token from the kern.boottime sysctl.
// BootID only needs equality across a reboot (see its doc comment), and
// boottime changes on every reboot and holds steady within one, so it
// satisfies the contract without needing a dedicated boot-id concept the way
// Linux's kernel does. "" on sysctl failure, per the package's best-effort
// contract.
func readBootID() string {
tv, err := unix.SysctlTimeval("kern.boottime")
if err != nil {
return ""
}
return fmt.Sprintf("boottime-%d.%d", tv.Sec, tv.Usec)
}
func totalMemMB() int64 {
v, err := unix.SysctlUint64("hw.memsize")
if err != nil {
return 0
}
return int64(v) / mb
}
func totalDiskGB(stateDir string) int64 {
var fs unix.Statfs_t
if err := unix.Statfs(stateDir, &fs); err != nil {
return 0
}
// Bsize is uint32 on Darwin (int64 on Linux) — convert, never multiply raw.
return int64(fs.Blocks) * int64(fs.Bsize) / gb
}
// osIdentity is Facts' Darwin source for the OS fields: macOS's own version
// plist, mapped to the same shape /etc/os-release gives on Linux.
func osIdentity() (id, pretty, version string) {
return macOSIdentity(readFile(systemVersionPath))
}
// virtSource is Facts' Darwin answer for Virt. A Mac in the fleet is bare
// metal — Virtualization.framework guests can't nest — so the answer is the
// constant "none", the same string a bare-metal Linux host reports. No
// subprocess runs: there is no Darwin systemd-detect-virt, and spawning
// something per Hello to learn nothing is the machinery this design removes.
func virtSource(_ context.Context, _ agentexec.Runner) string { return "none" }
// kernelVersion is Facts' Darwin source for Kernel: the XNU release string
// (e.g. "23.6.0") via the kern.osrelease sysctl — Darwin's equivalent of
// Linux's /proc/sys/kernel/osrelease.
func kernelVersion() string {
s, err := unix.Sysctl("kern.osrelease")
if err != nil {
return ""
}
return s
}
// cpuModel is Facts' Darwin source for CpuModel via the machdep.cpu.brand_string
// sysctl — Darwin's equivalent of Linux's /proc/cpuinfo "model name" line.
func cpuModel() string {
s, err := unix.Sysctl("machdep.cpu.brand_string")
if err != nil {
return ""
}
return s
}
// readMetrics is Metrics' Darwin implementation: uptime and load from
// sysctls, memory headroom from free pages, disk from statfs on the state dir.
// Each probe is independent — one failing sysctl zeroes its own dimension and
// nothing else.
func readMetrics(stateDir string) *pb.HostMetrics {
m := &pb.HostMetrics{}
if tv, err := unix.SysctlTimeval("kern.boottime"); err == nil {
if uptime := time.Now().Unix() - tv.Sec; uptime > 0 {
m.UptimeS = uptime
}
}
if raw, err := unix.SysctlRaw("vm.loadavg"); err == nil {
if loads, ok := decodeLoadavg(raw); ok {
m.Load1, m.Load5, m.Load15 = loads[0], loads[1], loads[2]
}
}
if avail := memAvailableMB(); avail > 0 {
m.MemAvailableMb = avail
if used := totalMemMB() - avail; used > 0 {
m.MemUsedMb = used
}
}
var fs unix.Statfs_t
if err := unix.Statfs(stateDir, &fs); err == nil {
// Bsize is uint32 on Darwin (int64 on Linux) — convert, never multiply raw.
bsize := int64(fs.Bsize)
m.DiskFreeGb = int64(fs.Bavail) * bsize / gb
m.DiskUsedGb = (int64(fs.Blocks) - int64(fs.Bfree)) * bsize / gb
}
return m
}
// memAvailableMB is Darwin's best-effort headroom: free pages only. macOS also
// reclaims inactive and purgeable memory on demand, but counting those needs
// host_statistics64 and therefore cgo, which this package does not take. The
// number under-reports headroom; it never over-reports it.
//
// Pages are 16 KiB on Apple Silicon, so the page size is read, not assumed.
func memAvailableMB() int64 {
free, err := unix.SysctlUint32("vm.page_free_count")
if err != nil {
return 0
}
pageSize, err := unix.SysctlUint32("hw.pagesize")
if err != nil {
return 0
}
return int64(free) * int64(pageSize) / mb
}