mirror of
https://github.com/Alexander-D-Karpov/netfetch.git
synced 2026-03-16 22:07:03 +03:00
361 lines
8.1 KiB
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
|
|
}
|
|
}
|