重构 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:
74
auto-check/pkg/stress/metrics.go
Normal file
74
auto-check/pkg/stress/metrics.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package stress
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ============================
|
||||
// 测试指标定义
|
||||
// ============================
|
||||
|
||||
// Metric 测试指标(定义测什么、用什么工具、怎么判结果)
|
||||
type Metric struct {
|
||||
Name string // 指标名称(cpu/memory/disk/memnative/full)
|
||||
Tool string // 依赖的工具(stress-ng/stressapptest)
|
||||
Enabled bool // 是否启用
|
||||
IsMonitor bool // 是否为监控指标(temp/dmesg,自动附加)
|
||||
}
|
||||
|
||||
// BuildMetrics 根据配置和可用工具,生成要执行的指标列表
|
||||
func BuildMetrics(types []TestType, tools ToolSet) []Metric {
|
||||
var metrics []Metric
|
||||
|
||||
for _, t := range types {
|
||||
m := Metric{Name: string(t), Enabled: true}
|
||||
switch t {
|
||||
case TestCPU:
|
||||
m.Tool = "stress-ng"
|
||||
case TestMemory:
|
||||
m.Tool = "stress-ng"
|
||||
case TestDiskIO:
|
||||
m.Tool = "stress-ng"
|
||||
case TestMemNative:
|
||||
m.Tool = "stressapptest"
|
||||
case TestFull:
|
||||
m.Tool = "stress-ng"
|
||||
default:
|
||||
m.Enabled = false
|
||||
}
|
||||
if m.Tool != "" && !tools.Has(m.Tool) {
|
||||
m.Enabled = false // 工具不可用,禁用
|
||||
}
|
||||
metrics = append(metrics, m)
|
||||
}
|
||||
|
||||
// 自动附加监控指标
|
||||
metrics = append(metrics, Metric{Name: "temp", Tool: "lm-sensors", Enabled: tools.Has("lm-sensors"), IsMonitor: true})
|
||||
metrics = append(metrics, Metric{Name: "dmesg", Tool: "", Enabled: true, IsMonitor: true})
|
||||
|
||||
return metrics
|
||||
}
|
||||
|
||||
// ============================
|
||||
// 压力测试配置
|
||||
// ============================
|
||||
|
||||
// Config 压力测试配置
|
||||
type Config struct {
|
||||
Duration time.Duration
|
||||
Threads int
|
||||
MemSizeMB int // 0=自动(可用60%)
|
||||
DiskSizeMB int // 0=默认1024
|
||||
DiskDir string // 空=自动临时目录
|
||||
TempLogInt time.Duration // 0=10s
|
||||
}
|
||||
|
||||
// DefaultConfig 默认配置
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
Duration: 30 * time.Second,
|
||||
Threads: 4,
|
||||
DiskSizeMB: 1024,
|
||||
TempLogInt: 10 * time.Second,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
package stress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// truncate 截断过长输出
|
||||
func truncate(s string, maxLen int) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if len(s) > maxLen {
|
||||
return s[:maxLen] + "\n ... (输出已截断)"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ============================
|
||||
// stress-ng 压测
|
||||
// ============================
|
||||
|
||||
// CPUStress stress-ng CPU 压力测试
|
||||
func CPUStress(client *ssh.Client, ex Executor, cfg Config) Result {
|
||||
start := time.Now()
|
||||
fmt.Printf(" [CPU] stress-ng CPU 压力 (%d线程, %v)...\n", cfg.Threads, cfg.Duration)
|
||||
|
||||
info, err := EnsureTool(client, ex, "stress-ng", "apt install stress-ng / opkg install stress-ng")
|
||||
if err != nil {
|
||||
return Result{Type: TestCPU, Status: "skip", Error: err.Error(), Duration: time.Since(start)}
|
||||
}
|
||||
fmt.Printf(" [CPU] 使用 %s (%s)\n", info.Path, info.Version)
|
||||
|
||||
cmd := fmt.Sprintf("%s --cpu %d --cpu-method all --timeout %v --metrics-brief --temp-path /tmp 2>&1",
|
||||
info.Path, cfg.Threads, cfg.Duration)
|
||||
out, err := exec(client, ex, cmd)
|
||||
duration := time.Since(start)
|
||||
|
||||
if err != nil {
|
||||
return Result{Type: TestCPU, Status: "error", Output: truncate(out, 600), Error: err.Error(), Duration: duration}
|
||||
}
|
||||
return Result{Type: TestCPU, Status: "pass", Output: truncate(out, 600), Duration: duration}
|
||||
}
|
||||
|
||||
// MemoryStress stress-ng 内存压力测试
|
||||
func MemoryStress(client *ssh.Client, ex Executor, cfg Config) Result {
|
||||
start := time.Now()
|
||||
fmt.Printf(" [Memory] stress-ng 内存压力 (%d线程, %v)...\n", cfg.Threads, cfg.Duration)
|
||||
|
||||
info, err := EnsureTool(client, ex, "stress-ng", "apt install stress-ng / opkg install stress-ng")
|
||||
if err != nil {
|
||||
return Result{Type: TestMemory, Status: "skip", Error: err.Error(), Duration: time.Since(start)}
|
||||
}
|
||||
|
||||
memWorkers := cfg.Threads
|
||||
if memWorkers > 4 {
|
||||
memWorkers = 4
|
||||
}
|
||||
cmd := fmt.Sprintf("%s --vm %d --vm-bytes 256M --vm-method all --timeout %v --metrics-brief 2>&1",
|
||||
info.Path, memWorkers, cfg.Duration)
|
||||
out, err := exec(client, ex, cmd)
|
||||
duration := time.Since(start)
|
||||
|
||||
if err != nil {
|
||||
return Result{Type: TestMemory, Status: "error", Output: truncate(out, 600), Error: err.Error(), Duration: duration}
|
||||
}
|
||||
return Result{Type: TestMemory, Status: "pass", Output: truncate(out, 600), Duration: duration}
|
||||
}
|
||||
|
||||
// DiskIOStress stress-ng 磁盘IO压力测试
|
||||
func DiskIOStress(client *ssh.Client, ex Executor, cfg Config) Result {
|
||||
start := time.Now()
|
||||
fmt.Printf(" [DiskIO] stress-ng 磁盘IO压力 (%v)...\n", cfg.Duration)
|
||||
|
||||
info, err := EnsureTool(client, ex, "stress-ng", "apt install stress-ng / opkg install stress-ng")
|
||||
if err != nil {
|
||||
return Result{Type: TestDiskIO, Status: "skip", Error: err.Error(), Duration: time.Since(start)}
|
||||
}
|
||||
|
||||
testDir := cfg.DiskDir
|
||||
if testDir == "" {
|
||||
testDir = "/tmp/stress-disk-test"
|
||||
}
|
||||
_, _ = exec(client, ex, fmt.Sprintf("mkdir -p %s", testDir))
|
||||
|
||||
cmd := fmt.Sprintf("%s --iomix 2 --iomix-bytes %dM --timeout %v --metrics-brief 2>&1",
|
||||
info.Path, cfg.DiskSizeMB, cfg.Duration)
|
||||
out, err := exec(client, ex, cmd)
|
||||
duration := time.Since(start)
|
||||
|
||||
_, _ = exec(client, ex, fmt.Sprintf("rm -rf %s", testDir))
|
||||
|
||||
if err != nil {
|
||||
return Result{Type: TestDiskIO, Status: "error", Output: truncate(out, 600), Error: err.Error(), Duration: duration}
|
||||
}
|
||||
return Result{Type: TestDiskIO, Status: "pass", Output: truncate(out, 600), Duration: duration}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// stressapptest 内存精压
|
||||
// ============================
|
||||
|
||||
// MemNativeStress stressapptest 内存稳定性精压
|
||||
func MemNativeStress(client *ssh.Client, ex Executor, cfg Config) Result {
|
||||
start := time.Now()
|
||||
fmt.Printf(" [MemNative] stressapptest 内存精压 (%v)...\n", cfg.Duration)
|
||||
|
||||
info, err := EnsureTool(client, ex, "stressapptest", "apt install stressapptest / opkg install stressapptest")
|
||||
if err != nil {
|
||||
return Result{Type: TestMemNative, Status: "skip", Error: err.Error(), Duration: time.Since(start)}
|
||||
}
|
||||
fmt.Printf(" [MemNative] 使用 %s\n", info.Path)
|
||||
|
||||
memSize := cfg.MemSizeMB
|
||||
if memSize <= 0 {
|
||||
out, _ := exec(client, ex, "free -m 2>/dev/null | awk '/Mem:/{print int($7*0.6)}'")
|
||||
out = strings.TrimSpace(out)
|
||||
if out != "" {
|
||||
fmt.Sscanf(out, "%d", &memSize)
|
||||
}
|
||||
if memSize <= 0 {
|
||||
memSize = 512
|
||||
}
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf("%s -s %d -M %d -f 0 -v 2>&1",
|
||||
info.Path, int(cfg.Duration.Seconds()), memSize)
|
||||
out, err := exec(client, ex, cmd)
|
||||
duration := time.Since(start)
|
||||
|
||||
output := truncate(out, 800)
|
||||
if strings.Contains(strings.ToUpper(output), "PASS") {
|
||||
return Result{Type: TestMemNative, Status: "pass", Output: output, Duration: duration}
|
||||
}
|
||||
if strings.Contains(strings.ToUpper(output), "FAIL") {
|
||||
return Result{Type: TestMemNative, Status: "fail", Output: output, Duration: duration}
|
||||
}
|
||||
if err != nil {
|
||||
return Result{Type: TestMemNative, Status: "error", Output: output, Error: err.Error(), Duration: duration}
|
||||
}
|
||||
return Result{Type: TestMemNative, Status: "pass", Output: output, Duration: duration}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// 综合压测
|
||||
// ============================
|
||||
|
||||
// FullStress 三合一综合压测 (CPU + 内存 + 磁盘IO 同时进行)
|
||||
func FullStress(client *ssh.Client, ex Executor, cfg Config) Result {
|
||||
start := time.Now()
|
||||
fmt.Printf(" [Full] 三合一综合压测 (CPU %d线程 + 内存 + 磁盘IO, %v)...\n", cfg.Threads, cfg.Duration)
|
||||
|
||||
info, err := EnsureTool(client, ex, "stress-ng", "apt install stress-ng / opkg install stress-ng")
|
||||
if err != nil {
|
||||
return Result{Type: TestFull, Status: "skip", Error: err.Error(), Duration: time.Since(start)}
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf("%s --cpu %d --vm 2 --vm-bytes 128M --iomix 1 --iomix-bytes 256M --timeout %v --metrics-brief 2>&1",
|
||||
info.Path, cfg.Threads, cfg.Duration)
|
||||
out, err := exec(client, ex, cmd)
|
||||
duration := time.Since(start)
|
||||
|
||||
if err != nil {
|
||||
return Result{Type: TestFull, Status: "error", Output: truncate(out, 600), Error: err.Error(), Duration: duration}
|
||||
}
|
||||
return Result{Type: TestFull, Status: "pass", Output: truncate(out, 600), Duration: duration}
|
||||
}
|
||||
156
auto-check/pkg/stress/script.go
Normal file
156
auto-check/pkg/stress/script.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package stress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ============================
|
||||
// 脚本生成(根据指标生成远程执行的 shell 脚本)
|
||||
// ============================
|
||||
|
||||
// BuildScript 根据指标列表和配置生成 self-contained shell 脚本
|
||||
// 脚本输出结构化文本,便于 ParseResults 解析
|
||||
func BuildScript(metrics []Metric, cfg Config) string {
|
||||
var sb strings.Builder
|
||||
|
||||
// 脚本头部
|
||||
sb.WriteString("#!/bin/bash\n")
|
||||
sb.WriteString("set -e\n\n")
|
||||
|
||||
// 系统信息探测
|
||||
sb.WriteString("echo '===SYSTEM_INFO==='\n")
|
||||
sb.WriteString("echo hostname:$(hostname)\n")
|
||||
sb.WriteString("echo cpu:$(lscpu 2>/dev/null | grep 'Model name' | sed 's/Model name:\\s*//' || echo unknown)\n")
|
||||
sb.WriteString("echo cores:$(nproc 2>/dev/null || echo 0)\n")
|
||||
sb.WriteString("echo memory:$(free -h 2>/dev/null | awk '/Mem:/{print $2}' || echo unknown)\n")
|
||||
sb.WriteString("echo kernel:$(uname -r 2>/dev/null || echo unknown)\n")
|
||||
sb.WriteString("echo disk:$(lsblk -d -o NAME,SIZE 2>/dev/null | head -3 | tr '\\n' ' ')\n")
|
||||
sb.WriteString("echo ''\n\n")
|
||||
|
||||
// 启动温度监控(后台)
|
||||
hasTemp := false
|
||||
for _, m := range metrics {
|
||||
if m.Name == "temp" && m.Enabled {
|
||||
hasTemp = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasTemp {
|
||||
tempInterval := int(cfg.TempLogInt.Seconds())
|
||||
if tempInterval <= 0 {
|
||||
tempInterval = 10
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("# 温度监控(后台)\n"))
|
||||
sb.WriteString("TEMP_LOG=/tmp/auto-check-temp.log\n")
|
||||
sb.WriteString("> $TEMP_LOG\n")
|
||||
sb.WriteString(fmt.Sprintf("(while true; do sensors 2>/dev/null | grep -i 'temp\\|core\\|cpu' | head -5 >> $TEMP_LOG; sleep %d; done) &\n", tempInterval))
|
||||
sb.WriteString("TEMP_PID=$!\n\n")
|
||||
}
|
||||
|
||||
// 逐项执行测试
|
||||
for _, m := range metrics {
|
||||
if m.IsMonitor || !m.Enabled {
|
||||
continue
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("echo '===TEST:%s==='\n", m.Name))
|
||||
sb.WriteString("START_TIME=$(date +%%s%%N)\n")
|
||||
sb.WriteString("set +e\n")
|
||||
|
||||
cmd := buildTestCommand(m, cfg)
|
||||
sb.WriteString(fmt.Sprintf("OUTPUT=$(%s 2>&1)\n", cmd))
|
||||
sb.WriteString("EXIT_CODE=$?\n")
|
||||
sb.WriteString("set -e\n")
|
||||
sb.WriteString("END_TIME=$(date +%%s%%N)\n")
|
||||
sb.WriteString("DURATION=$(( (END_TIME - START_TIME) / 1000000 ))\n")
|
||||
|
||||
// 判定结果
|
||||
sb.WriteString("if [ $EXIT_CODE -eq 0 ]; then\n")
|
||||
sb.WriteString(" echo 'status:pass'\n")
|
||||
sb.WriteString("else\n")
|
||||
sb.WriteString(" echo 'status:fail'\n")
|
||||
sb.WriteString("fi\n")
|
||||
sb.WriteString("echo \"duration:${DURATION}ms\"\n")
|
||||
sb.WriteString("echo \"output:${OUTPUT}\"\n")
|
||||
sb.WriteString("echo ''\n\n")
|
||||
}
|
||||
|
||||
// 收集温度监控结果
|
||||
if hasTemp {
|
||||
sb.WriteString("echo '===MONITOR:temp==='\n")
|
||||
sb.WriteString("if [ -f $TEMP_LOG ] && [ -s $TEMP_LOG ]; then\n")
|
||||
sb.WriteString(" echo 'status:pass'\n")
|
||||
sb.WriteString(" TEMP_MAX=$(grep -oP '\\d+\\.?\\d*°C' $TEMP_LOG | sort -t. -k1 -n | tail -1 || echo 'unknown')\n")
|
||||
sb.WriteString(" echo \"samples:$(wc -l < $TEMP_LOG)\"\n")
|
||||
sb.WriteString(" echo \"max:$TEMP_MAX\"\n")
|
||||
sb.WriteString(" echo \"output:$(tail -10 $TEMP_LOG)\"\n")
|
||||
sb.WriteString("else\n")
|
||||
sb.WriteString(" echo 'status:skip'\n")
|
||||
sb.WriteString("fi\n")
|
||||
sb.WriteString("echo ''\n")
|
||||
sb.WriteString("kill $TEMP_PID 2>/dev/null || true\n\n")
|
||||
}
|
||||
|
||||
// 收集 dmesg 结果
|
||||
sb.WriteString("echo '===MONITOR:dmesg==='\n")
|
||||
sb.WriteString("DMESG_ERR=$(dmesg --level=err,crit,alert,emerg 2>/dev/null | tail -30 || true)\n")
|
||||
sb.WriteString("if [ -n \"$DMESG_ERR\" ]; then\n")
|
||||
sb.WriteString(" echo 'status:fail'\n")
|
||||
sb.WriteString(" echo \"output:$DMESG_ERR\"\n")
|
||||
sb.WriteString("else\n")
|
||||
sb.WriteString(" echo 'status:pass'\n")
|
||||
sb.WriteString(" echo 'output:无硬件相关内核报错'\n")
|
||||
sb.WriteString("fi\n")
|
||||
sb.WriteString("echo ''\n")
|
||||
sb.WriteString("echo '===END==='\n")
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// buildTestCommand 根据指标生成测试命令
|
||||
func buildTestCommand(m Metric, cfg Config) string {
|
||||
sec := int(cfg.Duration.Seconds())
|
||||
threads := cfg.Threads
|
||||
|
||||
switch m.Name {
|
||||
case "cpu":
|
||||
return fmt.Sprintf("stress-ng --cpu %d --cpu-method all --timeout %ds --metrics-brief --temp-path /tmp", threads, sec)
|
||||
|
||||
case "memory":
|
||||
workers := threads
|
||||
if workers > 4 {
|
||||
workers = 4
|
||||
}
|
||||
return fmt.Sprintf("stress-ng --vm %d --vm-bytes 256M --vm-method all --timeout %ds --metrics-brief", workers, sec)
|
||||
|
||||
case "disk":
|
||||
testDir := cfg.DiskDir
|
||||
if testDir == "" {
|
||||
testDir = "/tmp/stress-disk-test"
|
||||
}
|
||||
diskMB := cfg.DiskSizeMB
|
||||
if diskMB <= 0 {
|
||||
diskMB = 1024
|
||||
}
|
||||
return fmt.Sprintf("mkdir -p %s && stress-ng --iomix 2 --iomix-bytes %dM --timeout %ds --metrics-brief && rm -rf %s",
|
||||
testDir, diskMB, sec, testDir)
|
||||
|
||||
case "memnative":
|
||||
memMB := cfg.MemSizeMB
|
||||
if memMB <= 0 {
|
||||
memMB = 0 // 脚本内自动计算
|
||||
}
|
||||
if memMB > 0 {
|
||||
return fmt.Sprintf("stressapptest -s %d -M %d -f 0 -v", sec, memMB)
|
||||
}
|
||||
// 自动计算可用内存的 60%
|
||||
return fmt.Sprintf("M=$(free -m 2>/dev/null | awk '/Mem:/{print int($7*0.6)}' || echo 512) && stressapptest -s %d -M $M -f 0 -v", sec)
|
||||
|
||||
case "full":
|
||||
return fmt.Sprintf("stress-ng --cpu %d --vm 2 --vm-bytes 128M --iomix 1 --iomix-bytes 256M --timeout %ds --metrics-brief",
|
||||
threads, sec)
|
||||
|
||||
default:
|
||||
return "echo '未知测试类型'"
|
||||
}
|
||||
}
|
||||
@@ -1,269 +1,78 @@
|
||||
package stress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"auto-check/pkg/sshclient"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// ============================
|
||||
// 远程命令执行接口
|
||||
// ============================
|
||||
|
||||
// Executor 远程命令执行器
|
||||
type Executor interface {
|
||||
RunCommand(client *ssh.Client, command string) (string, error)
|
||||
}
|
||||
|
||||
// exec 执行远程命令(带超时)
|
||||
func exec(client *ssh.Client, executor Executor, cmd string) (string, error) {
|
||||
return executor.RunCommand(client, cmd)
|
||||
}
|
||||
|
||||
// execf 格式化执行
|
||||
func execf(client *ssh.Client, executor Executor, format string, args ...interface{}) (string, error) {
|
||||
return exec(client, executor, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// ============================
|
||||
// 工具检测
|
||||
// 远程工具检测
|
||||
// ============================
|
||||
|
||||
// ToolInfo 远程工具信息
|
||||
type ToolInfo struct {
|
||||
Available bool
|
||||
Path string
|
||||
Version string
|
||||
Name string
|
||||
Path string
|
||||
Version string
|
||||
}
|
||||
|
||||
// DetectTool 检测远程工具是否可用
|
||||
func DetectTool(client *ssh.Client, executor Executor, name string) ToolInfo {
|
||||
// 检查路径
|
||||
out, err := execf(client, executor, "which %s 2>/dev/null", name)
|
||||
// ToolSet 远程可用工具集合
|
||||
type ToolSet struct {
|
||||
Tools map[string]ToolInfo // name → info
|
||||
}
|
||||
|
||||
// Has 工具是否可用
|
||||
func (ts ToolSet) Has(name string) bool {
|
||||
_, ok := ts.Tools[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Get 获取工具信息
|
||||
func (ts ToolSet) Get(name string) (ToolInfo, bool) {
|
||||
t, ok := ts.Tools[name]
|
||||
return t, ok
|
||||
}
|
||||
|
||||
// DetectTools 检测远程所有相关工具是否可用
|
||||
func DetectTools(client *ssh.Client) ToolSet {
|
||||
ts := ToolSet{Tools: make(map[string]ToolInfo)}
|
||||
for _, name := range []string{"stress-ng", "stressapptest", "iperf3", "fio", "lm-sensors"} {
|
||||
if info, ok := detectOne(client, name); ok {
|
||||
ts.Tools[name] = info
|
||||
}
|
||||
}
|
||||
return ts
|
||||
}
|
||||
|
||||
// detectOne 检测单个工具
|
||||
func detectOne(client *ssh.Client, name string) (ToolInfo, bool) {
|
||||
out, err := sshclient.RunCommand(client, "which "+name+" 2>/dev/null")
|
||||
path := strings.TrimSpace(out)
|
||||
if err != nil || path == "" {
|
||||
return ToolInfo{Available: false}
|
||||
return ToolInfo{}, false
|
||||
}
|
||||
|
||||
// 获取版本
|
||||
var version string
|
||||
switch name {
|
||||
case "stress-ng":
|
||||
out, _ = execf(client, executor, "%s --version 2>&1 | head -1", path)
|
||||
out, _ = sshclient.RunCommand(client, path+" --version 2>&1 | head -1")
|
||||
version = strings.TrimSpace(out)
|
||||
case "stressapptest":
|
||||
out, _ = execf(client, executor, "%s --help 2>&1 | head -1", path)
|
||||
out, _ = sshclient.RunCommand(client, path+" --help 2>&1 | head -1")
|
||||
version = strings.TrimSpace(out)
|
||||
case "iperf3":
|
||||
out, _ = sshclient.RunCommand(client, path+" --version 2>&1 | head -1")
|
||||
version = strings.TrimSpace(out)
|
||||
case "fio":
|
||||
out, _ = sshclient.RunCommand(client, path+" --version 2>&1 | head -1")
|
||||
version = strings.TrimSpace(out)
|
||||
case "lm-sensors":
|
||||
out, _ = execf(client, executor, "%s -v 2>&1 | head -1", path)
|
||||
out, _ = sshclient.RunCommand(client, path+" -v 2>&1 | head -1")
|
||||
version = strings.TrimSpace(out)
|
||||
}
|
||||
|
||||
return ToolInfo{Available: true, Path: path, Version: version}
|
||||
}
|
||||
|
||||
// EnsureTool 检测工具,不可用则返回错误提示
|
||||
func EnsureTool(client *ssh.Client, executor Executor, name string, installHint string) (ToolInfo, error) {
|
||||
info := DetectTool(client, executor, name)
|
||||
if !info.Available {
|
||||
return info, fmt.Errorf("%s 未安装。安装方式: %s", name, installHint)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// ============================
|
||||
// 环境探测
|
||||
// ============================
|
||||
|
||||
// SystemInfo 系统基础信息
|
||||
type SystemInfo struct {
|
||||
Hostname string
|
||||
CPUModel string
|
||||
CPUCores string
|
||||
MemTotal string
|
||||
KernelVer string
|
||||
DiskInfo string
|
||||
}
|
||||
|
||||
// ProbeSystem 探测远程系统基础信息
|
||||
func ProbeSystem(client *ssh.Client, executor Executor) SystemInfo {
|
||||
info := SystemInfo{}
|
||||
|
||||
out, _ := exec(client, executor, "hostname 2>/dev/null")
|
||||
info.Hostname = strings.TrimSpace(out)
|
||||
|
||||
out, _ = exec(client, executor, "lscpu 2>/dev/null | grep 'Model name' | sed 's/Model name:\\s*//'")
|
||||
info.CPUModel = strings.TrimSpace(out)
|
||||
|
||||
out, _ = exec(client, executor, "nproc 2>/dev/null")
|
||||
info.CPUCores = strings.TrimSpace(out)
|
||||
|
||||
out, _ = exec(client, executor, "free -h 2>/dev/null | awk '/Mem:/{print $2}'")
|
||||
info.MemTotal = strings.TrimSpace(out)
|
||||
|
||||
out, _ = exec(client, executor, "uname -r 2>/dev/null")
|
||||
info.KernelVer = strings.TrimSpace(out)
|
||||
|
||||
out, _ = exec(client, executor, "lsblk -d -o NAME,SIZE,TYPE 2>/dev/null | head -5")
|
||||
info.DiskInfo = strings.TrimSpace(out)
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
// ============================
|
||||
// 温度监控
|
||||
// ============================
|
||||
|
||||
// TempMonitor 温度监控器
|
||||
type TempMonitor struct {
|
||||
client *ssh.Client
|
||||
executor Executor
|
||||
interval time.Duration
|
||||
stopCh chan struct{}
|
||||
logs []string
|
||||
}
|
||||
|
||||
// NewTempMonitor 创建温度监控器
|
||||
func NewTempMonitor(client *ssh.Client, executor Executor, interval time.Duration) *TempMonitor {
|
||||
if interval <= 0 {
|
||||
interval = 10 * time.Second
|
||||
}
|
||||
return &TempMonitor{
|
||||
client: client,
|
||||
executor: executor,
|
||||
interval: interval,
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Start 后台启动温度监控
|
||||
func (m *TempMonitor) Start() {
|
||||
go m.loop()
|
||||
}
|
||||
|
||||
// Stop 停止监控并返回所有采样
|
||||
func (m *TempMonitor) Stop() []string {
|
||||
close(m.stopCh)
|
||||
return m.logs
|
||||
}
|
||||
|
||||
func (m *TempMonitor) loop() {
|
||||
ticker := time.NewTicker(m.interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
// 初始温度
|
||||
m.sample()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-m.stopCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
m.sample()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *TempMonitor) sample() {
|
||||
// 尝试 sensors
|
||||
out, err := execf(m.client, m.executor, "sensors 2>/dev/null | grep -i 'temp\\|core\\|cpu' | head -10")
|
||||
if err == nil && strings.TrimSpace(out) != "" {
|
||||
ts := time.Now().Format("15:04:05")
|
||||
for _, line := range strings.Split(out, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line != "" {
|
||||
m.logs = append(m.logs, fmt.Sprintf("[%s] %s", ts, line))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 回退: 读 sysfs
|
||||
out, _ = execf(m.client, m.executor, `cat /sys/class/thermal/thermal_zone*/temp 2>/dev/null | while read t; do echo "$((t/1000))°C"; done`)
|
||||
if strings.TrimSpace(out) != "" {
|
||||
ts := time.Now().Format("15:04:05")
|
||||
m.logs = append(m.logs, fmt.Sprintf("[%s] %s", ts, strings.TrimSpace(out)))
|
||||
}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// dmesg 监控
|
||||
// ============================
|
||||
|
||||
// DmesgMonitor 内核日志监控
|
||||
type DmesgMonitor struct {
|
||||
client *ssh.Client
|
||||
executor Executor
|
||||
stopCh chan struct{}
|
||||
baseline string // 启动时的 dmesg 行数
|
||||
logs []string
|
||||
}
|
||||
|
||||
// NewDmesgMonitor 创建 dmesg 监控器
|
||||
func NewDmesgMonitor(client *ssh.Client, executor Executor) *DmesgMonitor {
|
||||
return &DmesgMonitor{
|
||||
client: client,
|
||||
executor: executor,
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Start 记录基线,后台监控
|
||||
func (m *DmesgMonitor) Start() {
|
||||
out, _ := exec(m.client, m.executor, "dmesg 2>/dev/null | wc -l")
|
||||
m.baseline = strings.TrimSpace(out)
|
||||
go m.loop()
|
||||
}
|
||||
|
||||
// Stop 停止监控,返回新增的硬件报错
|
||||
func (m *DmesgMonitor) Stop() []string {
|
||||
close(m.stopCh)
|
||||
|
||||
// 获取新增的 dmesg 日志中的硬件错误
|
||||
out, _ := execf(m.client, m.executor,
|
||||
`dmesg --level=err,crit,alert,emerg 2>/dev/null | tail -30`)
|
||||
if strings.TrimSpace(out) != "" {
|
||||
m.logs = append(m.logs, strings.Split(out, "\n")...)
|
||||
}
|
||||
|
||||
// 过滤硬件相关关键词
|
||||
var hwErrors []string
|
||||
keywords := []string{"error", "fail", "fault", "warn", "critical", "oom", "panic", "hardware", "thermal", "throttl"}
|
||||
for _, line := range m.logs {
|
||||
lower := strings.ToLower(line)
|
||||
for _, kw := range keywords {
|
||||
if strings.Contains(lower, kw) {
|
||||
hwErrors = append(hwErrors, strings.TrimSpace(line))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return hwErrors
|
||||
}
|
||||
|
||||
func (m *DmesgMonitor) loop() {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-m.stopCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
// 周期性检查是否有新的硬件相关日志(轻量级)
|
||||
out, _ := execf(m.client, m.executor,
|
||||
`dmesg --level=err,crit 2>/dev/null | tail -3`)
|
||||
if strings.TrimSpace(out) != "" {
|
||||
ts := time.Now().Format("15:04:05")
|
||||
for _, line := range strings.Split(out, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line != "" {
|
||||
m.logs = append(m.logs, fmt.Sprintf("[%s] %s", ts, line))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ToolInfo{Name: name, Path: path, Version: version}, true
|
||||
}
|
||||
|
||||
@@ -6,72 +6,38 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ============================
|
||||
// 测试类型定义
|
||||
// ============================
|
||||
|
||||
// TestType 压力测试类型
|
||||
type TestType string
|
||||
|
||||
const (
|
||||
// 单项压测
|
||||
TestCPU TestType = "cpu" // stress-ng CPU 压力
|
||||
TestMemory TestType = "memory" // stress-ng 内存压力
|
||||
TestDiskIO TestType = "disk" // stress-ng 磁盘IO压力
|
||||
TestMemNative TestType = "memnative" // stressapptest 内存精压
|
||||
|
||||
// 综合压测
|
||||
TestFull TestType = "full" // CPU+内存+磁盘IO 三合一
|
||||
|
||||
// 监控(自动附加)
|
||||
MonitorTemp TestType = "temp" // lm-sensors 温度监控
|
||||
MonitorDmesg TestType = "dmesg" // dmesg 内核报错监控
|
||||
TestCPU TestType = "cpu"
|
||||
TestMemory TestType = "memory"
|
||||
TestDiskIO TestType = "disk"
|
||||
TestMemNative TestType = "memnative"
|
||||
TestFull TestType = "full"
|
||||
MonitorTemp TestType = "temp"
|
||||
MonitorDmesg TestType = "dmesg"
|
||||
)
|
||||
|
||||
// ============================
|
||||
// 配置
|
||||
// ============================
|
||||
|
||||
// Config 压力测试配置
|
||||
type Config struct {
|
||||
Duration time.Duration // 总持续时间
|
||||
Threads int // CPU/内存线程数
|
||||
MemSizeMB int // 内存测试大小 (MB),0=自动(可用60%)
|
||||
DiskSizeMB int // 磁盘测试大小 (MB),0=默认1024
|
||||
DiskDir string // 磁盘测试目录,空=自动临时目录
|
||||
TempLogInt time.Duration // 温度采样间隔,0=10s
|
||||
}
|
||||
|
||||
// DefaultConfig 默认配置
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
Duration: 30 * time.Second,
|
||||
Threads: 4,
|
||||
MemSizeMB: 0, // 自动
|
||||
DiskSizeMB: 1024,
|
||||
DiskDir: "",
|
||||
TempLogInt: 10 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// 单项测试结果
|
||||
// ============================
|
||||
// 结果状态常量
|
||||
const (
|
||||
StatusPass = "pass"
|
||||
StatusFail = "fail"
|
||||
StatusSkip = "skip"
|
||||
StatusError = "error"
|
||||
StatusUnknown = "untested"
|
||||
)
|
||||
|
||||
// Result 单项测试结果
|
||||
type Result struct {
|
||||
Type TestType
|
||||
Status string // "pass" / "fail" / "skip" / "error"
|
||||
Output string // 工具输出摘要
|
||||
Error string // 错误信息
|
||||
Status string
|
||||
Output string
|
||||
Error string
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
// ============================
|
||||
// 完整测试报告
|
||||
// ============================
|
||||
|
||||
// Report 一次完整压测的报告
|
||||
// Report 完整压测报告
|
||||
type Report struct {
|
||||
IP string
|
||||
StartTime time.Time
|
||||
@@ -84,17 +50,17 @@ type Report struct {
|
||||
Errors int
|
||||
}
|
||||
|
||||
// AddResult 添加测试结果
|
||||
// AddResult 添加测试结果并更新统计
|
||||
func (r *Report) AddResult(res Result) {
|
||||
r.Results = append(r.Results, res)
|
||||
switch res.Status {
|
||||
case "pass":
|
||||
case StatusPass:
|
||||
r.Passed++
|
||||
case "fail":
|
||||
case StatusFail:
|
||||
r.Failed++
|
||||
case "skip":
|
||||
case StatusSkip:
|
||||
r.Skipped++
|
||||
case "error":
|
||||
case StatusError:
|
||||
r.Errors++
|
||||
}
|
||||
}
|
||||
@@ -114,19 +80,17 @@ func (r *Report) ToText() string {
|
||||
for _, res := range r.Results {
|
||||
icon := "✓"
|
||||
switch res.Status {
|
||||
case "fail":
|
||||
case StatusFail:
|
||||
icon = "✗"
|
||||
case "skip":
|
||||
case StatusSkip:
|
||||
icon = "⊘"
|
||||
case "error":
|
||||
case StatusError:
|
||||
icon = "⚠"
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf(" %s %-12s %s\n", icon, res.Type, res.Status))
|
||||
if res.Output != "" {
|
||||
for _, line := range strings.Split(res.Output, "\n") {
|
||||
if strings.TrimSpace(line) != "" {
|
||||
sb.WriteString(fmt.Sprintf(" %s\n", line))
|
||||
}
|
||||
for _, line := range strings.Split(res.Output, "\n") {
|
||||
if strings.TrimSpace(line) != "" {
|
||||
sb.WriteString(fmt.Sprintf(" %s\n", line))
|
||||
}
|
||||
}
|
||||
if res.Error != "" {
|
||||
|
||||
Reference in New Issue
Block a user