feature: 合并系统命令

This commit is contained in:
2026-11-03 20:15:27 +08:00
parent aeeef892ba
commit 0bd7b3accd
7 changed files with 128 additions and 174 deletions
-1
View File
@@ -79,7 +79,6 @@ var builtins = map[string]Builtin{
"svc.enable": runSvcEnable,
"svc.logs": runSvcLogs,
"svc.status": runSvcStatus,
"sys.uptime": runSysUptime,
"sysinfo": runSysInfo,
"user.grant": runUserGrant,
"user.info": runUserInfo,
+113 -1
View File
@@ -8,11 +8,12 @@ import (
"runtime"
"strconv"
"strings"
"time"
"gitea.odboy.cn/odboy/kenaito-kopsc/internal/platform"
)
// runSysInfo implements `kopsc sysinfo`: show OS, CPU, memory, disk overview.
// 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()
@@ -38,6 +39,10 @@ func runSysInfo(ctx context.Context, req BuiltinRequest) error {
}
}
// 运行时间
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" {
@@ -67,6 +72,113 @@ func runSysInfo(ctx context.Context, req BuiltinRequest) error {
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 {
-133
View File
@@ -1,133 +0,0 @@
package executor
import (
"context"
"fmt"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"time"
)
// runSysUptime implements `kopsc sys uptime`: show system uptime.
func runSysUptime(ctx context.Context, req BuiltinRequest) error {
switch runtime.GOOS {
case "linux":
return sysUptimeLinux(req)
case "darwin":
return sysUptimeDarwin(ctx, req)
case "windows":
return sysUptimeWindows(ctx, req)
default:
return fmt.Errorf("不支持的操作系统: %s", runtime.GOOS)
}
}
func sysUptimeLinux(req BuiltinRequest) error {
data, err := os.ReadFile("/proc/uptime")
if err != nil {
return fmt.Errorf("读取 /proc/uptime 失败: %w", err)
}
fields := strings.Fields(string(data))
if len(fields) < 1 {
return fmt.Errorf("/proc/uptime 格式异常")
}
seconds, err := strconv.ParseFloat(fields[0], 64)
if err != nil {
return fmt.Errorf("解析 uptime 失败: %w", err)
}
bootTime := time.Now().Add(-time.Duration(seconds) * time.Second)
fmt.Fprintf(req.Stdout, "运行时间: %s\n", formatDuration(time.Duration(seconds)*time.Second))
fmt.Fprintf(req.Stdout, "启动时间: %s\n", bootTime.Format("2006-01-02 15:04:05"))
// Load average
if len(fields) >= 2 {
idle, _ := strconv.ParseFloat(fields[1], 64)
fmt.Fprintf(req.Stdout, "空闲时间: %s\n", formatDuration(time.Duration(idle)*time.Second))
}
// 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(req.Stdout, "负载均值: %s %s %s (1/5/15分钟)\n", parts[0], parts[1], parts[2])
}
}
return nil
}
func sysUptimeDarwin(ctx context.Context, req BuiltinRequest) error {
out, err := exec.CommandContext(ctx, "sysctl", "-n", "kern.boottime").Output()
if err != nil {
return fmt.Errorf("获取启动时间失败: %w", err)
}
// Output: { sec = 1234567890, usec = 123456 } Mon Jan 01 00:00:00 2024
line := string(out)
secStart := strings.Index(line, "sec = ")
if secStart < 0 {
return fmt.Errorf("无法解析 kern.boottime: %s", line)
}
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 fmt.Errorf("解析启动时间失败: %w", err)
}
bootTime := time.Unix(sec, 0)
uptime := time.Since(bootTime)
fmt.Fprintf(req.Stdout, "运行时间: %s\n", formatDuration(uptime))
fmt.Fprintf(req.Stdout, "启动时间: %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(req.Stdout, "负载均值: %s\n", strings.TrimSpace(string(out)))
}
return nil
}
func sysUptimeWindows(ctx context.Context, req BuiltinRequest) error {
// Use wmic to get boot time
out, err := exec.CommandContext(ctx, "wmic", "os", "get", "LastBootUpTime", "/value").Output()
if err != nil {
return fmt.Errorf("获取启动时间失败: %w", err)
}
line := strings.TrimSpace(string(out))
// LastBootUpTime=20240101000000.000000+480
if !strings.HasPrefix(line, "LastBootUpTime=") {
return fmt.Errorf("无法解析 wmic 输出: %s", line)
}
timeStr := strings.TrimPrefix(line, "LastBootUpTime=")
// Parse: 20240101000000.000000+480
if len(timeStr) < 14 {
return fmt.Errorf("时间格式异常: %s", timeStr)
}
bootTime, err := time.Parse("20060102150405", timeStr[:14])
if err != nil {
return fmt.Errorf("解析时间失败: %w", err)
}
uptime := time.Since(bootTime)
fmt.Fprintf(req.Stdout, "运行时间: %s\n", formatDuration(uptime))
fmt.Fprintf(req.Stdout, "启动时间: %s\n", bootTime.Format("2006-01-02 15:04:05"))
return nil
}
// 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)
}
+5 -16
View File
@@ -704,8 +704,8 @@ func Builtin() *Registry {
{
ID: "sysinfo",
Group: "sys",
Summary: "显示系统信息一览(OS、CPU、内存、磁盘)",
Action: "一次性输出操作系统、CPU 型号与核心数、内存总量与可用量、磁盘使用情况。" +
Summary: "显示系统信息一览(OS、运行时间、CPU、内存、磁盘)",
Action: "一次性输出操作系统、运行时间、CPU 型号与核心数、内存总量与可用量、磁盘使用情况。" +
"Linux 读取 /proc 与 cgroup,macOS 使用 sysctl,Windows 使用 wmic",
Variants: threePlatformBuiltins("sysinfo"),
},
@@ -837,17 +837,6 @@ func Builtin() *Registry {
Variants: threePlatformBuiltins("fw.deny"),
},
// ----- sys additions -----
{
ID: "sys.uptime",
Group: "sys",
Summary: "查看系统运行时间和启动时间",
Action: "显示系统已运行多长时间、启动时间、负载均值。" +
"Linux: /proc/uptime + /proc/loadavg;macOS: sysctl kern.boottime;" +
"Windows: wmic os get LastBootUpTime",
Variants: threePlatformBuiltins("sys.uptime"),
},
// ----- net additions -----
{
ID: "net.iface",
@@ -871,7 +860,7 @@ func Builtin() *Registry {
Summary: "简单网速测试(下载/上传)",
Action: "从 Cloudflare 下载测试文件测量下载速度,向 httpbin.org 上传数据测量上传速度。" +
"默认测试 10MB,--size 可指定大小,--download 或 --upload 可单独测试",
Usage: "[--size 大小] [--download] [--upload]",
Usage: "[--size 大小] [--download] [--upload]",
Variants: threePlatformBuiltins("net.speed"),
},
@@ -892,7 +881,7 @@ func Builtin() *Registry {
Summary: "查找空文件和空目录",
Action: "递归扫描目录,找出大小为 0 的文件和没有任何内容的目录。" +
"--files-only 只查找空文件,--dirs-only 只查找空目录",
Usage: "[路径] [--files-only] [--dirs-only]",
Usage: "[路径] [--files-only] [--dirs-only]",
Variants: threePlatformBuiltins("file.empty"),
},
@@ -904,7 +893,7 @@ func Builtin() *Registry {
Action: "显示用户的 UID、GID、组、家目录、Shell 等信息。" +
"不指定用户名时显示当前用户。" +
"Linux/macOS: id + /etc/passwd + groups;Windows: net user",
Usage: "[用户名]",
Usage: "[用户名]",
Variants: threePlatformBuiltins("user.info"),
},
}}
-1
View File
@@ -35,7 +35,6 @@ func TestBuiltinIDsStable(t *testing.T) {
"net.ping", "net.trace", "net.dns", "net.curl",
"log.search", "log.last",
"fw.status", "fw.allow", "fw.deny",
"sys.uptime",
"net.iface", "net.conn", "net.speed",
"proc.tree",
"file.empty",