Files
run/runtime/metrics_host_linux.go
T
2026-08-26 09:56:43 +08:00

124 lines
2.7 KiB
Go

//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
}