124 lines
2.7 KiB
Go
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
|
|
}
|