refactor(discovery): use ping for discovery and fill MAC via SSH
This commit is contained in:
@@ -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 ""
|
||||
}
|
||||
|
||||
// ============================
|
||||
// 工具函数
|
||||
// ============================
|
||||
|
||||
Reference in New Issue
Block a user