273 lines
7.2 KiB
Go
273 lines
7.2 KiB
Go
package executor
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"gitea.odboy.cn/odboy/kenaito-kopsc/internal/platform"
|
|
)
|
|
|
|
// runSysInfo implements `kopsc sysinfo`: show OS, CPU, memory, disk, uptime overview.
|
|
func runSysInfo(ctx context.Context, req BuiltinRequest) error {
|
|
w := req.Stdout
|
|
res := platform.DetectResources()
|
|
|
|
fmt.Fprintf(w, "=== 系统信息 ===\n\n")
|
|
|
|
fmt.Fprintf(w, "操作系统: %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
|
if runtime.GOOS == "linux" {
|
|
if data, err := os.ReadFile("/etc/os-release"); err == nil {
|
|
distro, _ := platform.ParseOSRelease(data)
|
|
if distro != "" {
|
|
fmt.Fprintf(w, "发行版: %s\n", distro)
|
|
}
|
|
}
|
|
if hostname, err := os.Hostname(); err == nil {
|
|
fmt.Fprintf(w, "主机名: %s\n", hostname)
|
|
}
|
|
if data, err := os.ReadFile("/proc/version"); err == nil {
|
|
fields := strings.Fields(string(data))
|
|
if len(fields) >= 3 {
|
|
fmt.Fprintf(w, "内核: %s\n", fields[2])
|
|
}
|
|
}
|
|
}
|
|
|
|
// 运行时间
|
|
fmt.Fprintf(w, "\n=== 运行时间 ===\n\n")
|
|
printUptime(ctx, w)
|
|
|
|
fmt.Fprintf(w, "\n=== CPU ===\n\n")
|
|
fmt.Fprintf(w, "逻辑核心: %d\n", res.CPUs)
|
|
if runtime.GOOS == "linux" {
|
|
if model := cpuModelLinux(); model != "" {
|
|
fmt.Fprintf(w, "型号: %s\n", model)
|
|
}
|
|
} else if runtime.GOOS == "darwin" {
|
|
if model := cpuModelDarwin(ctx); model != "" {
|
|
fmt.Fprintf(w, "型号: %s\n", model)
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(w, "\n=== 内存 ===\n\n")
|
|
if res.Memory > 0 {
|
|
fmt.Fprintf(w, "总计: %s\n", humanSize(res.Memory))
|
|
fmt.Fprintf(w, "检测方式: %s\n", res.Detected)
|
|
}
|
|
if runtime.GOOS == "linux" {
|
|
if free := memFreeLinux(); free > 0 {
|
|
fmt.Fprintf(w, "可用: %s\n", humanSize(free))
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(w, "\n=== 磁盘 ===\n\n")
|
|
printDiskInfo(ctx, w)
|
|
|
|
return nil
|
|
}
|
|
|
|
// printUptime 打印系统运行时间信息
|
|
func printUptime(ctx context.Context, w interface{ Write([]byte) (int, error) }) {
|
|
switch runtime.GOOS {
|
|
case "linux":
|
|
printUptimeLinux(w)
|
|
case "darwin":
|
|
printUptimeDarwin(ctx, w)
|
|
case "windows":
|
|
printUptimeWindows(ctx, w)
|
|
}
|
|
}
|
|
|
|
func printUptimeLinux(w interface{ Write([]byte) (int, error) }) {
|
|
data, err := os.ReadFile("/proc/uptime")
|
|
if err != nil {
|
|
return
|
|
}
|
|
fields := strings.Fields(string(data))
|
|
if len(fields) < 1 {
|
|
return
|
|
}
|
|
seconds, err := strconv.ParseFloat(fields[0], 64)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
bootTime := time.Now().Add(-time.Duration(seconds) * time.Second)
|
|
fmt.Fprintf(w, "运行时间: %s\n", formatDuration(time.Duration(seconds)*time.Second))
|
|
fmt.Fprintf(w, "启动时间: %s\n", bootTime.Format("2006-01-02 15:04:05"))
|
|
|
|
// Load average from /proc/loadavg
|
|
if loadavg, err := os.ReadFile("/proc/loadavg"); err == nil {
|
|
parts := strings.Fields(string(loadavg))
|
|
if len(parts) >= 3 {
|
|
fmt.Fprintf(w, "负载均值: %s %s %s (1/5/15分钟)\n", parts[0], parts[1], parts[2])
|
|
}
|
|
}
|
|
}
|
|
|
|
func printUptimeDarwin(ctx context.Context, w interface{ Write([]byte) (int, error) }) {
|
|
out, err := exec.CommandContext(ctx, "sysctl", "-n", "kern.boottime").Output()
|
|
if err != nil {
|
|
return
|
|
}
|
|
// Output: { sec = 1234567890, usec = 123456 } Mon Jan 01 00:00:00 2024
|
|
line := string(out)
|
|
secStart := strings.Index(line, "sec = ")
|
|
if secStart < 0 {
|
|
return
|
|
}
|
|
secStr := strings.TrimSpace(line[secStart+6:])
|
|
if idx := strings.Index(secStr, ","); idx >= 0 {
|
|
secStr = secStr[:idx]
|
|
}
|
|
sec, err := strconv.ParseInt(secStr, 10, 64)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
bootTime := time.Unix(sec, 0)
|
|
uptime := time.Since(bootTime)
|
|
fmt.Fprintf(w, "运行时间: %s\n", formatDuration(uptime))
|
|
fmt.Fprintf(w, "启动时间: %s\n", bootTime.Format("2006-01-02 15:04:05"))
|
|
|
|
// Load average
|
|
if out, err := exec.CommandContext(ctx, "sysctl", "-n", "vm.loadavg").Output(); err == nil {
|
|
fmt.Fprintf(w, "负载均值: %s\n", strings.TrimSpace(string(out)))
|
|
}
|
|
}
|
|
|
|
func printUptimeWindows(ctx context.Context, w interface{ Write([]byte) (int, error) }) {
|
|
out, err := exec.CommandContext(ctx, "wmic", "os", "get", "LastBootUpTime", "/value").Output()
|
|
if err != nil {
|
|
return
|
|
}
|
|
line := strings.TrimSpace(string(out))
|
|
if !strings.HasPrefix(line, "LastBootUpTime=") {
|
|
return
|
|
}
|
|
timeStr := strings.TrimPrefix(line, "LastBootUpTime=")
|
|
if len(timeStr) < 14 {
|
|
return
|
|
}
|
|
bootTime, err := time.Parse("20060102150405", timeStr[:14])
|
|
if err != nil {
|
|
return
|
|
}
|
|
uptime := time.Since(bootTime)
|
|
fmt.Fprintf(w, "运行时间: %s\n", formatDuration(uptime))
|
|
fmt.Fprintf(w, "启动时间: %s\n", bootTime.Format("2006-01-02 15:04:05"))
|
|
}
|
|
|
|
// formatDuration formats a duration into a human-readable string.
|
|
func formatDuration(d time.Duration) string {
|
|
days := int(d.Hours()) / 24
|
|
hours := int(d.Hours()) % 24
|
|
minutes := int(d.Minutes()) % 60
|
|
|
|
if days > 0 {
|
|
return fmt.Sprintf("%d 天 %d 小时 %d 分钟", days, hours, minutes)
|
|
}
|
|
if hours > 0 {
|
|
return fmt.Sprintf("%d 小时 %d 分钟", hours, minutes)
|
|
}
|
|
return fmt.Sprintf("%d 分钟", minutes)
|
|
}
|
|
|
|
func cpuModelLinux() string {
|
|
data, err := os.ReadFile("/proc/cpuinfo")
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
for _, line := range strings.Split(string(data), "\n") {
|
|
if strings.HasPrefix(line, "model name") {
|
|
_, value, ok := strings.Cut(line, ":")
|
|
if ok {
|
|
return strings.TrimSpace(value)
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func cpuModelDarwin(ctx context.Context) string {
|
|
out, err := exec.CommandContext(ctx, "sysctl", "-n", "machdep.cpu.brand_string").Output()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(string(out))
|
|
}
|
|
|
|
func memFreeLinux() int64 {
|
|
data, err := os.ReadFile("/proc/meminfo")
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
for _, line := range strings.Split(string(data), "\n") {
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 2 {
|
|
continue
|
|
}
|
|
if fields[0] == "MemAvailable:" || fields[0] == "MemFree:" {
|
|
n, err := strconv.ParseInt(fields[1], 10, 64)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return n * 1024
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func printDiskInfo(ctx context.Context, w interface{ Write([]byte) (int, error) }) {
|
|
if runtime.GOOS == "windows" {
|
|
cmd := exec.CommandContext(ctx, "wmic", "logicaldisk", "get", "size,freespace,caption", "/format:list")
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
fmt.Fprintln(w, "无法获取磁盘信息")
|
|
return
|
|
}
|
|
parseWMICDisk(string(out), w)
|
|
return
|
|
}
|
|
|
|
cmd := exec.CommandContext(ctx, "df", "-h")
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
fmt.Fprintln(w, "无法获取磁盘信息")
|
|
return
|
|
}
|
|
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
|
|
if len(lines) == 0 {
|
|
return
|
|
}
|
|
for _, line := range lines {
|
|
fmt.Fprintln(w, line)
|
|
}
|
|
}
|
|
|
|
func parseWMICDisk(out string, w interface{ Write([]byte) (int, error) }) {
|
|
var caption, freeStr, sizeStr string
|
|
for _, line := range strings.Split(out, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if strings.HasPrefix(line, "Caption=") {
|
|
caption = strings.TrimPrefix(line, "Caption=")
|
|
} else if strings.HasPrefix(line, "FreeSpace=") {
|
|
freeStr = strings.TrimPrefix(line, "FreeSpace=")
|
|
} else if strings.HasPrefix(line, "Size=") {
|
|
sizeStr = strings.TrimPrefix(line, "Size=")
|
|
if caption != "" && sizeStr != "" {
|
|
size, _ := strconv.ParseInt(sizeStr, 10, 64)
|
|
free, _ := strconv.ParseInt(freeStr, 10, 64)
|
|
fmt.Fprintf(w, "%-6s 总计 %-10s 可用 %-10s\n", caption, humanSize(size), humanSize(free))
|
|
caption, freeStr, sizeStr = "", "", ""
|
|
}
|
|
}
|
|
}
|
|
}
|