Files
2025-12-14 15:25:31 +08:00

391 lines
9.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"bufio"
"context"
"os"
"os/exec"
"strconv"
"strings"
"time"
)
// readSystemStats 读取系统统计信息
func readSystemStats() SystemStats {
stats := SystemStats{}
// 读取进程数量
stats.ProcessCount = countProcesses()
// 读取已安装包数量和包管理器类型
stats.PackageCount, stats.PackageManager = countPackages()
// 读取系统温度
stats.Temperature = readSystemTemperature()
// 读取磁盘读写速度
stats.DiskReadSpeed, stats.DiskWriteSpeed = readDiskSpeed()
// 读取 Top 5 进程
stats.TopProcesses = readTopProcesses()
// 读取系统日志
stats.SystemLogs = readSystemLogs(10)
return stats
}
func countProcesses() int {
entries, err := os.ReadDir("/proc")
if err != nil {
return 0
}
count := 0
for _, entry := range entries {
if entry.IsDir() {
// 进程目录是数字命名的
if _, err := strconv.Atoi(entry.Name()); err == nil {
count++
}
}
}
return count
}
func countPackages() (int, string) {
// 尝试不同的包管理器
// dpkg (Debian/Ubuntu)
if _, err := exec.LookPath("dpkg"); err == nil {
cmd := exec.Command("dpkg", "-l")
out, err := cmd.Output()
if err == nil {
lines := strings.Split(string(out), "\n")
count := 0
for _, line := range lines {
if strings.HasPrefix(line, "ii ") {
count++
}
}
return count, "dpkg (apt)"
}
}
// rpm (RedHat/CentOS/Fedora)
if _, err := exec.LookPath("rpm"); err == nil {
cmd := exec.Command("rpm", "-qa")
out, err := cmd.Output()
if err == nil {
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
return len(lines), "rpm (yum/dnf)"
}
}
// pacman (Arch Linux)
if _, err := exec.LookPath("pacman"); err == nil {
cmd := exec.Command("pacman", "-Q")
out, err := cmd.Output()
if err == nil {
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
return len(lines), "pacman"
}
}
return 0, "unknown"
}
func readSystemTemperature() float64 {
var cpuTemp float64 = 0
var fallbackTemp float64 = 0
// 1. 优先读取 thermal_zone (通常是 CPU 温度)
thermalDir := "/sys/class/thermal"
entries, err := os.ReadDir(thermalDir)
if err == nil {
for _, entry := range entries {
if !strings.HasPrefix(entry.Name(), "thermal_zone") {
continue
}
tempPath := thermalDir + "/" + entry.Name() + "/temp"
if temp := readTempFromFile(tempPath); temp > 0 && temp > 20 && temp < 120 {
// thermal_zone0 通常是 CPU
if entry.Name() == "thermal_zone0" {
cpuTemp = temp
break
} else if fallbackTemp == 0 {
fallbackTemp = temp
}
}
}
}
// 2. 扫描所有 hwmon 设备,查找 CPU 温度
hwmonDir := "/sys/class/hwmon"
entries, err = os.ReadDir(hwmonDir)
if err == nil {
for _, entry := range entries {
hwmonPath := hwmonDir + "/" + entry.Name()
// 读取 name 文件,检查是否是 CPU 相关
namePath := hwmonPath + "/name"
name := strings.ToLower(strings.TrimSpace(readFirstLine(namePath)))
// 检查是否是 CPU 温度传感器
isCPU := strings.Contains(name, "cpu") ||
strings.Contains(name, "core") ||
strings.Contains(name, "k10temp") ||
strings.Contains(name, "coretemp") ||
strings.Contains(name, "zenpower")
// 尝试读取 temp1_input (通常是 CPU)
temp1Path := hwmonPath + "/temp1_input"
if temp := readTempFromFile(temp1Path); temp > 0 && temp > 20 && temp < 120 {
if isCPU {
cpuTemp = temp
break
} else if fallbackTemp == 0 {
fallbackTemp = temp
}
}
// 也尝试 temp2_input
temp2Path := hwmonPath + "/temp2_input"
if temp := readTempFromFile(temp2Path); temp > 0 && temp > 20 && temp < 120 {
if isCPU && cpuTemp == 0 {
cpuTemp = temp
} else if fallbackTemp == 0 {
fallbackTemp = temp
}
}
}
}
// 优先返回 CPU 温度,如果没有则返回其他温度
if cpuTemp > 0 {
return cpuTemp
}
return fallbackTemp
}
// readDiskSpeed 读取磁盘瞬时读写速度 (MB/s)
func readDiskSpeed() (float64, float64) {
// 第一次读取
readSectors1, writeSectors1 := getDiskSectors()
if readSectors1 == 0 && writeSectors1 == 0 {
return 0, 0
}
// 等待1秒
time.Sleep(1 * time.Second)
// 第二次读取
readSectors2, writeSectors2 := getDiskSectors()
// 计算差值(扇区数)
readDiff := readSectors2 - readSectors1
writeDiff := writeSectors2 - writeSectors1
// 扇区大小通常是 512 字节,转换为 MB/s
readSpeed := float64(readDiff) * 512 / 1024 / 1024
writeSpeed := float64(writeDiff) * 512 / 1024 / 1024
return round(readSpeed, 2), round(writeSpeed, 2)
}
func getDiskSectors() (uint64, uint64) {
f, err := os.Open("/proc/diskstats")
if err != nil {
return 0, 0
}
defer f.Close()
scanner := bufio.NewScanner(f)
var maxRead uint64 = 0
var mainDevice string
// 第一次遍历:找到读写量最大的主磁盘(通常是系统盘)
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) < 14 {
continue
}
deviceName := fields[2]
// 跳过分区(分区名通常包含数字,如 sda1, vda1, nvme0n1p1
if strings.ContainsAny(deviceName, "0123456789") &&
!strings.HasPrefix(deviceName, "nvme") &&
!strings.HasPrefix(deviceName, "loop") {
continue
}
// 跳过虚拟设备
if strings.HasPrefix(deviceName, "loop") ||
strings.HasPrefix(deviceName, "ram") ||
strings.HasPrefix(deviceName, "zram") {
continue
}
readSectors, _ := strconv.ParseUint(fields[5], 10, 64)
// 选择读写量最大的作为主磁盘
if readSectors > maxRead {
maxRead = readSectors
mainDevice = deviceName
}
}
// 第二次遍历:读取主磁盘的数据
f.Close()
f, err = os.Open("/proc/diskstats")
if err != nil {
return 0, 0
}
scanner = bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) < 14 {
continue
}
if fields[2] == mainDevice {
readSectors, _ := strconv.ParseUint(fields[5], 10, 64)
writeSectors, _ := strconv.ParseUint(fields[9], 10, 64)
return readSectors, writeSectors
}
}
// 如果没找到,尝试常见的设备名(向后兼容)
f.Close()
f, err = os.Open("/proc/diskstats")
if err != nil {
return 0, 0
}
scanner = bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) < 14 {
continue
}
deviceName := fields[2]
if deviceName == "sda" || deviceName == "vda" || deviceName == "nvme0n1" {
readSectors, _ := strconv.ParseUint(fields[5], 10, 64)
writeSectors, _ := strconv.ParseUint(fields[9], 10, 64)
return readSectors, writeSectors
}
}
return 0, 0
}
// readTopProcesses 读取 Top 5 进程 (按 CPU 使用率)
func readTopProcesses() []ProcessInfo {
processes := []ProcessInfo{}
// 读取系统总内存
memInfo, _ := readMemory()
totalMemGB := float64(memInfo.TotalBytes) / 1024 / 1024 / 1024
// 使用 ps 命令获取进程信息,添加超时控制
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "ps", "aux", "--sort=-%cpu", "--no-headers")
out, err := cmd.Output()
if err != nil {
return processes
}
lines := strings.Split(string(out), "\n")
count := 0
for _, line := range lines {
if count >= 5 { // 只取前5个
break
}
line = strings.TrimSpace(line)
if line == "" {
continue
}
fields := strings.Fields(line)
if len(fields) < 11 {
continue
}
pid, _ := strconv.Atoi(fields[1])
cpu, _ := strconv.ParseFloat(fields[2], 64)
mem, _ := strconv.ParseFloat(fields[3], 64)
// 计算内存MB数
memoryMB := (mem / 100) * totalMemGB * 1024
// 命令可能包含空格从第11个字段开始拼接
command := strings.Join(fields[10:], " ")
if len(command) > 50 {
command = command[:50] + "..."
}
processes = append(processes, ProcessInfo{
PID: pid,
Name: fields[10],
CPU: round(cpu, 1),
Memory: round(mem, 1),
MemoryMB: round(memoryMB, 1),
Command: command,
})
count++
}
return processes
}
// readSystemLogs 读取系统最新日志
func readSystemLogs(count int) []string {
logs := []string{}
// 尝试使用 journalctl 读取系统日志
if _, err := exec.LookPath("journalctl"); err == nil {
cmd := exec.Command("journalctl", "-n", strconv.Itoa(count), "--no-pager", "-o", "short")
out, err := cmd.Output()
if err == nil {
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
for _, line := range lines {
if line != "" {
logs = append(logs, line)
}
}
return logs
}
}
// 如果 journalctl 不可用,尝试读取 /var/log/syslog 或 /var/log/messages
logFiles := []string{"/var/log/syslog", "/var/log/messages"}
for _, logFile := range logFiles {
f, err := os.Open(logFile)
if err != nil {
continue
}
defer f.Close()
// 读取最后几行
var lines []string
scanner := bufio.NewScanner(f)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
// 取最后count行
start := len(lines) - count
if start < 0 {
start = 0
}
logs = lines[start:]
break
}
return logs
}