//go:build linux package runtime import ( "bufio" "context" "fmt" "os" "strconv" "strings" "time" ) const cpuSampleInterval = 100 * time.Millisecond func hostCPUPercent(ctx context.Context) (float64, error) { first, err := readLinuxCPUStat() if err != nil { return 0, err } timer := time.NewTimer(cpuSampleInterval) defer timer.Stop() select { case <-ctx.Done(): return 0, ctx.Err() case <-timer.C: } second, err := readLinuxCPUStat() if err != nil { return 0, err } totalDelta := second.total - first.total busyDelta := second.busy - first.busy if totalDelta == 0 || busyDelta > totalDelta { return 0, fmt.Errorf("CPU counters did not advance") } return clampMetricPercent(100 * float64(busyDelta) / float64(totalDelta)), nil } type linuxCPUStat struct { total uint64 busy uint64 } func readLinuxCPUStat() (linuxCPUStat, error) { file, err := os.Open("/proc/stat") if err != nil { return linuxCPUStat{}, err } defer file.Close() line, err := bufio.NewReader(file).ReadString('\n') if err != nil { return linuxCPUStat{}, err } fields := strings.Fields(line) if len(fields) < 5 || fields[0] != "cpu" { return linuxCPUStat{}, fmt.Errorf("/proc/stat CPU row is invalid") } values := make([]uint64, len(fields)-1) for index, field := range fields[1:] { value, parseErr := strconv.ParseUint(field, 10, 64) if parseErr != nil { return linuxCPUStat{}, fmt.Errorf("parse /proc/stat CPU counter: %w", parseErr) } values[index] = value } var total uint64 for _, value := range values { total += value } idle := values[3] if len(values) > 4 { idle += values[4] } if idle > total { return linuxCPUStat{}, fmt.Errorf("/proc/stat idle counter exceeds total") } return linuxCPUStat{total: total, busy: total - idle}, nil } func hostMemoryPercent() (float64, error) { file, err := os.Open("/proc/meminfo") if err != nil { return 0, err } defer file.Close() var total, available uint64 scanner := bufio.NewScanner(file) for scanner.Scan() { fields := strings.Fields(scanner.Text()) if len(fields) < 2 { continue } value, parseErr := strconv.ParseUint(fields[1], 10, 64) if parseErr != nil { return 0, fmt.Errorf("parse /proc/meminfo: %w", parseErr) } switch fields[0] { case "MemTotal:": total = value case "MemAvailable:": available = value } } if err := scanner.Err(); err != nil { return 0, err } if total == 0 || available > total { return 0, fmt.Errorf("/proc/meminfo memory counters are invalid") } return clampMetricPercent(100 * float64(total-available) / float64(total)), nil } func clampMetricPercent(value float64) float64 { if value < 0 { return 0 } if value > 100 { return 100 } return value }