feat: 添加内存监控命令,支持从 /proc/meminfo 读取内存信息

This commit is contained in:
Akizon77
2025-12-02 16:49:12 +08:00
parent 5d573cfb1a
commit 75442886c0
3 changed files with 431 additions and 18 deletions

View File

@@ -1,47 +1,243 @@
package monitoring
import (
"bufio"
"bytes"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
pkg_flags "github.com/komari-monitor/komari-agent/cmd/flags"
"github.com/shirou/gopsutil/v4/mem"
)
type RamInfo struct {
Total uint64 `json:"total"`
Used uint64 `json:"used"`
Mode string
}
func Ram() RamInfo {
raminfo := RamInfo{}
v, err := mem.VirtualMemory()
type ProcMemInfo struct {
MemTotal uint64
MemFree uint64
MemAvailable uint64
Buffers uint64
Cached uint64
SwapTotal uint64
SwapFree uint64
SwapCached uint64
Shmem uint64
SReclaimable uint64
Zswap uint64
Zswapped uint64
}
// readProcMeminfo reads /proc/meminfo and returns a filled ProcMemInfo struct
func ReadProcMeminfo() (*ProcMemInfo, error) {
file, err := os.Open("/proc/meminfo")
if err != nil {
raminfo.Total = 0
raminfo.Used = 0
return raminfo
return nil, err
}
if flags.MemoryIncludeCache {
defer file.Close()
info := &ProcMemInfo{}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) < 2 {
continue
}
key := strings.TrimSuffix(parts[0], ":")
valStr := parts[1]
val, err := strconv.ParseUint(valStr, 10, 64)
if err != nil {
continue
}
val *= 1024 // Convert kB to bytes
switch key {
case "MemTotal":
info.MemTotal = val
case "MemFree":
info.MemFree = val
case "MemAvailable":
info.MemAvailable = val
case "Buffers":
info.Buffers = val
case "Cached":
info.Cached = val
case "SwapTotal":
info.SwapTotal = val
case "SwapFree":
info.SwapFree = val
case "SwapCached":
info.SwapCached = val
case "Shmem":
info.Shmem = val
case "SReclaimable":
info.SReclaimable = val
case "Zswap":
info.Zswap = val
case "Zswapped":
info.Zswapped = val
}
}
return info, scanner.Err()
}
func GetMemHtopLike() RamInfo {
raminfo := RamInfo{Mode: "htoplike"}
if runtime.GOOS == "linux" {
info, err := ReadProcMeminfo()
if err == nil && info.MemTotal > 0 {
raminfo.Total = info.MemTotal
// htop logic:
// usedDiff = free + cached + sreclaimable + buffers
usedDiff := info.MemFree + info.Cached + info.SReclaimable + info.Buffers
if info.MemTotal >= usedDiff {
raminfo.Used = info.MemTotal - usedDiff
} else {
raminfo.Used = info.MemTotal - info.MemFree
}
// Adjust for Zswap
if info.Zswap > 0 || info.Zswapped > 0 {
if raminfo.Used > info.Zswap {
raminfo.Used -= info.Zswap
} else {
raminfo.Used = 0
}
}
return raminfo
}
}
return raminfo
}
func GetMemGopsutil() RamInfo {
raminfo := RamInfo{Mode: "gopsutil"}
v, err := mem.VirtualMemory()
if err == nil {
raminfo.Total = v.Total
raminfo.Used = v.Total - v.Free
raminfo.Used = v.Total - v.Available
}
return raminfo
}
// 这我还能干嘛大伙天天说和free显示不一样我也没办法
func CallFree() RamInfo {
raminfo := RamInfo{Mode: "callFree"}
// Only works on Linux/Unix systems
if runtime.GOOS != "linux" && runtime.GOOS != "freebsd" {
return raminfo
}
raminfo.Total = v.Total
if runtime.GOOS == "windows" || !flags.MemoryReportRawUsed {
raminfo.Used = v.Total - v.Available
} else {
raminfo.Used = v.Total - v.Free - v.Buffers - v.Cached
// Execute 'free -b' command to get memory in bytes
cmd := exec.Command("free", "-b")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return raminfo
}
// Parse the output
scanner := bufio.NewScanner(&out)
lineNum := 0
for scanner.Scan() {
line := scanner.Text()
lineNum++
// Skip the header line
if lineNum == 1 {
continue
}
// Parse the "Mem:" line
if strings.HasPrefix(line, "Mem:") {
fields := strings.Fields(line)
// Format: Mem: total used free shared buff/cache available
if len(fields) >= 3 {
total, err := strconv.ParseUint(fields[1], 10, 64)
if err == nil {
raminfo.Total = total
}
used, err := strconv.ParseUint(fields[2], 10, 64)
if err == nil {
raminfo.Used = used
}
}
break
}
}
return raminfo
}
func Ram() RamInfo {
// Use global config
if pkg_flags.GlobalConfig.MemoryIncludeCache {
v, err := mem.VirtualMemory()
if err != nil {
return RamInfo{}
}
return RamInfo{
Total: v.Total,
Used: v.Total - v.Free,
Mode: "includeCache",
}
}
if pkg_flags.GlobalConfig.MemoryReportRawUsed {
return GetMemHtopLike()
}
if runtime.GOOS == "linux" {
h := CallFree()
if h.Total > 0 {
return h
}
h = GetMemHtopLike()
if h.Total > 0 {
return h
}
}
// Default fallback
return GetMemGopsutil()
}
func Swap() RamInfo {
swapinfo := RamInfo{}
if runtime.GOOS == "linux" {
info, err := ReadProcMeminfo()
if err == nil {
swapinfo.Total = info.SwapTotal
// used = total - free - cached
// Check for underflow
usedDeductions := info.SwapFree + info.SwapCached
if info.SwapTotal >= usedDeductions {
swapinfo.Used = info.SwapTotal - usedDeductions
} else {
swapinfo.Used = info.SwapTotal - info.SwapFree
}
return swapinfo
}
}
s, err := mem.SwapMemory()
if err != nil {
swapinfo.Total = 0
swapinfo.Used = 0
} else {
swapinfo.Total = s.Total
swapinfo.Used = s.Used
return swapinfo
}
swapinfo.Total = s.Total
swapinfo.Used = s.Used
return swapinfo
}