重构 auto-check:状态数据下沉各包,workflow 精简为纯调度

- discovery: ping 探测存活 + ARP 解析 MAC,包级 Devices map,串行扫描
- stress: 脚本生成模式(BuildScript/ParseResults),ToolSet 工具检测,包级 Results map
- report: 包级 Printed map,SaveAndPrintDeviceReport 保存+打印
- sshclient: 包级 RunCommand
- workflow: 仅持有 config,Start() 固定循环,直接调用各包方法
- config: 移除 ScanConfig.Concurrency(串行扫描无需并发数)
This commit is contained in:
张威33321
2026-08-12 17:11:59 +08:00
parent 700ba91883
commit 906bb54aaa
15 changed files with 708 additions and 871 deletions

View File

@@ -1,269 +1,78 @@
package stress
import (
"fmt"
"strings"
"time"
"auto-check/pkg/sshclient"
"golang.org/x/crypto/ssh"
)
// ============================
// 远程命令执行接口
// ============================
// Executor 远程命令执行器
type Executor interface {
RunCommand(client *ssh.Client, command string) (string, error)
}
// exec 执行远程命令(带超时)
func exec(client *ssh.Client, executor Executor, cmd string) (string, error) {
return executor.RunCommand(client, cmd)
}
// execf 格式化执行
func execf(client *ssh.Client, executor Executor, format string, args ...interface{}) (string, error) {
return exec(client, executor, fmt.Sprintf(format, args...))
}
// ============================
// 工具检测
// 远程工具检测
// ============================
// ToolInfo 远程工具信息
type ToolInfo struct {
Available bool
Path string
Version string
Name string
Path string
Version string
}
// DetectTool 检测远程工具是否可用
func DetectTool(client *ssh.Client, executor Executor, name string) ToolInfo {
// 检查路径
out, err := execf(client, executor, "which %s 2>/dev/null", name)
// ToolSet 远程可用工具集合
type ToolSet struct {
Tools map[string]ToolInfo // name → info
}
// Has 工具是否可用
func (ts ToolSet) Has(name string) bool {
_, ok := ts.Tools[name]
return ok
}
// Get 获取工具信息
func (ts ToolSet) Get(name string) (ToolInfo, bool) {
t, ok := ts.Tools[name]
return t, ok
}
// DetectTools 检测远程所有相关工具是否可用
func DetectTools(client *ssh.Client) ToolSet {
ts := ToolSet{Tools: make(map[string]ToolInfo)}
for _, name := range []string{"stress-ng", "stressapptest", "iperf3", "fio", "lm-sensors"} {
if info, ok := detectOne(client, name); ok {
ts.Tools[name] = info
}
}
return ts
}
// detectOne 检测单个工具
func detectOne(client *ssh.Client, name string) (ToolInfo, bool) {
out, err := sshclient.RunCommand(client, "which "+name+" 2>/dev/null")
path := strings.TrimSpace(out)
if err != nil || path == "" {
return ToolInfo{Available: false}
return ToolInfo{}, false
}
// 获取版本
var version string
switch name {
case "stress-ng":
out, _ = execf(client, executor, "%s --version 2>&1 | head -1", path)
out, _ = sshclient.RunCommand(client, path+" --version 2>&1 | head -1")
version = strings.TrimSpace(out)
case "stressapptest":
out, _ = execf(client, executor, "%s --help 2>&1 | head -1", path)
out, _ = sshclient.RunCommand(client, path+" --help 2>&1 | head -1")
version = strings.TrimSpace(out)
case "iperf3":
out, _ = sshclient.RunCommand(client, path+" --version 2>&1 | head -1")
version = strings.TrimSpace(out)
case "fio":
out, _ = sshclient.RunCommand(client, path+" --version 2>&1 | head -1")
version = strings.TrimSpace(out)
case "lm-sensors":
out, _ = execf(client, executor, "%s -v 2>&1 | head -1", path)
out, _ = sshclient.RunCommand(client, path+" -v 2>&1 | head -1")
version = strings.TrimSpace(out)
}
return ToolInfo{Available: true, Path: path, Version: version}
}
// EnsureTool 检测工具,不可用则返回错误提示
func EnsureTool(client *ssh.Client, executor Executor, name string, installHint string) (ToolInfo, error) {
info := DetectTool(client, executor, name)
if !info.Available {
return info, fmt.Errorf("%s 未安装。安装方式: %s", name, installHint)
}
return info, nil
}
// ============================
// 环境探测
// ============================
// SystemInfo 系统基础信息
type SystemInfo struct {
Hostname string
CPUModel string
CPUCores string
MemTotal string
KernelVer string
DiskInfo string
}
// ProbeSystem 探测远程系统基础信息
func ProbeSystem(client *ssh.Client, executor Executor) SystemInfo {
info := SystemInfo{}
out, _ := exec(client, executor, "hostname 2>/dev/null")
info.Hostname = strings.TrimSpace(out)
out, _ = exec(client, executor, "lscpu 2>/dev/null | grep 'Model name' | sed 's/Model name:\\s*//'")
info.CPUModel = strings.TrimSpace(out)
out, _ = exec(client, executor, "nproc 2>/dev/null")
info.CPUCores = strings.TrimSpace(out)
out, _ = exec(client, executor, "free -h 2>/dev/null | awk '/Mem:/{print $2}'")
info.MemTotal = strings.TrimSpace(out)
out, _ = exec(client, executor, "uname -r 2>/dev/null")
info.KernelVer = strings.TrimSpace(out)
out, _ = exec(client, executor, "lsblk -d -o NAME,SIZE,TYPE 2>/dev/null | head -5")
info.DiskInfo = strings.TrimSpace(out)
return info
}
// ============================
// 温度监控
// ============================
// TempMonitor 温度监控器
type TempMonitor struct {
client *ssh.Client
executor Executor
interval time.Duration
stopCh chan struct{}
logs []string
}
// NewTempMonitor 创建温度监控器
func NewTempMonitor(client *ssh.Client, executor Executor, interval time.Duration) *TempMonitor {
if interval <= 0 {
interval = 10 * time.Second
}
return &TempMonitor{
client: client,
executor: executor,
interval: interval,
stopCh: make(chan struct{}),
}
}
// Start 后台启动温度监控
func (m *TempMonitor) Start() {
go m.loop()
}
// Stop 停止监控并返回所有采样
func (m *TempMonitor) Stop() []string {
close(m.stopCh)
return m.logs
}
func (m *TempMonitor) loop() {
ticker := time.NewTicker(m.interval)
defer ticker.Stop()
// 初始温度
m.sample()
for {
select {
case <-m.stopCh:
return
case <-ticker.C:
m.sample()
}
}
}
func (m *TempMonitor) sample() {
// 尝试 sensors
out, err := execf(m.client, m.executor, "sensors 2>/dev/null | grep -i 'temp\\|core\\|cpu' | head -10")
if err == nil && strings.TrimSpace(out) != "" {
ts := time.Now().Format("15:04:05")
for _, line := range strings.Split(out, "\n") {
line = strings.TrimSpace(line)
if line != "" {
m.logs = append(m.logs, fmt.Sprintf("[%s] %s", ts, line))
}
}
return
}
// 回退: 读 sysfs
out, _ = execf(m.client, m.executor, `cat /sys/class/thermal/thermal_zone*/temp 2>/dev/null | while read t; do echo "$((t/1000))°C"; done`)
if strings.TrimSpace(out) != "" {
ts := time.Now().Format("15:04:05")
m.logs = append(m.logs, fmt.Sprintf("[%s] %s", ts, strings.TrimSpace(out)))
}
}
// ============================
// dmesg 监控
// ============================
// DmesgMonitor 内核日志监控
type DmesgMonitor struct {
client *ssh.Client
executor Executor
stopCh chan struct{}
baseline string // 启动时的 dmesg 行数
logs []string
}
// NewDmesgMonitor 创建 dmesg 监控器
func NewDmesgMonitor(client *ssh.Client, executor Executor) *DmesgMonitor {
return &DmesgMonitor{
client: client,
executor: executor,
stopCh: make(chan struct{}),
}
}
// Start 记录基线,后台监控
func (m *DmesgMonitor) Start() {
out, _ := exec(m.client, m.executor, "dmesg 2>/dev/null | wc -l")
m.baseline = strings.TrimSpace(out)
go m.loop()
}
// Stop 停止监控,返回新增的硬件报错
func (m *DmesgMonitor) Stop() []string {
close(m.stopCh)
// 获取新增的 dmesg 日志中的硬件错误
out, _ := execf(m.client, m.executor,
`dmesg --level=err,crit,alert,emerg 2>/dev/null | tail -30`)
if strings.TrimSpace(out) != "" {
m.logs = append(m.logs, strings.Split(out, "\n")...)
}
// 过滤硬件相关关键词
var hwErrors []string
keywords := []string{"error", "fail", "fault", "warn", "critical", "oom", "panic", "hardware", "thermal", "throttl"}
for _, line := range m.logs {
lower := strings.ToLower(line)
for _, kw := range keywords {
if strings.Contains(lower, kw) {
hwErrors = append(hwErrors, strings.TrimSpace(line))
break
}
}
}
return hwErrors
}
func (m *DmesgMonitor) loop() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-m.stopCh:
return
case <-ticker.C:
// 周期性检查是否有新的硬件相关日志(轻量级)
out, _ := execf(m.client, m.executor,
`dmesg --level=err,crit 2>/dev/null | tail -3`)
if strings.TrimSpace(out) != "" {
ts := time.Now().Format("15:04:05")
for _, line := range strings.Split(out, "\n") {
line = strings.TrimSpace(line)
if line != "" {
m.logs = append(m.logs, fmt.Sprintf("[%s] %s", ts, line))
}
}
}
}
}
return ToolInfo{Name: name, Path: path, Version: version}, true
}