264 lines
5.5 KiB
Go
264 lines
5.5 KiB
Go
package metrics
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var ErrNotSupported = errors.New("not supported")
|
|
|
|
type CPUStat struct {
|
|
Total uint64 `json:"total"`
|
|
Idle uint64 `json:"idle"`
|
|
}
|
|
|
|
type MemInfo struct {
|
|
TotalKB uint64 `json:"total_kb"`
|
|
AvailableKB uint64 `json:"available_kb"`
|
|
FreeKB uint64 `json:"free_kb"`
|
|
BuffersKB uint64 `json:"buffers_kb"`
|
|
CachedKB uint64 `json:"cached_kb"`
|
|
}
|
|
|
|
type NetIO struct {
|
|
RxBytes uint64 `json:"rx_bytes"`
|
|
TxBytes uint64 `json:"tx_bytes"`
|
|
}
|
|
|
|
type DiskIO struct {
|
|
ReadBytes uint64 `json:"read_bytes"`
|
|
WriteBytes uint64 `json:"write_bytes"`
|
|
}
|
|
|
|
type LoadAvg struct {
|
|
One float64 `json:"one"`
|
|
Five float64 `json:"five"`
|
|
Fifteen float64 `json:"fifteen"`
|
|
}
|
|
|
|
type DiskUsage struct {
|
|
Path string `json:"path"`
|
|
TotalBytes uint64 `json:"total_bytes"`
|
|
FreeBytes uint64 `json:"free_bytes"`
|
|
UsedBytes uint64 `json:"used_bytes"`
|
|
UsedPct float64 `json:"used_pct"`
|
|
}
|
|
|
|
func ReadCPUStat() (CPUStat, error) {
|
|
b, err := os.ReadFile("/proc/stat")
|
|
if err != nil {
|
|
return CPUStat{}, err
|
|
}
|
|
return ParseProcStat(string(b))
|
|
}
|
|
|
|
func ParseProcStat(data string) (CPUStat, error) {
|
|
s := bufio.NewScanner(strings.NewReader(data))
|
|
for s.Scan() {
|
|
line := strings.TrimSpace(s.Text())
|
|
if strings.HasPrefix(line, "cpu ") {
|
|
parts := strings.Fields(line)
|
|
if len(parts) < 5 {
|
|
return CPUStat{}, errors.New("invalid cpu stat")
|
|
}
|
|
var total uint64
|
|
var idle uint64
|
|
for i := 1; i < len(parts); i++ {
|
|
v, err := strconv.ParseUint(parts[i], 10, 64)
|
|
if err != nil {
|
|
return CPUStat{}, err
|
|
}
|
|
total += v
|
|
if i == 4 || i == 5 {
|
|
idle += v
|
|
}
|
|
}
|
|
return CPUStat{Total: total, Idle: idle}, nil
|
|
}
|
|
}
|
|
if err := s.Err(); err != nil {
|
|
return CPUStat{}, err
|
|
}
|
|
return CPUStat{}, errors.New("cpu line not found")
|
|
}
|
|
|
|
func CPUUsage(prev, cur CPUStat) float64 {
|
|
if cur.Total <= prev.Total {
|
|
return 0
|
|
}
|
|
totalDelta := cur.Total - prev.Total
|
|
idleDelta := uint64(0)
|
|
if cur.Idle > prev.Idle {
|
|
idleDelta = cur.Idle - prev.Idle
|
|
}
|
|
if totalDelta == 0 {
|
|
return 0
|
|
}
|
|
used := totalDelta - idleDelta
|
|
return float64(used) / float64(totalDelta) * 100
|
|
}
|
|
|
|
func ReadMemInfo() (MemInfo, error) {
|
|
b, err := os.ReadFile("/proc/meminfo")
|
|
if err != nil {
|
|
return MemInfo{}, err
|
|
}
|
|
return ParseMemInfo(string(b))
|
|
}
|
|
|
|
func ParseMemInfo(data string) (MemInfo, error) {
|
|
out := MemInfo{}
|
|
s := bufio.NewScanner(strings.NewReader(data))
|
|
for s.Scan() {
|
|
line := strings.TrimSpace(s.Text())
|
|
if line == "" {
|
|
continue
|
|
}
|
|
parts := strings.Fields(line)
|
|
if len(parts) < 2 {
|
|
continue
|
|
}
|
|
key := strings.TrimSuffix(parts[0], ":")
|
|
val, err := strconv.ParseUint(parts[1], 10, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
switch key {
|
|
case "MemTotal":
|
|
out.TotalKB = val
|
|
case "MemAvailable":
|
|
out.AvailableKB = val
|
|
case "MemFree":
|
|
out.FreeKB = val
|
|
case "Buffers":
|
|
out.BuffersKB = val
|
|
case "Cached":
|
|
out.CachedKB = val
|
|
}
|
|
}
|
|
if err := s.Err(); err != nil {
|
|
return MemInfo{}, err
|
|
}
|
|
if out.TotalKB == 0 {
|
|
return MemInfo{}, errors.New("meminfo missing MemTotal")
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func ReadNetDev() (NetIO, error) {
|
|
b, err := os.ReadFile("/proc/net/dev")
|
|
if err != nil {
|
|
return NetIO{}, err
|
|
}
|
|
return ParseNetDev(string(b))
|
|
}
|
|
|
|
func ParseNetDev(data string) (NetIO, error) {
|
|
var out NetIO
|
|
s := bufio.NewScanner(strings.NewReader(data))
|
|
for s.Scan() {
|
|
line := strings.TrimSpace(s.Text())
|
|
if line == "" || strings.HasPrefix(line, "Inter-") || strings.HasPrefix(line, "face") {
|
|
continue
|
|
}
|
|
parts := strings.SplitN(line, ":", 2)
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
name := strings.TrimSpace(parts[0])
|
|
if name == "lo" {
|
|
continue
|
|
}
|
|
fields := strings.Fields(parts[1])
|
|
if len(fields) < 9 {
|
|
continue
|
|
}
|
|
rx, err := strconv.ParseUint(fields[0], 10, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
tx, err := strconv.ParseUint(fields[8], 10, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
out.RxBytes += rx
|
|
out.TxBytes += tx
|
|
}
|
|
if err := s.Err(); err != nil {
|
|
return NetIO{}, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func ReadDiskStats() (DiskIO, error) {
|
|
b, err := os.ReadFile("/proc/diskstats")
|
|
if err != nil {
|
|
return DiskIO{}, err
|
|
}
|
|
return ParseDiskStats(string(b))
|
|
}
|
|
|
|
func ParseDiskStats(data string) (DiskIO, error) {
|
|
var out DiskIO
|
|
s := bufio.NewScanner(strings.NewReader(data))
|
|
for s.Scan() {
|
|
line := strings.TrimSpace(s.Text())
|
|
if line == "" {
|
|
continue
|
|
}
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 11 {
|
|
continue
|
|
}
|
|
name := fields[2]
|
|
if strings.HasPrefix(name, "loop") || strings.HasPrefix(name, "ram") {
|
|
continue
|
|
}
|
|
readSectors, err := strconv.ParseUint(fields[5], 10, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
writeSectors, err := strconv.ParseUint(fields[9], 10, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
out.ReadBytes += readSectors * 512
|
|
out.WriteBytes += writeSectors * 512
|
|
}
|
|
if err := s.Err(); err != nil {
|
|
return DiskIO{}, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func ReadLoadAvg() (LoadAvg, error) {
|
|
b, err := os.ReadFile("/proc/loadavg")
|
|
if err != nil {
|
|
return LoadAvg{}, err
|
|
}
|
|
return ParseLoadAvg(string(b))
|
|
}
|
|
|
|
func ParseLoadAvg(data string) (LoadAvg, error) {
|
|
fields := strings.Fields(strings.TrimSpace(data))
|
|
if len(fields) < 3 {
|
|
return LoadAvg{}, errors.New("invalid loadavg")
|
|
}
|
|
one, err := strconv.ParseFloat(fields[0], 64)
|
|
if err != nil {
|
|
return LoadAvg{}, err
|
|
}
|
|
five, err := strconv.ParseFloat(fields[1], 64)
|
|
if err != nil {
|
|
return LoadAvg{}, err
|
|
}
|
|
fifteen, err := strconv.ParseFloat(fields[2], 64)
|
|
if err != nil {
|
|
return LoadAvg{}, err
|
|
}
|
|
return LoadAvg{One: one, Five: five, Fifteen: fifteen}, nil
|
|
}
|