各包职责: - config: 分类 YAML 配置 - model: Device/IP/MAC 公共类型 - discovery: 扫描 + ARP MAC 解析 + Devices 变量 - sshclient: SSH 连接 - stress: stress-ng/stressapptest 压测 + Results 变量 - report: 80mm 热敏模板/sparkline/打印 + Printed 变量 - workflow: 永久循环调度 + Start/Stop workflow 已精简为 100 行纯编排,所有业务逻辑下沉各包
204 lines
5.1 KiB
Go
204 lines
5.1 KiB
Go
package stress
|
||
|
||
import (
|
||
"fmt"
|
||
"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 {
|
||
Config Config
|
||
Types []TestType
|
||
Executor Executor
|
||
}
|
||
|
||
// NewRunner 创建调度器
|
||
func NewRunner(cfg Config, types []TestType, executor Executor) *Runner {
|
||
return &Runner{
|
||
Config: cfg,
|
||
Types: types,
|
||
Executor: executor,
|
||
}
|
||
}
|
||
|
||
// Run 执行压力测试(单台主机)
|
||
func (r *Runner) Run(client *ssh.Client, 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)
|
||
|
||
// 启动温度监控
|
||
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)}
|
||
}
|
||
|
||
report.AddResult(res)
|
||
fmt.Printf(" ── 完成: %s [%s] %s ──\n", t, res.Status, res.Duration.Round(time.Millisecond))
|
||
}
|
||
|
||
// 停止监控,收集结果
|
||
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),
|
||
})
|
||
}
|
||
|
||
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))
|
||
}
|
||
return reports
|
||
}
|
||
|
||
// IsPassed 报告是否通过
|
||
func IsPassed(rpt *Report) bool {
|
||
return rpt != nil && rpt.Failed == 0 && rpt.Errors == 0
|
||
}
|
||
|
||
// Status 报告状态字符串:pass / fail / untested
|
||
func Status(rpt *Report) string {
|
||
if rpt == nil {
|
||
return "untested"
|
||
}
|
||
if IsPassed(rpt) {
|
||
return "pass"
|
||
}
|
||
return "fail"
|
||
}
|
||
|
||
// 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
|
||
}
|
||
|
||
// 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
|
||
}
|