重构 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:
@@ -2,6 +2,7 @@ package stress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -14,145 +15,212 @@ import (
|
||||
// Results 测试结果表(IP → 报告),包级公开
|
||||
var Results = make(map[string]*Report)
|
||||
|
||||
// Runner 压力测试调度器
|
||||
// ============================
|
||||
// 执行器
|
||||
// ============================
|
||||
|
||||
// Runner 压力测试执行器
|
||||
type Runner struct {
|
||||
Config Config
|
||||
Types []TestType
|
||||
Executor Executor
|
||||
cfg Config
|
||||
types []TestType
|
||||
client *ssh.Client
|
||||
tools ToolSet
|
||||
metrics []Metric
|
||||
}
|
||||
|
||||
// NewRunner 创建调度器
|
||||
func NewRunner(cfg Config, types []TestType, executor Executor) *Runner {
|
||||
return &Runner{
|
||||
Config: cfg,
|
||||
Types: types,
|
||||
Executor: executor,
|
||||
}
|
||||
// NewRunner 创建执行器(检测工具 → 构建指标 → 生成脚本 → 执行)
|
||||
func NewRunner(cfg Config, types []TestType, client *ssh.Client) *Runner {
|
||||
tools := DetectTools(client)
|
||||
metrics := BuildMetrics(types, tools)
|
||||
return &Runner{cfg: cfg, types: types, client: client, tools: tools, metrics: metrics}
|
||||
}
|
||||
|
||||
// Run 执行压力测试(单台主机)
|
||||
func (r *Runner) Run(client *ssh.Client, ip string) *Report {
|
||||
// Run 执行压力测试(生成脚本 → 远程执行 → 解析结果)
|
||||
func (r *Runner) Run(ip string) *Report {
|
||||
report := &Report{IP: ip, StartTime: time.Now()}
|
||||
fmt.Printf("\n════════ [%s] 压力测试开始 ════════\n", ip)
|
||||
|
||||
// 系统信息探测
|
||||
info := ProbeSystem(client, r.Executor)
|
||||
fmt.Printf(" 主机: %s | CPU: %s (%s核) | 内存: %s | 内核: %s\n",
|
||||
info.Hostname, info.CPUModel, info.CPUCores, info.MemTotal, info.KernelVer)
|
||||
// 显示可用工具
|
||||
fmt.Printf(" 工具: ")
|
||||
for name, info := range r.tools.Tools {
|
||||
fmt.Printf("%s(%s) ", name, info.Version)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// 启动温度监控
|
||||
tempMon := NewTempMonitor(client, r.Executor, r.Config.TempLogInt)
|
||||
tempMon.Start()
|
||||
|
||||
// 启动 dmesg 监控
|
||||
dmesgMon := NewDmesgMonitor(client, r.Executor)
|
||||
dmesgMon.Start()
|
||||
|
||||
// 逐项执行测试
|
||||
for _, t := range r.Types {
|
||||
fmt.Printf("\n ── 开始: %s ──\n", t)
|
||||
var res Result
|
||||
|
||||
switch t {
|
||||
case TestCPU:
|
||||
res = CPUStress(client, r.Executor, r.Config)
|
||||
case TestMemory:
|
||||
res = MemoryStress(client, r.Executor, r.Config)
|
||||
case TestDiskIO:
|
||||
res = DiskIOStress(client, r.Executor, r.Config)
|
||||
case TestMemNative:
|
||||
res = MemNativeStress(client, r.Executor, r.Config)
|
||||
case TestFull:
|
||||
res = FullStress(client, r.Executor, r.Config)
|
||||
default:
|
||||
res = Result{Type: t, Status: "skip", Error: fmt.Sprintf("未知测试类型: %s", t)}
|
||||
// 显示将执行的指标
|
||||
fmt.Printf(" 指标: ")
|
||||
for _, m := range r.metrics {
|
||||
if m.Enabled {
|
||||
fmt.Printf("%s ", m.Name)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
report.AddResult(res)
|
||||
fmt.Printf(" ── 完成: %s [%s] %s ──\n", t, res.Status, res.Duration.Round(time.Millisecond))
|
||||
// 生成脚本
|
||||
script := BuildScript(r.metrics, r.cfg)
|
||||
|
||||
// 远程执行脚本(通过 stdin 传入)
|
||||
fmt.Printf(" [执行] 生成脚本 %d 字节,开始远程执行...\n", len(script))
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf(" [执行] 脚本执行出错: %v\n", err)
|
||||
}
|
||||
|
||||
// 停止监控,收集结果
|
||||
tempLogs := tempMon.Stop()
|
||||
dmesgErrors := dmesgMon.Stop()
|
||||
|
||||
// 添加温度报告
|
||||
if len(tempLogs) > 0 {
|
||||
maxTemp := ""
|
||||
for _, line := range tempLogs {
|
||||
// 提取温度值
|
||||
parts := strings.Fields(line)
|
||||
for _, p := range parts {
|
||||
if strings.HasSuffix(p, "°C") || strings.HasSuffix(p, "C") {
|
||||
maxTemp = p
|
||||
}
|
||||
}
|
||||
}
|
||||
tempSummary := fmt.Sprintf("采样 %d 次", len(tempLogs))
|
||||
if maxTemp != "" {
|
||||
tempSummary += ", 最高温度: " + maxTemp
|
||||
}
|
||||
status := "pass"
|
||||
if strings.Contains(strings.ToLower(strings.Join(tempLogs, " ")), "throttl") {
|
||||
status = "fail"
|
||||
tempSummary += " [检测到降频!]"
|
||||
}
|
||||
report.AddResult(Result{
|
||||
Type: MonitorTemp,
|
||||
Status: status,
|
||||
Output: tempSummary,
|
||||
Duration: time.Since(report.StartTime),
|
||||
})
|
||||
}
|
||||
|
||||
// 添加 dmesg 报告
|
||||
if len(dmesgErrors) > 0 {
|
||||
report.AddResult(Result{
|
||||
Type: MonitorDmesg,
|
||||
Status: "fail",
|
||||
Output: fmt.Sprintf("检测到 %d 条硬件相关报错:\n%s", len(dmesgErrors), strings.Join(dmesgErrors[:min(10, len(dmesgErrors))], "\n")),
|
||||
Duration: time.Since(report.StartTime),
|
||||
})
|
||||
} else {
|
||||
report.AddResult(Result{
|
||||
Type: MonitorDmesg,
|
||||
Status: "pass",
|
||||
Output: "无硬件相关内核报错",
|
||||
Duration: time.Since(report.StartTime),
|
||||
})
|
||||
// 解析结果
|
||||
if output != "" {
|
||||
r.parseResults(report, output)
|
||||
}
|
||||
|
||||
report.EndTime = time.Now()
|
||||
report.Duration = report.EndTime.Sub(report.StartTime)
|
||||
|
||||
fmt.Printf("\n════════ [%s] 压力测试完成 ════════\n", ip)
|
||||
fmt.Println(report.ToText())
|
||||
return report
|
||||
}
|
||||
|
||||
// RunAll 对多台主机执行压力测试
|
||||
func (r *Runner) RunAll(clients map[string]*ssh.Client) []*Report {
|
||||
var reports []*Report
|
||||
for ip, client := range clients {
|
||||
reports = append(reports, r.Run(client, ip))
|
||||
// parseResults 解析脚本的结构化输出
|
||||
func (r *Runner) parseResults(report *Report, output string) {
|
||||
lines := strings.Split(output, "\n")
|
||||
var currentTest string
|
||||
var currentStatus string
|
||||
var currentDuration string
|
||||
var currentOutput []string
|
||||
|
||||
flush := func() {
|
||||
if currentTest == "" {
|
||||
return
|
||||
}
|
||||
status := StatusPass
|
||||
switch currentStatus {
|
||||
case "fail":
|
||||
status = StatusFail
|
||||
case "skip":
|
||||
status = StatusSkip
|
||||
case "error":
|
||||
status = StatusError
|
||||
}
|
||||
duration, _ := time.ParseDuration(currentDuration)
|
||||
report.AddResult(Result{
|
||||
Type: TestType(currentTest),
|
||||
Status: status,
|
||||
Output: strings.Join(currentOutput, "\n"),
|
||||
Duration: duration,
|
||||
})
|
||||
currentTest = ""
|
||||
currentStatus = ""
|
||||
currentDuration = ""
|
||||
currentOutput = nil
|
||||
}
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
if strings.HasPrefix(line, "===TEST:") {
|
||||
flush()
|
||||
currentTest = strings.TrimSuffix(strings.TrimPrefix(line, "===TEST:"), "===")
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(line, "===MONITOR:") {
|
||||
flush()
|
||||
currentTest = strings.TrimSuffix(strings.TrimPrefix(line, "===MONITOR:"), "===")
|
||||
continue
|
||||
}
|
||||
|
||||
if line == "===END===" {
|
||||
flush()
|
||||
continue
|
||||
}
|
||||
|
||||
// 解析 key:value
|
||||
if strings.HasPrefix(line, "status:") {
|
||||
currentStatus = strings.TrimPrefix(line, "status:")
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "duration:") {
|
||||
durStr := strings.TrimPrefix(line, "duration:")
|
||||
ms, _ := strconv.ParseInt(strings.TrimSuffix(durStr, "ms"), 10, 64)
|
||||
currentDuration = fmt.Sprintf("%dms", ms)
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "output:") {
|
||||
currentOutput = append(currentOutput, strings.TrimPrefix(line, "output:"))
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "samples:") || strings.HasPrefix(line, "max:") {
|
||||
currentOutput = append(currentOutput, line)
|
||||
continue
|
||||
}
|
||||
|
||||
// 普通输出行
|
||||
if currentTest != "" && line != "" {
|
||||
currentOutput = append(currentOutput, line)
|
||||
}
|
||||
}
|
||||
flush()
|
||||
|
||||
// 从系统信息输出解析主机信息
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "hostname:") {
|
||||
// 可扩展:存入 report 的系统信息字段
|
||||
}
|
||||
}
|
||||
return reports
|
||||
}
|
||||
|
||||
// ============================
|
||||
// 业务入口(workflow 调用)
|
||||
// ============================
|
||||
|
||||
// TestDevice 对单台设备执行 SSH 登录 + 压力测试(业务入口)
|
||||
func TestDevice(ip string, cfg config.Config) *Report {
|
||||
sshCfg := sshclient.NewConfig(cfg.SSH.User, cfg.SSH.Password, cfg.SSH.KeyFile, cfg.SSH.Port, cfg.Scan.Timeout)
|
||||
|
||||
conn, err := sshclient.Connect(ip, sshCfg)
|
||||
if err != nil {
|
||||
fmt.Printf(" [测试] %s SSH 连接失败: %v\n", ip, err)
|
||||
return NewSSHFailReport(ip, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
stressCfg := Config{
|
||||
Duration: cfg.Stress.Duration,
|
||||
Threads: cfg.Stress.Threads,
|
||||
DiskSizeMB: 1024,
|
||||
TempLogInt: 10 * time.Second,
|
||||
}
|
||||
return NewRunner(stressCfg, ParseTypes(cfg.Stress.Types), conn).Run(ip)
|
||||
}
|
||||
|
||||
// NewSSHFailReport 构造 SSH 连接失败报告
|
||||
func NewSSHFailReport(ip string, err error) *Report {
|
||||
return &Report{
|
||||
IP: ip,
|
||||
StartTime: time.Now(),
|
||||
Results: []Result{{Type: "ssh", Status: StatusFail, Error: err.Error()}},
|
||||
Failed: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// 工具函数
|
||||
// ============================
|
||||
|
||||
// IsPassed 报告是否通过
|
||||
func IsPassed(rpt *Report) bool {
|
||||
return rpt != nil && rpt.Failed == 0 && rpt.Errors == 0
|
||||
}
|
||||
|
||||
// Status 报告状态字符串:pass / fail / untested
|
||||
// Status 报告状态字符串
|
||||
func Status(rpt *Report) string {
|
||||
if rpt == nil {
|
||||
return "untested"
|
||||
return StatusUnknown
|
||||
}
|
||||
if IsPassed(rpt) {
|
||||
return "pass"
|
||||
return StatusPass
|
||||
}
|
||||
return "fail"
|
||||
return StatusFail
|
||||
}
|
||||
|
||||
// ParseTypes 逗号分隔字符串 → TestType 列表
|
||||
@@ -165,39 +233,3 @@ func ParseTypes(s string) []TestType {
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
// TestDevice 对单台设备执行 SSH 登录 + 压力测试(业务入口)
|
||||
func TestDevice(ip string, cfg config.Config) *Report {
|
||||
client := sshclient.NewClient(cfg.SSH.User, cfg.SSH.Password, cfg.SSH.KeyFile, cfg.SSH.Port, cfg.Scan.Timeout)
|
||||
|
||||
conn, err := client.Connect(ip)
|
||||
if err != nil {
|
||||
fmt.Printf(" [测试] %s SSH 连接失败: %v\n", ip, err)
|
||||
return NewSSHFailReport(ip, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
return NewRunner(
|
||||
Config{Duration: cfg.Stress.Duration, Threads: cfg.Stress.Threads, DiskSizeMB: 1024, TempLogInt: 10 * time.Second},
|
||||
ParseTypes(cfg.Stress.Types),
|
||||
client,
|
||||
).Run(conn, ip)
|
||||
}
|
||||
|
||||
// NewSSHFailReport 构造 SSH 连接失败报告
|
||||
func NewSSHFailReport(ip string, err error) *Report {
|
||||
return &Report{
|
||||
IP: ip,
|
||||
StartTime: time.Now(),
|
||||
Results: []Result{{Type: "ssh", Status: "fail", Error: err.Error()}},
|
||||
Failed: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// min 取较小值
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user