init
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
//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
|
||||
}
|
||||
Reference in New Issue
Block a user