144 lines
3.3 KiB
Go
144 lines
3.3 KiB
Go
//go:build linux
|
|
|
|
package metrics
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func readCPUTimes(c *Collector) (cpuTimes, error) {
|
|
return readProcStat(filepath.Join(c.procRoot, "stat"))
|
|
}
|
|
|
|
func (c *Collector) memoryPercent() (float64, error) {
|
|
if value, ok := c.cgroupMemoryPercent(); ok {
|
|
return value, nil
|
|
}
|
|
return memInfoPercent(filepath.Join(c.procRoot, "meminfo"))
|
|
}
|
|
|
|
func (c *Collector) cpuTempValue() (float64, error) {
|
|
return cpuTemp(c.sysRoot)
|
|
}
|
|
|
|
func (c *Collector) ioLoadValue() (float64, error) {
|
|
return ioLoad(c.procRoot)
|
|
}
|
|
|
|
func readProcStat(path string) (cpuTimes, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return cpuTimes{}, err
|
|
}
|
|
fields := strings.Fields(strings.SplitN(string(data), "\n", 2)[0])
|
|
if len(fields) < 8 || fields[0] != "cpu" {
|
|
return cpuTimes{}, errors.New("invalid /proc/stat cpu line")
|
|
}
|
|
var total uint64
|
|
var idle uint64
|
|
for i, field := range fields[1:] {
|
|
v, err := strconv.ParseUint(field, 10, 64)
|
|
if err != nil {
|
|
return cpuTimes{}, err
|
|
}
|
|
total += v
|
|
if i == 3 || i == 4 {
|
|
idle += v
|
|
}
|
|
}
|
|
return cpuTimes{total: total, idle: idle}, nil
|
|
}
|
|
|
|
func (c *Collector) cgroupMemoryPercent() (float64, bool) {
|
|
usage, err := readUintFile(filepath.Join(c.cgroupRoot, "memory.current"))
|
|
if err != nil {
|
|
return 0, false
|
|
}
|
|
maxRaw, err := os.ReadFile(filepath.Join(c.cgroupRoot, "memory.max"))
|
|
if err != nil {
|
|
return 0, false
|
|
}
|
|
maxText := strings.TrimSpace(string(maxRaw))
|
|
if maxText == "max" {
|
|
return 0, false
|
|
}
|
|
limit, err := strconv.ParseUint(maxText, 10, 64)
|
|
if err != nil || limit == 0 {
|
|
return 0, false
|
|
}
|
|
return float64(usage) / float64(limit) * 100, true
|
|
}
|
|
|
|
func memInfoPercent(path string) (float64, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
values := map[string]uint64{}
|
|
scanner := bufio.NewScanner(strings.NewReader(string(data)))
|
|
for scanner.Scan() {
|
|
fields := strings.Fields(scanner.Text())
|
|
if len(fields) < 2 {
|
|
continue
|
|
}
|
|
key := strings.TrimSuffix(fields[0], ":")
|
|
v, err := strconv.ParseUint(fields[1], 10, 64)
|
|
if err == nil {
|
|
values[key] = v
|
|
}
|
|
}
|
|
total := values["MemTotal"]
|
|
if total == 0 {
|
|
return 0, errors.New("MemTotal missing")
|
|
}
|
|
available, ok := values["MemAvailable"]
|
|
if !ok {
|
|
available = values["MemFree"]
|
|
}
|
|
return (1 - float64(available)/float64(total)) * 100, nil
|
|
}
|
|
|
|
func cpuTemp(sysRoot string) (float64, error) {
|
|
paths, err := filepath.Glob(filepath.Join(sysRoot, "class/thermal/thermal_zone*/temp"))
|
|
if err != nil || len(paths) == 0 {
|
|
return 0, errors.New("cpu temperature unavailable")
|
|
}
|
|
for _, path := range paths {
|
|
raw, err := readUintFile(path)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
value := float64(raw)
|
|
if value > 1000 {
|
|
value /= 1000
|
|
}
|
|
return value, nil
|
|
}
|
|
return 0, errors.New("cpu temperature unavailable")
|
|
}
|
|
|
|
func ioLoad(procRoot string) (float64, error) {
|
|
data, err := os.ReadFile(filepath.Join(procRoot, "loadavg"))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
fields := strings.Fields(string(data))
|
|
if len(fields) == 0 {
|
|
return 0, errors.New("loadavg missing")
|
|
}
|
|
return strconv.ParseFloat(fields[0], 64)
|
|
}
|
|
|
|
func readUintFile(path string) (uint64, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
|
|
}
|