netfetch/internal/collector/memory.go

361 lines
8.1 KiB
Go

package collector
import (
"bufio"
"netfetch/internal/model"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
)
func (c *Collector) collectMemory() interface{} {
c.mutex.Lock()
defer c.mutex.Unlock()
switch runtime.GOOS {
case "linux":
collectMemoryLinux(c.info)
case "darwin":
collectMemoryDarwin(c.info)
case "windows":
collectMemoryWindows(c.info)
case "freebsd", "openbsd", "netbsd":
collectMemoryBSD(c.info)
}
return c.info.Memory
}
func collectMemoryLinux(info *model.SystemInfo) {
memInfo := parseMemInfo("/proc/meminfo")
total := memInfo["MemTotal"]
memFree := memInfo["MemFree"]
buffers := memInfo["Buffers"]
cached := memInfo["Cached"]
sReclaimable := memInfo["SReclaimable"]
shmem := memInfo["Shmem"]
available := memInfo["MemAvailable"]
if available == 0 {
available = memFree + buffers + cached + sReclaimable - shmem
}
used := total - available
if used < 0 || used > total {
used = total - memFree - buffers - cached
}
info.Memory = &model.MemoryInfo{
Total: total,
Used: used,
Free: available,
}
swapTotal := memInfo["SwapTotal"]
swapFree := memInfo["SwapFree"]
swapCached := memInfo["SwapCached"]
if swapTotal > 0 {
swapUsed := swapTotal - swapFree - swapCached
info.Swap = &model.SwapInfo{
Total: swapTotal,
Used: swapUsed,
Free: swapFree,
}
}
if zswapData, err := os.ReadFile("/sys/module/zswap/parameters/enabled"); err == nil {
if strings.TrimSpace(string(zswapData)) == "Y" {
if poolSize, err := os.ReadFile("/sys/kernel/debug/zswap/pool_total_size"); err == nil {
if size, err := strconv.ParseUint(strings.TrimSpace(string(poolSize)), 10, 64); err == nil {
if info.Swap != nil {
info.Swap.Used += size
}
}
}
}
}
}
func parseMemInfo(filePath string) map[string]uint64 {
file, err := os.Open(filePath)
if err != nil {
return make(map[string]uint64)
}
defer file.Close()
memInfo := make(map[string]uint64)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
key := strings.TrimSuffix(fields[0], ":")
value, err := strconv.ParseUint(fields[1], 10, 64)
if err != nil {
continue
}
memInfo[key] = value * 1024
}
return memInfo
}
func collectMemoryDarwin(info *model.SystemInfo) {
total := getSysctlUint64("hw.memsize")
pageSize := uint64(4096)
if ps := getSysctlUint64("hw.pagesize"); ps > 0 {
pageSize = ps
}
out, err := exec.Command("vm_stat").Output()
if err != nil {
info.Memory = &model.MemoryInfo{
Total: total,
Used: 0,
Free: 0,
}
return
}
vmStats := make(map[string]uint64)
lines := strings.Split(string(out), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if idx := strings.Index(line, ":"); idx != -1 {
key := strings.TrimSpace(line[:idx])
valueStr := strings.TrimSpace(strings.TrimSuffix(line[idx+1:], "."))
if value, err := strconv.ParseUint(valueStr, 10, 64); err == nil {
vmStats[key] = value
}
}
}
wired := vmStats["Pages wired down"] * pageSize
active := vmStats["Pages active"] * pageSize
compressed := vmStats["Pages occupied by compressor"] * pageSize
appMemory := vmStats["Anonymous pages"] * pageSize
used := wired + active + compressed
if appMemory > 0 {
used = wired + appMemory + compressed
}
if used > total {
used = total - (vmStats["Pages free"]+vmStats["Pages inactive"]+vmStats["Pages speculative"])*pageSize
}
free := total - used
info.Memory = &model.MemoryInfo{
Total: total,
Used: used,
Free: free,
}
swapOut, err := exec.Command("sysctl", "-n", "vm.swapusage").Output()
if err == nil {
swapStr := string(swapOut)
parts := strings.Fields(swapStr)
var swapTotal, swapUsed, swapFree uint64
for i, part := range parts {
if part == "total" && i > 0 {
swapTotal = parseSwapValue(parts[i-1])
} else if part == "used" && i > 0 {
swapUsed = parseSwapValue(parts[i-1])
} else if part == "free" && i > 0 {
swapFree = parseSwapValue(parts[i-1])
}
}
if swapTotal > 0 {
info.Swap = &model.SwapInfo{
Total: swapTotal,
Used: swapUsed,
Free: swapFree,
}
}
}
}
func parseSwapValue(s string) uint64 {
s = strings.TrimSuffix(s, "M")
s = strings.TrimSuffix(s, "G")
s = strings.TrimSuffix(s, "K")
multiplier := uint64(1)
original := s
if strings.HasSuffix(original, "G") || strings.Contains(original, "G") {
multiplier = 1024 * 1024 * 1024
} else if strings.HasSuffix(original, "M") || strings.Contains(original, "M") {
multiplier = 1024 * 1024
} else if strings.HasSuffix(original, "K") || strings.Contains(original, "K") {
multiplier = 1024
}
value, err := strconv.ParseFloat(s, 64)
if err != nil {
return 0
}
return uint64(value * float64(multiplier))
}
func getSysctlUint64(key string) uint64 {
out, err := exec.Command("sysctl", "-n", key).Output()
if err != nil {
return 0
}
val, err := strconv.ParseUint(strings.TrimSpace(string(out)), 10, 64)
if err != nil {
return 0
}
return val
}
func collectMemoryWindows(info *model.SystemInfo) {
out, err := exec.Command("wmic", "OS", "get", "TotalVisibleMemorySize,FreePhysicalMemory", "/format:list").Output()
if err != nil {
info.Memory = &model.MemoryInfo{
Total: 0,
Used: 0,
Free: 0,
}
return
}
var total, free uint64
lines := strings.Split(string(out), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "TotalVisibleMemorySize=") {
valStr := strings.TrimPrefix(line, "TotalVisibleMemorySize=")
if val, err := strconv.ParseUint(valStr, 10, 64); err == nil {
total = val * 1024
}
} else if strings.HasPrefix(line, "FreePhysicalMemory=") {
valStr := strings.TrimPrefix(line, "FreePhysicalMemory=")
if val, err := strconv.ParseUint(valStr, 10, 64); err == nil {
free = val * 1024
}
}
}
used := total - free
info.Memory = &model.MemoryInfo{
Total: total,
Used: used,
Free: free,
}
out, err = exec.Command("wmic", "PAGEFILE", "get", "AllocatedBaseSize,CurrentUsage", "/format:list").Output()
if err != nil {
return
}
var swapTotal, swapUsed uint64
lines = strings.Split(string(out), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "AllocatedBaseSize=") {
valStr := strings.TrimPrefix(line, "AllocatedBaseSize=")
if val, err := strconv.ParseUint(valStr, 10, 64); err == nil {
swapTotal = val * 1024 * 1024
}
} else if strings.HasPrefix(line, "CurrentUsage=") {
valStr := strings.TrimPrefix(line, "CurrentUsage=")
if val, err := strconv.ParseUint(valStr, 10, 64); err == nil {
swapUsed = val * 1024 * 1024
}
}
}
if swapTotal > 0 {
info.Swap = &model.SwapInfo{
Total: swapTotal,
Used: swapUsed,
Free: swapTotal - swapUsed,
}
}
}
func collectMemoryBSD(info *model.SystemInfo) {
total := getSysctlUint64("hw.physmem")
pageSize := getSysctlUint64("hw.pagesize")
if pageSize == 0 {
pageSize = 4096
}
freePages := getSysctlUint64("vm.stats.vm.v_free_count")
inactivePages := getSysctlUint64("vm.stats.vm.v_inactive_count")
cachePages := getSysctlUint64("vm.stats.vm.v_cache_count")
free := (freePages + inactivePages + cachePages) * pageSize
used := total - free
if used > total {
used = total - freePages*pageSize
}
info.Memory = &model.MemoryInfo{
Total: total,
Used: used,
Free: free,
}
out, err := exec.Command("swapctl", "-sk").Output()
if err != nil {
out, err = exec.Command("swapinfo", "-k").Output()
}
if err != nil {
return
}
lines := strings.Split(string(out), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "Total") || strings.HasPrefix(line, "Device") {
continue
}
fields := strings.Fields(line)
if len(fields) < 3 {
continue
}
var swapTotal, swapUsed uint64
if val, err := strconv.ParseUint(fields[1], 10, 64); err == nil {
swapTotal = val * 1024
}
if val, err := strconv.ParseUint(fields[2], 10, 64); err == nil {
swapUsed = val * 1024
}
if swapTotal > 0 {
info.Swap = &model.SwapInfo{
Total: swapTotal,
Used: swapUsed,
Free: swapTotal - swapUsed,
}
}
break
}
}