- file:findbig 按大小、findrecent 按修改时间、findname 按名称查找文件 - svc:systemctl / launchctl / sc 统一接口,status、enable、disable、logs - archive:按扩展名自动识别格式并解压,支持 tar.gz/tgz/tar.bz2/tar.xz/tar/zip/gz - sysinfo:OS、CPU、内存、磁盘一览 - log:journalctl / log show / wevtutil 搜索关键字与查看最近日志 - fw:ufw / firewalld / iptables / ALF / netsh 统一接口,status、allow、deny - net 新增 ping、trace(mtr 优先)、dns(dig 优先)、curl HTTP 诊断 - proc 新增 topmem、topcpu、killname - 危险操作(svc enable/disable、proc killname、fw allow/deny)需 --yes 确认 - 注册表、executor 派发表、cli 分组说明与 README 同步更新
304 lines
7.8 KiB
Go
304 lines
7.8 KiB
Go
package executor
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// runProcTopMem implements `kopsc proc topmem`: show top N processes by memory.
|
|
func runProcTopMem(ctx context.Context, req BuiltinRequest) error {
|
|
opts, err := parseProcTopArgs(req.Args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
procs, err := listProcessesWithMem(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sort.Slice(procs, func(i, j int) bool { return procs[i].mem > procs[j].mem })
|
|
printProcTop(req, procs, opts.count, "内存")
|
|
return nil
|
|
}
|
|
|
|
// runProcTopCPU implements `kopsc proc topcpu`: show top N processes by CPU.
|
|
func runProcTopCPU(ctx context.Context, req BuiltinRequest) error {
|
|
opts, err := parseProcTopArgs(req.Args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
procs, err := listProcessesWithCPU(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sort.Slice(procs, func(i, j int) bool { return procs[i].cpu > procs[j].cpu })
|
|
printProcTop(req, procs, opts.count, "CPU")
|
|
return nil
|
|
}
|
|
|
|
type procTopOptions struct {
|
|
count int
|
|
}
|
|
|
|
func parseProcTopArgs(args []string) (procTopOptions, error) {
|
|
opts := procTopOptions{count: 10}
|
|
for i := 0; i < len(args); i++ {
|
|
arg := args[i]
|
|
switch {
|
|
case arg == "--":
|
|
remaining := args[i+1:]
|
|
if len(remaining) > 0 {
|
|
n, err := strconv.Atoi(remaining[0])
|
|
if err != nil || n < 1 {
|
|
return opts, fmt.Errorf("数量需要正整数,收到 %q", remaining[0])
|
|
}
|
|
opts.count = n
|
|
}
|
|
i = len(args)
|
|
case arg == "-n" || arg == "--count":
|
|
if i+1 >= len(args) {
|
|
return opts, fmt.Errorf("%s 缺少数量", arg)
|
|
}
|
|
i++
|
|
n, err := strconv.Atoi(args[i])
|
|
if err != nil || n < 1 {
|
|
return opts, fmt.Errorf("--count 需要正整数,收到 %q", args[i])
|
|
}
|
|
opts.count = n
|
|
case strings.HasPrefix(arg, "--count="):
|
|
n, err := strconv.Atoi(strings.TrimPrefix(arg, "--count="))
|
|
if err != nil || n < 1 {
|
|
return opts, fmt.Errorf("--count 需要正整数")
|
|
}
|
|
opts.count = n
|
|
case strings.HasPrefix(arg, "-") && arg != "-":
|
|
return opts, fmt.Errorf("未知选项 %q", arg)
|
|
default:
|
|
n, err := strconv.Atoi(arg)
|
|
if err != nil || n < 1 {
|
|
return opts, fmt.Errorf("数量需要正整数,收到 %q", arg)
|
|
}
|
|
opts.count = n
|
|
}
|
|
}
|
|
return opts, nil
|
|
}
|
|
|
|
type procEntry struct {
|
|
PID int
|
|
Name string
|
|
Command string
|
|
mem float64
|
|
cpu float64
|
|
}
|
|
|
|
func listProcessesWithMem(ctx context.Context) ([]procEntry, error) {
|
|
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
|
|
return listProcMemPS(ctx)
|
|
}
|
|
return listProcMemTasklist(ctx)
|
|
}
|
|
|
|
func listProcessesWithCPU(ctx context.Context) ([]procEntry, error) {
|
|
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
|
|
return listProcCPUPS(ctx)
|
|
}
|
|
return listProcCPUTasklist(ctx)
|
|
}
|
|
|
|
func listProcMemPS(ctx context.Context) ([]procEntry, error) {
|
|
out, err := exec.CommandContext(ctx, "ps", "-eo", "pid=,rss=,comm=,args=").Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ps 执行失败: %w", err)
|
|
}
|
|
var procs []procEntry
|
|
for _, line := range strings.Split(string(out), "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 3 {
|
|
continue
|
|
}
|
|
pid, _ := strconv.Atoi(fields[0])
|
|
rss, _ := strconv.ParseFloat(fields[1], 64)
|
|
name := fields[2]
|
|
command := strings.Join(fields[3:], " ")
|
|
if pid > 0 {
|
|
procs = append(procs, procEntry{PID: pid, Name: name, Command: command, mem: rss})
|
|
}
|
|
}
|
|
return procs, nil
|
|
}
|
|
|
|
func listProcCPUPS(ctx context.Context) ([]procEntry, error) {
|
|
out, err := exec.CommandContext(ctx, "ps", "-eo", "pid=,%cpu=,comm=,args=").Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ps 执行失败: %w", err)
|
|
}
|
|
var procs []procEntry
|
|
for _, line := range strings.Split(string(out), "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 3 {
|
|
continue
|
|
}
|
|
pid, _ := strconv.Atoi(fields[0])
|
|
cpu, _ := strconv.ParseFloat(fields[1], 64)
|
|
name := fields[2]
|
|
command := strings.Join(fields[3:], " ")
|
|
if pid > 0 {
|
|
procs = append(procs, procEntry{PID: pid, Name: name, Command: command, cpu: cpu})
|
|
}
|
|
}
|
|
return procs, nil
|
|
}
|
|
|
|
func listProcMemTasklist(ctx context.Context) ([]procEntry, error) {
|
|
out, err := exec.CommandContext(ctx, "tasklist", "/fo", "csv", "/nh").Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("tasklist 执行失败: %w", err)
|
|
}
|
|
var procs []procEntry
|
|
for _, line := range strings.Split(string(out), "\n") {
|
|
fields := strings.Split(strings.TrimSpace(line), ",")
|
|
if len(fields) < 5 {
|
|
continue
|
|
}
|
|
name := strings.Trim(fields[0], `"`)
|
|
pid, _ := strconv.Atoi(strings.Trim(fields[1], `"`))
|
|
memStr := strings.Trim(fields[4], ` "K`)
|
|
memStr = strings.ReplaceAll(memStr, ",", "")
|
|
mem, _ := strconv.ParseFloat(memStr, 64)
|
|
if pid > 0 {
|
|
procs = append(procs, procEntry{PID: pid, Name: name, mem: mem})
|
|
}
|
|
}
|
|
return procs, nil
|
|
}
|
|
|
|
func listProcCPUTasklist(ctx context.Context) ([]procEntry, error) {
|
|
return nil, fmt.Errorf("Windows 下 tasklist 不报告 CPU 使用率,请使用任务管理器")
|
|
}
|
|
|
|
func printProcTop(req BuiltinRequest, procs []procEntry, count int, label string) {
|
|
if len(procs) == 0 {
|
|
fmt.Fprintln(req.Stdout, "没有获取到进程信息")
|
|
return
|
|
}
|
|
if count > len(procs) {
|
|
count = len(procs)
|
|
}
|
|
w := req.Stdout
|
|
fmt.Fprintf(w, "=== 占用 %s 前 %d 的进程 ===\n\n", label, count)
|
|
fmt.Fprintf(w, "%-8s %-8s %-20s %s\n", "PID", label, "进程", "命令")
|
|
for i := 0; i < count; i++ {
|
|
p := procs[i]
|
|
memStr := ""
|
|
if label == "内存" {
|
|
memStr = humanSize(int64(p.mem * 1024))
|
|
} else {
|
|
memStr = fmt.Sprintf("%.1f%%", p.cpu)
|
|
}
|
|
cmd := p.Command
|
|
if len(cmd) > 50 {
|
|
cmd = cmd[:50] + "..."
|
|
}
|
|
fmt.Fprintf(w, "%-8d %-8s %-20s %s\n", p.PID, memStr, p.Name, cmd)
|
|
}
|
|
}
|
|
|
|
// runProcKillName implements `kopsc proc killname <name>`: kill processes by name.
|
|
func runProcKillName(ctx context.Context, req BuiltinRequest) error {
|
|
opts, err := parseProcKillNameArgs(req.Args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
procs, err := listProcesses(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var targets []processInfo
|
|
for _, p := range procs {
|
|
if strings.EqualFold(p.Name, opts.name) || strings.Contains(strings.ToLower(p.Command), strings.ToLower(opts.name)) {
|
|
targets = append(targets, p)
|
|
}
|
|
}
|
|
|
|
if len(targets) == 0 {
|
|
fmt.Fprintf(req.Stdout, "没有找到名为 %q 的进程\n", opts.name)
|
|
return nil
|
|
}
|
|
|
|
fmt.Fprintf(req.Stdout, "找到 %d 个匹配进程:\n", len(targets))
|
|
for _, p := range targets {
|
|
fmt.Fprintf(req.Stdout, " %d %s %s\n", p.PID, p.Name, p.Command)
|
|
}
|
|
|
|
if !opts.confirmed {
|
|
fmt.Fprintf(req.Stdout, "\n这是破坏性操作,未执行。加 --yes 确认终止以上进程\n")
|
|
return &ExitError{Code: 2}
|
|
}
|
|
|
|
for _, p := range targets {
|
|
if err := killPID(p.PID); err != nil {
|
|
fmt.Fprintf(req.Stderr, "终止 PID %d 失败: %v\n", p.PID, err)
|
|
} else {
|
|
fmt.Fprintf(req.Stdout, "已终止 PID %d (%s)\n", p.PID, p.Name)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type procKillNameOptions struct {
|
|
name string
|
|
confirmed bool
|
|
}
|
|
|
|
func parseProcKillNameArgs(args []string) (procKillNameOptions, error) {
|
|
var opts procKillNameOptions
|
|
var positional []string
|
|
|
|
for i := 0; i < len(args); i++ {
|
|
arg := args[i]
|
|
switch {
|
|
case arg == "--":
|
|
positional = append(positional, args[i+1:]...)
|
|
i = len(args)
|
|
case arg == "-y" || arg == "--yes":
|
|
opts.confirmed = true
|
|
case strings.HasPrefix(arg, "-") && arg != "-":
|
|
return opts, fmt.Errorf("未知选项 %q", arg)
|
|
default:
|
|
positional = append(positional, arg)
|
|
}
|
|
}
|
|
if len(positional) == 0 {
|
|
return opts, fmt.Errorf("缺少进程名;用法: kopsc proc killname <进程名>")
|
|
}
|
|
opts.name = positional[0]
|
|
return opts, nil
|
|
}
|
|
|
|
func killPID(pid int) error {
|
|
if runtime.GOOS == "windows" {
|
|
return exec.Command("taskkill", "/PID", strconv.Itoa(pid), "/F").Run()
|
|
}
|
|
proc, err := os.FindProcess(pid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return proc.Kill()
|
|
}
|