refactor(discovery): use ping for discovery and fill MAC via SSH

This commit is contained in:
12600k-rog-d4
2026-08-12 21:45:51 +08:00
parent 906bb54aaa
commit 175c5e8c6c
21 changed files with 542 additions and 310 deletions

View File

@@ -2,11 +2,14 @@ package stress
import (
"fmt"
"net"
"os"
"strconv"
"strings"
"time"
"auto-check/pkg/config"
"auto-check/pkg/discovery"
"auto-check/pkg/sshclient"
"golang.org/x/crypto/ssh"
@@ -56,11 +59,11 @@ func (r *Runner) Run(ip string) *Report {
}
fmt.Println()
// 生成脚本
script := BuildScript(r.metrics, r.cfg)
// 按需读取各测试脚本,拼装成完整脚本
script := r.assembleScript()
fmt.Printf(" [脚本] 拼装完成,%d 字节\n", len(script))
// 远程执行脚本(通过 stdin 传入)
fmt.Printf(" [执行] 生成脚本 %d 字节,开始远程执行...\n", len(script))
// 通过 heredoc 传到远程执行
scriptCmd := fmt.Sprintf("cat <<'AUTOCHECKSCRIPT' > /tmp/auto-check.sh\n%s\nAUTOCHECKSCRIPT\nchmod +x /tmp/auto-check.sh && bash /tmp/auto-check.sh 2>&1", script)
output, err := sshclient.RunCommand(r.client, scriptCmd)
@@ -73,6 +76,18 @@ func (r *Runner) Run(ip string) *Report {
r.parseResults(report, output)
}
// 生成采样折线图
for _, res := range report.Results {
if len(res.Samples) > 0 {
chartPath, err := WriteChartHTML(ip, res, "reports")
if err != nil {
fmt.Printf(" [图表] 生成失败: %v\n", err)
} else {
fmt.Printf(" [图表] %s\n", chartPath)
}
}
}
report.EndTime = time.Now()
report.Duration = report.EndTime.Sub(report.StartTime)
fmt.Printf("\n════════ [%s] 压力测试完成 ════════\n", ip)
@@ -87,6 +102,8 @@ func (r *Runner) parseResults(report *Report, output string) {
var currentStatus string
var currentDuration string
var currentOutput []string
var currentSamples []Sample
inSamples := false
flush := func() {
if currentTest == "" {
@@ -107,16 +124,41 @@ func (r *Runner) parseResults(report *Report, output string) {
Status: status,
Output: strings.Join(currentOutput, "\n"),
Duration: duration,
Samples: currentSamples,
})
currentTest = ""
currentStatus = ""
currentDuration = ""
currentOutput = nil
currentSamples = nil
}
for _, line := range lines {
line = strings.TrimSpace(line)
// 采样数据块
if line == "===SAMPLES===" {
inSamples = true
continue
}
if line == "===END_SAMPLES===" {
inSamples = false
continue
}
if inSamples {
parts := strings.Split(line, ",")
if len(parts) >= 3 {
cpu, _ := strconv.ParseFloat(strings.TrimSpace(parts[1]), 64)
temp, _ := strconv.ParseFloat(strings.TrimSpace(parts[2]), 64)
currentSamples = append(currentSamples, Sample{
Time: strings.TrimSpace(parts[0]),
CPU: cpu,
Temp: temp,
})
}
continue
}
if strings.HasPrefix(line, "===TEST:") {
flush()
currentTest = strings.TrimSuffix(strings.TrimPrefix(line, "===TEST:"), "===")
@@ -169,6 +211,81 @@ func (r *Runner) parseResults(report *Report, output string) {
}
}
// assembleScript 根据 metrics 读取对应的独立脚本文件,拼装成完整脚本
func (r *Runner) assembleScript() string {
var sb strings.Builder
// shebang + 环境变量导出
sb.WriteString("#!/bin/bash\n")
sb.WriteString("set -e\n\n")
// 导出参数变量
sb.WriteString(fmt.Sprintf("export AUTOCHECK_DURATION=%d\n", int(r.cfg.Duration.Seconds())))
sb.WriteString(fmt.Sprintf("export AUTOCHECK_THREADS=%d\n", r.cfg.Threads))
sb.WriteString(fmt.Sprintf("export AUTOCHECK_DISK_SIZE_MB=%d\n", r.cfg.DiskSizeMB))
sb.WriteString(fmt.Sprintf("export AUTOCHECK_MEM_SIZE_MB=%d\n", r.cfg.MemSizeMB))
sb.WriteString(fmt.Sprintf("export AUTOCHECK_TEMP_INTERVAL=%d\n", int(r.cfg.TempLogInt.Seconds())))
sb.WriteString("export AUTOCHECK_SAMPLE_INTERVAL=2\n")
if r.cfg.DiskDir != "" {
sb.WriteString(fmt.Sprintf("export AUTOCHECK_DISK_DIR=%s\n", r.cfg.DiskDir))
}
sb.WriteString("\n")
// 系统信息
sb.Write(r.readScript("sysinfo"))
sb.WriteString("\n")
// 温度监控启动
hasTemp := false
for _, m := range r.metrics {
if m.Name == "temp" && m.Enabled {
hasTemp = true
break
}
}
if hasTemp {
sb.Write(r.readScript("temp_start"))
sb.WriteString("\n")
}
// 逐个测试脚本
for _, m := range r.metrics {
if m.IsMonitor || !m.Enabled {
continue
}
data := r.readScript(m.Name)
if data == nil {
sb.WriteString(fmt.Sprintf("echo '===TEST:%s==='\n", m.Name))
sb.WriteString("echo 'status:skip'\n")
sb.WriteString(fmt.Sprintf("echo 'output:脚本文件 scripts/%s.sh 不存在'\n", m.Name))
sb.WriteString("echo ''\n")
continue
}
sb.Write(data)
sb.WriteString("\n")
}
// 温度监控收尾
if hasTemp {
sb.Write(r.readScript("temp_end"))
sb.WriteString("\n")
}
// dmesg + 结束标记
sb.Write(r.readScript("dmesg"))
return sb.String()
}
// readScript 读取 scripts/ 目录下的脚本文件
func (r *Runner) readScript(name string) []byte {
data, err := os.ReadFile(fmt.Sprintf("scripts/%s.sh", name))
if err != nil {
return nil
}
return data
}
// ============================
// 业务入口workflow 调用)
// ============================
@@ -184,6 +301,15 @@ func TestDevice(ip string, cfg config.Config) *Report {
}
defer conn.Close()
// SSH 登录成功后回填远端 MAC发现阶段只做了 ping暂无 MAC
if mac := queryRemoteMAC(conn); mac != "" {
if dev, ok := discovery.Devices[ip]; ok {
dev.MAC = mac
discovery.Devices[ip] = dev
}
fmt.Printf(" [MAC] %s -> %s\n", ip, mac)
}
stressCfg := Config{
Duration: cfg.Stress.Duration,
Threads: cfg.Stress.Threads,
@@ -203,6 +329,34 @@ func NewSSHFailReport(ip string, err error) *Report {
}
}
// queryRemoteMAC 查询远端主机的 MAC 地址(取第一个有效的单播地址)。
// 失败或无有效地址时返回空字符串,不阻断主流程。
func queryRemoteMAC(client *ssh.Client) string {
out, err := sshclient.RunCommand(client, "cat /sys/class/net/*/address 2>/dev/null")
if err != nil {
return ""
}
for _, line := range strings.Split(out, "\n") {
mac := strings.TrimSpace(line)
if mac == "" {
continue
}
hw, e := net.ParseMAC(mac)
if e != nil || len(hw) != 6 {
continue
}
// 排除零地址、广播、组播
if hw[0] == 0 && hw[1] == 0 && hw[2] == 0 && hw[3] == 0 && hw[4] == 0 && hw[5] == 0 {
continue
}
if hw[0]&0x01 != 0 {
continue
}
return hw.String()
}
return ""
}
// ============================
// 工具函数
// ============================