Files
membank/auto-check/pkg/stress/runner.go
张威33321 906bb54aaa 重构 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(串行扫描无需并发数)
2026-08-12 17:11:59 +08:00

236 lines
5.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package stress
import (
"fmt"
"strconv"
"strings"
"time"
"auto-check/pkg/config"
"auto-check/pkg/sshclient"
"golang.org/x/crypto/ssh"
)
// Results 测试结果表IP → 报告),包级公开
var Results = make(map[string]*Report)
// ============================
// 执行器
// ============================
// Runner 压力测试执行器
type Runner struct {
cfg Config
types []TestType
client *ssh.Client
tools ToolSet
metrics []Metric
}
// 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(ip string) *Report {
report := &Report{IP: ip, StartTime: time.Now()}
fmt.Printf("\n════════ [%s] 压力测试开始 ════════\n", ip)
// 显示可用工具
fmt.Printf(" 工具: ")
for name, info := range r.tools.Tools {
fmt.Printf("%s(%s) ", name, info.Version)
}
fmt.Println()
// 显示将执行的指标
fmt.Printf(" 指标: ")
for _, m := range r.metrics {
if m.Enabled {
fmt.Printf("%s ", m.Name)
}
}
fmt.Println()
// 生成脚本
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)
}
// 解析结果
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
}
// 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 的系统信息字段
}
}
}
// ============================
// 业务入口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 报告状态字符串
func Status(rpt *Report) string {
if rpt == nil {
return StatusUnknown
}
if IsPassed(rpt) {
return StatusPass
}
return StatusFail
}
// ParseTypes 逗号分隔字符串 → TestType 列表
func ParseTypes(s string) []TestType {
var types []TestType
for _, t := range strings.Split(s, ",") {
if t = strings.TrimSpace(t); t != "" {
types = append(types, TestType(t))
}
}
return types
}